diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 62626cd83b..7ca485bd27 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -4877,10 +4877,11 @@ class InstanceSerializer(BaseSerializer): percent_capacity_remaining = serializers.SerializerMethodField() jobs_running = serializers.IntegerField(help_text=_('Count of jobs in the running or waiting state that are targeted for this instance'), read_only=True) jobs_total = serializers.IntegerField(help_text=_('Count of all jobs that target this instance'), read_only=True) + ip_address = serializers.IPAddressField(required=False) class Meta: model = Instance - read_only_fields = ('uuid', 'hostname', 'version', 'node_type', 'node_state') + read_only_fields = ('uuid', 'version') fields = ( "id", "type", @@ -4909,6 +4910,7 @@ class InstanceSerializer(BaseSerializer): "managed_by_policy", "node_type", "node_state", + "ip_address", ) def get_related(self, obj): @@ -4923,6 +4925,7 @@ class InstanceSerializer(BaseSerializer): def get_summary_fields(self, obj): summary = super().get_summary_fields(obj) + # use this handle to distinguish between a listView and a detailView if self.is_detail_view: summary['links'] = InstanceLinkSerializer(InstanceLink.objects.select_related('target', 'source').filter(source=obj), many=True).data @@ -4937,10 +4940,30 @@ class InstanceSerializer(BaseSerializer): else: return float("{0:.2f}".format(((float(obj.capacity) - float(obj.consumed_capacity)) / (float(obj.capacity))) * 100)) - def validate(self, attrs): - if self.instance.node_type == 'hop': - raise serializers.ValidationError(_('Hop node instances may not be changed.')) - return attrs + def validate_node_type(self, value): + # ensure that new node type is execution node-only + if not self.instance: + if value not in [Instance.Types.EXECUTION, Instance.Types.HOP]: + raise serializers.ValidationError('invalid node_type; can only create execution and hop nodes') + else: + if self.instance.node_type != value: + raise serializers.ValidationError('cannot change node_type') + + def validate_node_state(self, value): + if not self.instance: + if value not in [Instance.States.PROVISIONING, Instance.States.INSTALLED]: + raise serializers.ValidationError('net new execution node creation must be in installed or provisioning node_state') + else: + if self.instance.node_state != value and value not in [Instance.States.PROVISIONING, Instance.States.INSTALLED, Instance.States.DEPROVISIONING]: + raise serializers.ValidationError('modifying an existing instance can only be in provisioning or deprovisoning node_states') + + def validate_peers(self, value): + pass + # 1- dont wanna remove links between two control plane nodes + # 2- can of worms - reversing links + + def validate_instance_group(self, value): + pass class InstanceHealthCheckSerializer(BaseSerializer): diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index a318f36c54..0b2a1a252e 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -359,7 +359,7 @@ class DashboardJobsGraphView(APIView): return Response(dashboard_data) -class InstanceList(ListAPIView): +class InstanceList(ListCreateAPIView): name = _("Instances") model = models.Instance diff --git a/awx_collection/test/awx/test_completeness.py b/awx_collection/test/awx/test_completeness.py index 75e6bff29f..93ddd52fea 100644 --- a/awx_collection/test/awx/test_completeness.py +++ b/awx_collection/test/awx/test_completeness.py @@ -78,10 +78,11 @@ no_api_parameter_ok = { # When this tool was created we were not feature complete. Adding something in here indicates a module # that needs to be developed. If the module is found on the file system it will auto-detect that the # work is being done and will bypass this check. At some point this module should be removed from this list. -needs_development = ['inventory_script'] +needs_development = ['inventory_script', 'instance'] needs_param_development = { 'host': ['instance_id'], 'workflow_approval': ['description', 'execution_environment'], + 'instances': ['capacity_adjustment', 'enabled', 'hostname', 'ip_address', 'managed_by_policy', 'node_state', 'node_type'], } # -----------------------------------------------------------------------------------------------------------