Skip to content

Commit bc8eae4

Browse files
1101-1aquamatthias
andauthored
[aws][feat] Make a collection of Ec2 Instance types only for existing instances (#2264)
Co-authored-by: Matthias Veit <matthias_veit@yahoo.de>
1 parent ebb67be commit bc8eae4

File tree

7 files changed

+182
-9
lines changed

7 files changed

+182
-9
lines changed

plugins/aws/fix_plugin_aws/resource/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def from_api(cls: Type[AwsResourceType], json: Json, builder: GraphBuilder) -> O
198198
return parse_json(json, cls, builder, cls.mapping)
199199

200200
@classmethod
201-
def collect_resources(cls: Type[AwsResource], builder: GraphBuilder) -> None:
201+
def collect_resources(cls, builder: GraphBuilder) -> None:
202202
# Default behavior: in case the class has an ApiSpec, call the api and call collect.
203203
log.debug(f"Collecting {cls.__name__} in region {builder.region.name}")
204204
if spec := cls.api_spec:

plugins/aws/fix_plugin_aws/resource/ec2.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from functools import partial
77
from typing import ClassVar, Dict, Optional, List, Type, Any
88

9+
from boto3.exceptions import Boto3Error
910
from attrs import define, field
1011

1112
from fix_plugin_aws.aws_client import AwsClient
@@ -22,6 +23,7 @@
2223
from fix_plugin_aws.resource.kms import AwsKmsKey
2324
from fix_plugin_aws.resource.s3 import AwsS3Bucket
2425
from fix_plugin_aws.utils import ToDict, TagsValue
26+
from fix_plugin_aws.aws_client import AwsClient
2527
from fixlib.baseresources import (
2628
BaseInstance,
2729
BaseKeyPair,
@@ -384,14 +386,14 @@ class AwsEc2InferenceAcceleratorInfo:
384386

385387
@define(eq=False, slots=False)
386388
class AwsEc2InstanceType(AwsResource, BaseInstanceType):
389+
# collected via AwsEc2Instance
387390
kind: ClassVar[str] = "aws_ec2_instance_type"
388391
_kind_display: ClassVar[str] = "AWS EC2 Instance Type"
389392
_kind_description: ClassVar[str] = "AWS EC2 Instance Types are predefined virtual server configurations offered by Amazon Web Services. Each type specifies the compute, memory, storage, and networking capacity of the virtual machine. Users select an instance type based on their application's requirements, balancing performance and cost. EC2 instances can be launched, stopped, and terminated as needed for various computing workloads." # fmt: skip
390393
_docs_url: ClassVar[str] = "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html"
391394
_kind_service: ClassVar[Optional[str]] = service_name
392395
_metadata: ClassVar[Dict[str, Any]] = {"icon": "type", "group": "compute"}
393396
_aws_metadata: ClassVar[Dict[str, Any]] = {"arn_tpl": "arn:{partition}:ec2:{region}:{account}:instance/{id}"} # fmt: skip
394-
api_spec: ClassVar[AwsApiSpec] = AwsApiSpec(service_name, "describe-instance-types", "InstanceTypes")
395397
_reference_kinds: ClassVar[ModelReference] = {
396398
"successors": {
397399
"default": ["aws_ec2_instance"],
@@ -456,6 +458,29 @@ class AwsEc2InstanceType(AwsResource, BaseInstanceType):
456458
auto_recovery_supported: Optional[bool] = field(default=None)
457459
supported_boot_modes: List[str] = field(factory=list)
458460

461+
@classmethod
462+
def collect_resource_types(cls, builder: GraphBuilder, instance_types: List[str]) -> None:
463+
spec = AwsApiSpec(service_name, "describe-instance-types", "InstanceTypes")
464+
log.debug(f"Collecting {cls.__name__} in region {builder.region.name}")
465+
try:
466+
filters = [{"Name": "instance-type", "Values": instance_types}]
467+
items = builder.client.list(
468+
aws_service=spec.service,
469+
action=spec.api_action,
470+
result_name=spec.result_property,
471+
expected_errors=spec.expected_errors,
472+
Filters=filters,
473+
)
474+
cls.collect(items, builder)
475+
except Boto3Error as e:
476+
msg = f"Error while collecting {cls.__name__} in region {builder.region.name}: {e}"
477+
builder.core_feedback.error(msg, log)
478+
raise
479+
except Exception as e:
480+
msg = f"Error while collecting {cls.__name__} in region {builder.region.name}: {e}"
481+
builder.core_feedback.info(msg, log)
482+
raise
483+
459484
@classmethod
460485
def collect(cls: Type[AwsResource], json: List[Json], builder: GraphBuilder) -> None:
461486
for js in json:
@@ -467,6 +492,14 @@ def collect(cls: Type[AwsResource], json: List[Json], builder: GraphBuilder) ->
467492
# we collect instance types in all regions and make the data unique in the builder
468493
builder.global_instance_types[it.safe_name] = it
469494

495+
@classmethod
496+
def service_name(cls) -> Optional[str]:
497+
return service_name
498+
499+
@classmethod
500+
def called_collect_apis(cls) -> List[AwsApiSpec]:
501+
return [AwsApiSpec(service_name, "describe-instance-types")]
502+
470503

471504
# endregion
472505

@@ -1375,6 +1408,17 @@ class AwsEc2Instance(EC2Taggable, AwsResource, BaseInstance):
13751408
instance_maintenance_options: Optional[str] = field(default=None)
13761409
instance_user_data: Optional[str] = field(default=None)
13771410

1411+
@classmethod
1412+
def collect_resources(cls, builder: GraphBuilder) -> None:
1413+
super().collect_resources(builder)
1414+
ec2_instance_types = set()
1415+
for instance in builder.nodes(clazz=AwsEc2Instance):
1416+
ec2_instance_types.add(instance.instance_type)
1417+
if ec2_instance_types:
1418+
builder.submit_work(
1419+
service_name, AwsEc2InstanceType.collect_resource_types, builder, list(ec2_instance_types)
1420+
)
1421+
13781422
@classmethod
13791423
def collect(cls: Type[AwsResource], json: List[Json], builder: GraphBuilder) -> None:
13801424
def fetch_user_data(instance: AwsEc2Instance) -> None:

plugins/aws/fix_plugin_aws/resource/iam.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -676,11 +676,11 @@ def called_collect_apis(cls) -> List[AwsApiSpec]:
676676
]
677677

678678
@classmethod
679-
def collect_resources(cls: Type[AwsResource], builder: GraphBuilder) -> None:
679+
def collect_resources(cls, builder: GraphBuilder) -> None:
680680
# start generation of the credentials resport and pick it up later
681681
builder.client.get(service_name, "generate-credential-report")
682682
# let super handle the rest (this will take some time for the report to be done)
683-
super().collect_resources(builder) # type: ignore # mypy bug: https://github.com/python/mypy/issues/12885
683+
super().collect_resources(builder)
684684

685685
@classmethod
686686
def collect(cls: Type[AwsResource], json_list: List[Json], builder: GraphBuilder) -> None:

plugins/aws/test/graphbuilder_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def test_instance_type(builder: GraphBuilder) -> None:
1717
cloud_instance_data, ["aws", instance_type, "pricing", builder.region.id, "linux", "ondemand"]
1818
)
1919
eu_builder = builder.for_region(AwsRegion(id="eu-central-1"))
20+
builder.global_instance_types[instance_type] = AwsEc2InstanceType(id=instance_type)
2021
m4l_eu: AwsEc2InstanceType = eu_builder.instance_type(eu_builder.region, instance_type) # type: ignore
2122
assert m4l != m4l_eu
2223
assert m4l_eu == eu_builder.instance_type(eu_builder.region, instance_type)

plugins/aws/test/resources/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@ def round_trip_for(
180180
to_collect = [cls] + collect_also if collect_also else [cls]
181181
builder = build_graph(to_collect, region_name=region_name)
182182
assert len(builder.graph.nodes) > 0
183-
for node, data in builder.graph.nodes(data=True):
183+
nodes_to_process = list(builder.graph.nodes(data=True))
184+
for node, data in nodes_to_process:
184185
node.connect_in_graph(builder, data.get("source", {}))
185186
check_single_node(node)
186187
first = next(iter(builder.resources_of(cls)))
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
{
2+
"InstanceTypes": [
3+
{
4+
"InstanceType": "m4.large",
5+
"CurrentGeneration": true,
6+
"FreeTierEligible": false,
7+
"SupportedUsageClasses": [
8+
"on-demand",
9+
"spot"
10+
],
11+
"SupportedRootDeviceTypes": [
12+
"ebs"
13+
],
14+
"SupportedVirtualizationTypes": [
15+
"hvm"
16+
],
17+
"BareMetal": false,
18+
"Hypervisor": "nitro",
19+
"ProcessorInfo": {
20+
"SupportedArchitectures": [
21+
"x86_64"
22+
],
23+
"SustainedClockSpeedInGhz": 3.5
24+
},
25+
"VCpuInfo": {
26+
"DefaultVCpus": 8,
27+
"DefaultCores": 4,
28+
"DefaultThreadsPerCore": 2,
29+
"ValidCores": [
30+
2,
31+
4
32+
],
33+
"ValidThreadsPerCore": [
34+
1,
35+
2
36+
]
37+
},
38+
"MemoryInfo": {
39+
"SizeInMiB": 16384
40+
},
41+
"InstanceStorageSupported": false,
42+
"InstanceStorageInfo": {
43+
"EbsInfo": {
44+
"EbsStorageSupported": false,
45+
"EbsStorageInfo": {
46+
"VolumeTypes": [
47+
"standard"
48+
],
49+
"VolumeSizeInGiBMin": 1,
50+
"VolumeSizeInGiBMax": 1024
51+
}
52+
},
53+
"InstanceStorageSupported": false,
54+
"InstanceStorageInfo": {
55+
"VolumeTypes": [
56+
"standard"
57+
],
58+
"VolumeSizeInGiBMin": 1,
59+
"VolumeSizeInGiBMax": 1024
60+
}
61+
},
62+
"GpuInfo": {
63+
"GPUsSupported": false,
64+
"GPUSupported": false,
65+
"GPUSupportedOnDemand": false,
66+
"GPUSupportedSpot": false
67+
},
68+
"FpgaInfo": {
69+
"FPGAsSupported": false,
70+
"FPGASupported": false,
71+
"FPGASupportedOnDemand": false,
72+
"FPGASupportedSpot": false
73+
},
74+
"InferenceAcceleratorInfo": {
75+
"InferenceAcceleratorsSupported": false,
76+
"InferenceAcceleratorsSupportedOnDemand": false,
77+
"InferenceAcceleratorsSupportedSpot": false
78+
},
79+
"EbsInfo": {
80+
"EbsOptimizedSupport": "default",
81+
"EncryptionSupport": "supported",
82+
"EbsOptimizedInfo": {
83+
"BaselineBandwidthInMbps": 2500,
84+
"BaselineThroughputInMBps": 312.5,
85+
"BaselineIops": 12000,
86+
"MaximumBandwidthInMbps": 10000,
87+
"MaximumThroughputInMBps": 1250,
88+
"MaximumIops": 40000
89+
},
90+
"NvmeSupport": "required"
91+
},
92+
"NetworkInfo": {
93+
"NetworkPerformance": "Up to 12.5 Gigabit",
94+
"MaximumNetworkInterfaces": 4,
95+
"MaximumNetworkCards": 1,
96+
"DefaultNetworkCardIndex": 0,
97+
"NetworkCards": [
98+
{
99+
"NetworkCardIndex": 0,
100+
"NetworkPerformance": "Up to 12.5 Gigabit",
101+
"MaximumNetworkInterfaces": 4
102+
}
103+
],
104+
"Ipv4AddressesPerInterface": 15,
105+
"Ipv6AddressesPerInterface": 15,
106+
"Ipv6Supported": true,
107+
"EnaSupport": "required",
108+
"EfaSupported": false,
109+
"EncryptionInTransitSupported": true
110+
},
111+
"PlacementGroupInfo": {
112+
"SupportedStrategies": [
113+
"cluster",
114+
"partition",
115+
"spread"
116+
]
117+
},
118+
"HibernationSupported": false,
119+
"BurstablePerformanceSupported": false,
120+
"DedicatedHostsSupported": true,
121+
"AutoRecoverySupported": true,
122+
"SupportedBootModes": [
123+
"legacy-bios",
124+
"uefi"
125+
]
126+
}
127+
]
128+
}

plugins/aws/test/resources/service_quotas_test.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from fix_plugin_aws.resource.base import AwsResource
77
from fix_plugin_aws.aws_client import AwsClient
88
from fix_plugin_aws.resource.base import GraphBuilder, AwsRegion
9-
from fix_plugin_aws.resource.ec2 import AwsEc2InstanceType, AwsEc2Vpc
9+
from fix_plugin_aws.resource.ec2 import AwsEc2InstanceType, AwsEc2Instance, AwsEc2Vpc
1010
from fix_plugin_aws.resource.elbv2 import AwsAlb
1111
from fix_plugin_aws.resource.iam import AwsIamServerCertificate
1212
from fix_plugin_aws.resource.service_quotas import AwsServiceQuota, RegionalQuotas
@@ -20,11 +20,10 @@ def test_service_quotas() -> None:
2020

2121

2222
def test_instance_type_quotas() -> None:
23-
_, builder = round_trip_for(AwsServiceQuota, "usage", "quota_type")
24-
AwsEc2InstanceType.collect_resources(builder)
23+
_, builder = round_trip_for(AwsServiceQuota, "usage", "quota_type", collect_also=[AwsEc2Instance])
2524
for _, it in builder.global_instance_types.items():
2625
builder.add_node(it, {})
27-
expect_quotas(builder, 3)
26+
expect_quotas(builder, 5)
2827

2928

3029
def test_volume_type_quotas() -> None:

0 commit comments

Comments
 (0)