diff --git a/CHANGELOG.md b/CHANGELOG.md index da34a8acb..105242fdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 2.5.2 2024-04-25 + +## Fix + +- Redirection of "Scope" API returned UI instead API +- add missing securitycontext and missing volumeMounts in K8S deployment +- Consumption were not calculated on resource deletion + + # 2.5.1 2024-02-08 ## Fix diff --git a/Squest/version.py b/Squest/version.py index 037904b86..9055aa17e 100644 --- a/Squest/version.py +++ b/Squest/version.py @@ -1,2 +1,2 @@ -__version__ = "2.5.1" +__version__ = "2.5.2" VERSION = __version__ diff --git a/k8s/squest_k8s/tasks/05-django.yml b/k8s/squest_k8s/tasks/05-django.yml index 65986e3bb..0f4959f40 100644 --- a/k8s/squest_k8s/tasks/05-django.yml +++ b/k8s/squest_k8s/tasks/05-django.yml @@ -229,6 +229,8 @@ service: django spec: serviceAccountName: squest-sa + securityContext: + fsGroup: 999 dnsConfig: options: - name: ndots @@ -260,6 +262,11 @@ envFrom: - configMapRef: name: django-env + volumeMounts: + - mountPath: /app/media + name: django-media + - mountPath: /app/Squest/ldap_config.py + name: ldap-config - name: nginx image: nginx:1.23.4-alpine command: ["nginx", "-c", "/etc/nginx/squest/nginx.conf"] @@ -268,10 +275,13 @@ volumeMounts: - name: nginx-config mountPath: /etc/nginx/squest + readOnly: true - mountPath: /app/static name: django-static - - mountPath: /app/Squest/ldap_config.py - name: ldap-config + readOnly: true + - name: django-media + mountPath: /app/media + readOnly: true restartPolicy: Always volumes: - name: django-media diff --git a/profiles/api/views/scope_api_views.py b/profiles/api/views/scope_api_views.py index 4208572e3..96a655557 100644 --- a/profiles/api/views/scope_api_views.py +++ b/profiles/api/views/scope_api_views.py @@ -84,4 +84,4 @@ class RedirectScopeDetails(RedirectView): def get_redirect_url(self, *args, **kwargs): pk = kwargs.pop("pk") scope = get_object_or_404(Scope, pk=pk) - return scope.get_url() + return scope.get_url().replace('/ui/', '/api/') diff --git a/pyproject.toml b/pyproject.toml index f8994539c..e4ab1935a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "squest" -version = "2.5.1" +version = "2.5.2" description = "Service catalog on top of Red Hat Ansible Automation Platform(RHAAP)/AWX (formerly known as Ansible Tower)" authors = ["Nicolas Marcq ", "Elias Boulharts "] license = "MIT" diff --git a/resource_tracker_v2/api/serializers/resource_serializer.py b/resource_tracker_v2/api/serializers/resource_serializer.py index da970938c..cec9369d6 100644 --- a/resource_tracker_v2/api/serializers/resource_serializer.py +++ b/resource_tracker_v2/api/serializers/resource_serializer.py @@ -59,10 +59,9 @@ def create(self, validated_data): resource_attributes = validated_data.pop('resource_attributes', list()) new_resource = super().create(validated_data) for attribute in resource_attributes: + attribute_definition = AttributeDefinition.objects.get(name=attribute["attribute_definition"].get('name')) - ResourceAttribute.objects.create(value=attribute.pop('value'), - resource=new_resource, - attribute_definition=attribute_definition) + new_resource.set_attribute(attribute_definition, attribute.pop('value')) return new_resource def update(self, instance, validated_data): @@ -80,7 +79,11 @@ def update(self, instance, validated_data): # the resource attribute is not yet created ResourceAttribute.objects.create(resource=instance, attribute_definition=attribute_def, value=attribute.get('value', 0)) - + # notify transformer (we should have only one single transformer) + transformer = Transformer.objects.get(attribute_definition=attribute_def, + resource_group=instance.resource_group) + transformer.calculate_total_produced() + transformer.notify_parent() except AttributeDefinition.DoesNotExist: raise serializers.ValidationError({ attribute_item_name: f"'{attribute_item_name}' is not a valid attribute of the resource group {instance.resource_group.name}" diff --git a/resource_tracker_v2/migrations/0006_force_calculate_consumption.py b/resource_tracker_v2/migrations/0006_force_calculate_consumption.py new file mode 100644 index 000000000..4dbc6d5a8 --- /dev/null +++ b/resource_tracker_v2/migrations/0006_force_calculate_consumption.py @@ -0,0 +1,39 @@ +# Generated by Django 4.2.5 on 2024-04-24 13:49 + +from django.db import migrations + + +def update_all_resource_consumption(apps, schema_editor): + Transformer = apps.get_model('resource_tracker_v2', 'Transformer') + ResourceAttribute = apps.get_model('resource_tracker_v2', 'ResourceAttribute') + + for transformer in Transformer.objects.all(): + # update total produced + total_produced = 0 + all_resource_att = ResourceAttribute.objects.filter( + resource_id__in=transformer.resource_group.resources.values("id"), + attribute_definition=transformer.attribute_definition) + for attribute in all_resource_att: + total_produced += attribute.value + transformer.total_produced = total_produced + transformer.save() + + # update total consumed + total_consumed = 0 + child_transformers = Transformer.objects.filter(consume_from_attribute_definition=transformer.attribute_definition, + consume_from_resource_group=transformer.resource_group) + for child_transformer in child_transformers: + total_consumed += child_transformer.total_produced / child_transformer.factor + transformer.total_consumed = total_consumed + transformer.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("resource_tracker_v2", "0005_auto_20230803_1126"), + ] + + operations = [ + migrations.RunPython(update_all_resource_consumption), + ] diff --git a/service_catalog/models/instance.py b/service_catalog/models/instance.py index 702d69ffd..0b9068e07 100644 --- a/service_catalog/models/instance.py +++ b/service_catalog/models/instance.py @@ -193,7 +193,8 @@ def reset_to_last_stable_state(self): self.state = InstanceState.AVAILABLE def delete_linked_resources(self): - self.resources.filter(is_deleted_on_instance_deletion=True).delete() + for resource in self.resources.filter(is_deleted_on_instance_deletion=True): + resource.delete() @classmethod def on_create_call_hook_manager(cls, sender, instance, created, *args, **kwargs): diff --git a/tests/test_resource_tracker_v2/test_api/test_resource_api_views.py b/tests/test_resource_tracker_v2/test_api/test_resource_api_views.py index 452a68506..d35688f08 100644 --- a/tests/test_resource_tracker_v2/test_api/test_resource_api_views.py +++ b/tests/test_resource_tracker_v2/test_api/test_resource_api_views.py @@ -214,11 +214,19 @@ def test_update_resource_patch_one_attribute(self): self.assertEqual(2, self.server1.resource_attributes.get(attribute_definition=self.memory_attribute).value) def test_delete_resource(self): + transformer = Transformer.objects.get(attribute_definition=self.core_attribute, + resource_group=self.cluster) + available_before = transformer.available + resource_to_delete = self.server1.id response = self.client.delete(self._details_url, format='json') self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertFalse(Resource.objects.filter(id=resource_to_delete).exists()) + # check consumption updated + transformer.refresh_from_db() + self.assertEqual(transformer.available, available_before - 10) + def test_update_resource_group_of_resource(self): new_rg = ResourceGroup.objects.create(name="new_rg") data = { diff --git a/tests/test_resource_tracker_v2/test_api/test_serializers/test_resource_serializer.py b/tests/test_resource_tracker_v2/test_api/test_serializers/test_resource_serializer.py index 9c29c3bde..b83922903 100644 --- a/tests/test_resource_tracker_v2/test_api/test_serializers/test_resource_serializer.py +++ b/tests/test_resource_tracker_v2/test_api/test_serializers/test_resource_serializer.py @@ -1,5 +1,5 @@ from resource_tracker_v2.api.serializers.resource_serializer import ResourceSerializer -from resource_tracker_v2.models import Resource +from resource_tracker_v2.models import Resource, Transformer from tests.test_resource_tracker_v2.base_test_resource_tracker_v2 import BaseTestResourceTrackerV2API @@ -13,6 +13,11 @@ def _validate_created(self): self.assertEqual(self.number_resource_before + 1, Resource.objects.all().count()) def test_create_resource(self): + transformer = Transformer.objects.get(attribute_definition=self.core_attribute, + resource_group=self.cluster) + available_before = transformer.available + core_attribute_value = 10 + data = { "resource_group": self.cluster.id, "name": "new_server", @@ -20,7 +25,7 @@ def test_create_resource(self): "is_deleted_on_instance_deletion": False, "resource_attributes": [ {"name": "core", - "value": 10 + "value": core_attribute_value } ], } @@ -28,3 +33,31 @@ def test_create_resource(self): self.assertTrue(serializer.is_valid()) serializer.save() self._validate_created() + + transformer.refresh_from_db() + self.assertEqual(transformer.available, available_before + core_attribute_value) + + def test_update_resource(self): + transformer = Transformer.objects.get(attribute_definition=self.core_attribute, + resource_group=self.cluster) + available_before = transformer.available + core_attribute_before = 10 + new_core_attribute_value = 20 + + data = { + "resource_group": self.cluster.id, + "name": "new_server", + "service_catalog_instance": None, + "is_deleted_on_instance_deletion": False, + "resource_attributes": [ + {"name": "core", + "value": new_core_attribute_value + } + ], + } + serializer = ResourceSerializer(instance=self.server1, data=data) + self.assertTrue(serializer.is_valid()) + serializer.save() + # check consumption updated + transformer.refresh_from_db() + self.assertEqual(transformer.available, available_before - core_attribute_before + new_core_attribute_value) diff --git a/tests/test_resource_tracker_v2/test_model/test_resource.py b/tests/test_resource_tracker_v2/test_model/test_resource.py index 6bec3b8dd..f6224e2eb 100644 --- a/tests/test_resource_tracker_v2/test_model/test_resource.py +++ b/tests/test_resource_tracker_v2/test_model/test_resource.py @@ -1,7 +1,6 @@ from copy import copy -from django.contrib.auth.models import User - +from resource_tracker_v2.models import Transformer from resource_tracker_v2.models.resource import Resource, InvalidAttributeDefinition from service_catalog.models import Service, Instance, InstanceState from tests.test_resource_tracker_v2.base_test_resource_tracker_v2 import BaseTestResourceTrackerV2 @@ -58,3 +57,26 @@ def test_set_attribute_on_invalid_attribute(self): server2 = self.cluster.create_resource(name="server2") with self.assertRaises(InvalidAttributeDefinition): server2.set_attribute(self.vcpu_attribute, 10) + + def test_transformer_consumption_updated_on_resource_delete(self): + transformer = Transformer.objects.get(attribute_definition=self.core_attribute, + resource_group=self.cluster) + available_before = transformer.available + self.server1.delete() + # check consumption updated + transformer.refresh_from_db() + self.assertEqual(transformer.available, available_before - 10) + + def test_consumption_updated_on_delete_resource_linked_to_instance(self): + self._prepare_service_catalog() + + transformer = Transformer.objects.get(attribute_definition=self.vcpu_attribute, + resource_group=self.single_vms) + available_before = transformer.available + self.test_instance.delete() + # check resource is deleted + self.assertFalse(Resource.objects.filter(id=self.resource_id_to_delete).exists()) + + # check consumption updated + transformer.refresh_from_db() + self.assertEqual(transformer.available, available_before - 5) diff --git a/tests/test_resource_tracker_v2/test_views/test_resource_group_resources_views.py b/tests/test_resource_tracker_v2/test_views/test_resource_group_resources_views.py index 8727f1757..3d8616922 100644 --- a/tests/test_resource_tracker_v2/test_views/test_resource_group_resources_views.py +++ b/tests/test_resource_tracker_v2/test_views/test_resource_group_resources_views.py @@ -2,7 +2,7 @@ from django.urls import reverse -from resource_tracker_v2.models import Resource, ResourceGroup +from resource_tracker_v2.models import Resource, ResourceGroup, Transformer from tests.test_resource_tracker_v2.base_test_resource_tracker_v2 import BaseTestResourceTrackerV2 @@ -42,19 +42,28 @@ def test_resource_group_resources_create(self): f"{self.memory_attribute.name}": 20, "is_deleted_on_instance_deletion": True } + # Check consumption before update + transformer = Transformer.objects.get(attribute_definition=self.core_attribute, + resource_group=self.cluster) + available_before = transformer.available + core_attribute_value = 10 + response = self.client.post(url, data=data) self.assertEqual(302, response.status_code) self.assertTrue(Resource.objects.filter(name="new_resource", resource_group=self.cluster).exists()) target_resource = Resource.objects.get(name="new_resource", resource_group=self.cluster) - self.assertEqual(10, target_resource.resource_attributes. + self.assertEqual(core_attribute_value, target_resource.resource_attributes. filter(attribute_definition=self.core_attribute).first().value) self.assertEqual(20, target_resource.resource_attributes. filter(attribute_definition=self.memory_attribute).first().value) self.assertEqual(0, target_resource.resource_attributes. filter(attribute_definition=self.three_par_attribute).first().value) + transformer.refresh_from_db() + self.assertEqual(transformer.available, available_before + core_attribute_value) + def test_resource_group_resources_edit(self): args = { "resource_group_id": self.cluster.id,