diff --git a/containers/Dockerfile_dev b/containers/Dockerfile_dev index c97bf77e..8ddbe6bc 100644 --- a/containers/Dockerfile_dev +++ b/containers/Dockerfile_dev @@ -1,9 +1,9 @@ FROM registry.suse.com/bci/python:3.11 -COPY requirements.txt /pcw/ +COPY requirements.txt requirements_k8s.txt requirements_test.txt /pcw/ RUN source /etc/os-release && zypper addrepo -G -cf "https://download.opensuse.org/repositories/SUSE:/CA/$VERSION_ID/SUSE:CA.repo" && \ zypper -n in ca-certificates-suse gcc libffi-devel && \ - pip install --no-cache-dir wheel && pip install --no-cache-dir -r /pcw/requirements.txt && zypper clean && rm -rf /var/cache + pip install --no-cache-dir wheel && pip install --no-cache-dir -r /pcw/requirements_test.txt && zypper clean && rm -rf /var/cache ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 UWSGI_WSGI_FILE=/pcw/webui/wsgi.py UWSGI_MASTER=1 ENV UWSGI_HTTP_AUTO_CHUNKED=1 UWSGI_HTTP_KEEPALIVE=1 UWSGI_LAZY_APPS=1 UWSGI_WSGI_ENV_BEHAVIOR=holy diff --git a/ocw/lib/dump_state.py b/ocw/lib/dump_state.py index 27b3a5bf..f7256626 100644 --- a/ocw/lib/dump_state.py +++ b/ocw/lib/dump_state.py @@ -1,7 +1,9 @@ +import os import logging import traceback from webui.PCWConfig import PCWConfig from ocw.lib.azure import Azure +from ocw.lib.ec2 import EC2 from ocw.enums import ProviderChoice from ocw.lib.influx import Influx @@ -9,6 +11,12 @@ def dump_state(): + if os.getenv("INFLUX_TOKEN") is None: + logger.warning("INFLUX_TOKEN is not set, dumping state is not possible") + return + if not PCWConfig.has("influxdb/url"): + logger.warning("pcw.ini missing influxdb configuration, dumping state is not possible") + return for namespace in PCWConfig.get_namespaces_for("influxdb"): try: providers = PCWConfig.get_providers_for("influxdb", namespace) @@ -38,6 +46,25 @@ def dump_state(): namespace, Azure(namespace).get_img_versions_count, ) + if ProviderChoice.EC2 in providers: + Influx().dump_resource( + ProviderChoice.EC2.value, + Influx.VMS_QUANTITY, + namespace, + EC2(namespace).count_all_instances + ) + Influx().dump_resource( + ProviderChoice.EC2.value, + Influx.IMAGES_QUANTITY, + namespace, + EC2(namespace).count_all_images + ) + Influx().dump_resource( + ProviderChoice.EC2.value, + Influx.VOLUMES_QUANTITY, + namespace, + EC2(namespace).count_all_volumes + ) except Exception: logger.exception( "[%s] Dump state failed!: \n %s", namespace, traceback.format_exc() diff --git a/ocw/lib/ec2.py b/ocw/lib/ec2.py index dab2b383..7a43a0c7 100644 --- a/ocw/lib/ec2.py +++ b/ocw/lib/ec2.py @@ -116,6 +116,13 @@ def volume_protected(self, volume: dict) -> bool: def list_instances(self, region: str) -> list: return list(self.ec2_resource(region).instances.all()) + def count_all_instances(self) -> int: + instance_quantity = 0 + for region in self.all_regions: + instances = self.list_instances(region=region) + instance_quantity += len(instances) + return instance_quantity + def get_all_regions(self) -> list: regions_resp = self.ec2_client(EC2.default_region).describe_regions() regions = [region['RegionName'] for region in regions_resp['Regions']] @@ -324,6 +331,20 @@ def report_cleanup_results(self, vpc_errors: list, vpc_notify: list, vpc_locked: if len(vpc_locked) > 0: send_mail('VPC deletion locked by running VMs', '\n'.join(vpc_locked)) + def count_all_images(self) -> int: + all_images_cnt = 0 + for region in self.all_regions: + response = self.ec2_client(region).describe_images(Owners=['self']) + all_images_cnt += len(response['Images']) + return all_images_cnt + + def count_all_volumes(self) -> int: + all_volumes_cnt = 0 + for region in self.all_regions: + response = self.ec2_client(region).describe_volumes() + all_volumes_cnt += len(response['Volumes']) + return all_volumes_cnt + def cleanup_images(self, valid_period_days: float) -> None: self.log_dbg('Call cleanup_images') for region in self.all_regions: diff --git a/ocw/lib/influx.py b/ocw/lib/influx.py index 774ebb50..9b921c5f 100644 --- a/ocw/lib/influx.py +++ b/ocw/lib/influx.py @@ -17,6 +17,7 @@ class Influx: VMS_QUANTITY: str = "vms_quantity" IMAGES_QUANTITY: str = "images_quantity" DISK_QUANTITY: str = "disk_quantity" + VOLUMES_QUANTITY: str = "volumes_quanity" IMAGE_VERSION_QUANTITY: str = "img_version_quantity" NAMESPACE_TAG: str = "namespace" diff --git a/tests/test_ec2.py b/tests/test_ec2.py index 34f4fe41..ff1f9474 100644 --- a/tests/test_ec2.py +++ b/tests/test_ec2.py @@ -47,6 +47,7 @@ def mocked_client(): mocked_ec2_resource.Vpc = mocked_vpc mocked_ec2_resource.meta = mocked_meta mocked_ec2_resource.VpcPeeringConnection = lambda id: MockedVpcPeeringConnection() + mocked_ec2_resource.instances = MockedCollectionWithAllMethod() mocked_meta.client = mocked_client # don't mix up this with EC2.delete_vpc . this one is boto3 side of the call mocked_client.delete_vpc = mocked_boto3_delete_vpc @@ -507,3 +508,27 @@ def mocked_get_boolean(config_path, field=None): ec2_patch.cleanup_all() assert called_stack == ['cleanup_images', 'cleanup_snapshots', 'cleanup_volumes', 'cleanup_vpcs'] + + +def test_count_all_instances(ec2_patch): + assert ec2_patch.count_all_instances() == 1 + + +def test_count_all_images(ec2_patch): + MockedEC2Client.response = { + 'Images': [ + {'Name': Faker().uuid4(), 'CreationDate': now_age_str, 'ImageId': 0}, + {'Name': Faker().uuid4(), 'CreationDate': older_than_max_age_str, 'ImageId': 2}, + ] + } + assert ec2_patch.count_all_images() == 2 + + +def test_count_all_volumes(ec2_patch): + MockedEC2Client.response = { + 'Volumes': [{'VolumeId': MockedEC2Client.volumeid_to_delete, 'CreateTime': older_than_max_age_date}, + {'VolumeId': 'too_young_to_die', 'CreateTime': now_age_date}, + {'VolumeId': MockedEC2Client.volumeid_to_delete, 'CreateTime': older_than_max_age_date, + 'Tags': [{'Key': 'pcw_ignore', 'Value': '1'}]}, ] + } + assert ec2_patch.count_all_volumes() == 3