Skip to content

Commit

Permalink
[plugins/digitalocean][feat] Collect droplet neighbors (#1670)
Browse files Browse the repository at this point in the history
  • Loading branch information
meln1k committed Jun 19, 2023
1 parent b4e121a commit 9b4c546
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 0 deletions.
5 changes: 5 additions & 0 deletions plugins/digitalocean/resoto_plugin_digitalocean/client.py
Expand Up @@ -156,6 +156,11 @@ def list_project_resources(self, project_id: str) -> List[Json]:
def list_droplets(self) -> List[Json]:
return self._fetch("/droplets", "droplets")

def list_droplets_neighbors_ids(self) -> List[List[str]]:
json_obj = self._fetch("/reports/droplet_neighbors_ids", "neighbor_ids")
result = [[str(id) for id in droplet_ids] for droplet_ids in json_obj if isinstance(droplet_ids, list)]
return result

def list_regions(self) -> List[Json]:
return self._fetch("/regions", "regions")

Expand Down
36 changes: 36 additions & 0 deletions plugins/digitalocean/resoto_plugin_digitalocean/collector.py
Expand Up @@ -8,10 +8,12 @@
from resotolib.baseresources import BaseResource, EdgeType, InstanceStatus, VolumeStatus
from resotolib.graph import Graph
from resotolib.types import Json
from hashlib import sha256
from .client import StreamingWrapper
from .resources import (
DigitalOceanDroplet,
DigitalOceanDropletSize,
DigitalOceanDropletNeighborhood,
DigitalOceanProject,
DigitalOceanRegion,
DigitalOceanTeam,
Expand Down Expand Up @@ -65,6 +67,7 @@
alert_policy_id,
parse_tag,
parse_tags,
droplet_neighborhood_id,
)

log = logging.getLogger("resoto." + __name__)
Expand Down Expand Up @@ -522,6 +525,39 @@ def get_size(droplet: Json) -> Json:
},
)

neighborhoods = self.client.list_droplets_neighbors_ids()
neighbors_json = []
for neighbors in neighborhoods:
m = sha256()
neighbors = sorted(neighbors)
m.update(DigitalOceanDropletNeighborhood.kind.encode())
for droplet in neighbors or []:
m.update(droplet.encode())
id = m.hexdigest()[0:16]
neighbors_json.append(
{
"droplets": neighbors,
"id": id,
}
)
instances_to_region = {str(droplet["id"]): region_id(droplet["region"]["slug"]) for droplet in instances}
self.collect_resource(
neighbors_json,
resource_class=DigitalOceanDropletNeighborhood,
attr_map={
"id": "id",
"urn": lambda n: droplet_neighborhood_id(n["id"]),
"droplets": "droplets",
},
search_map={
"_region": ["urn", lambda neighbor: instances_to_region[neighbor["droplets"][0]]],
"__droplets": ["urn", lambda neighbor: [droplet_id(id) for id in neighbor["droplets"]]],
},
successors={
EdgeType.default: ["__droplets"],
},
)

@metrics_collect_regions.time()
def collect_regions(self) -> None:
regions = self.client.list_regions()
Expand Down
12 changes: 12 additions & 0 deletions plugins/digitalocean/resoto_plugin_digitalocean/resources.py
Expand Up @@ -24,6 +24,7 @@
BaseDNSZone,
BaseDNSRecord,
ModelReference,
PhantomBaseResource,
)
from resotolib.graph import Graph
import time
Expand Down Expand Up @@ -231,6 +232,17 @@ def tag_resource_name(self) -> Optional[str]:
return "droplet"


@define(eq=False, slots=False)
class DigitalOceanDropletNeighborhood(DigitalOceanResource, PhantomBaseResource):
"""A DigitalOcean Droplet Neighborhood Resource
Represents a physical hardware server where droplets can be placed.
"""

kind: ClassVar[str] = "digitalocean_droplet_neighborhood"
droplets: Optional[List[str]] = None


@define(eq=False, slots=False)
class DigitalOceanKubernetesCluster(DigitalOceanResource, BaseResource):
"""DigitalOcean Kubernetes Cluster"""
Expand Down
4 changes: 4 additions & 0 deletions plugins/digitalocean/resoto_plugin_digitalocean/utils.py
Expand Up @@ -146,6 +146,10 @@ def alert_policy_id(value: str) -> str:
return f"do:alert:{value}"


def droplet_neighborhood_id(value: str) -> str:
return f"do:neighborhood:{value}"


tag_value_sep: str = "--"


Expand Down
1 change: 1 addition & 0 deletions plugins/digitalocean/test/fixtures/__init__.py
@@ -1,5 +1,6 @@
# flake8: noqa F401
from .droplets import droplets as droplets
from .neighbor_ids import neighbor_ids as neighbor_ids
from .regions import regions as regions
from .volumes import volumes as volumes
from .vpcs import vpcs as vpcs
Expand Down
1 change: 1 addition & 0 deletions plugins/digitalocean/test/fixtures/neighbor_ids.py
@@ -0,0 +1 @@
neighbor_ids = [["289110074", "290075243"]]
11 changes: 11 additions & 0 deletions plugins/digitalocean/test/test_collector.py
Expand Up @@ -29,13 +29,15 @@
DigitalOceanDomainRecord,
DigitalOceanFirewall,
DigitalOceanAlertPolicy,
DigitalOceanDropletNeighborhood,
)
from resotolib.baseresources import Cloud, EdgeType, GraphRoot, InstanceStatus, VolumeStatus
from resotolib.core.actions import CoreFeedback
from resotolib.graph import Graph
from resotolib.graph import sanitize
from .fixtures import (
droplets,
neighbor_ids,
regions,
volumes,
vpcs,
Expand Down Expand Up @@ -169,6 +171,7 @@ def test_collect_droplets() -> None:
"list_droplets": droplets,
"list_vpcs": vpcs,
"list_tags": tags,
"list_droplets_neighbors_ids": neighbor_ids,
}
)
graph = prepare_graph(do_client)
Expand Down Expand Up @@ -216,6 +219,14 @@ def test_collect_droplets() -> None:
assert droplet.ctime == datetime.datetime(2022, 3, 3, 16, 26, 55, tzinfo=datetime.timezone.utc)
assert droplet.tags == {"droplet_tag": None}

neighborhood: DigitalOceanDropletNeighborhood = graph.search_first(
"kind", DigitalOceanDropletNeighborhood.kind
) # type: ignore
assert neighborhood.droplets == ["289110074", "290075243"]
check_edges(graph, neighborhood.urn, "do:droplet:289110074")
check_edges(graph, neighborhood.urn, "do:droplet:290075243")
check_edges(graph, "do:region:fra1", neighborhood.urn)


def test_collect_volumes() -> None:
do_client = ClientMock(
Expand Down

0 comments on commit 9b4c546

Please sign in to comment.