This is an automated email from the ASF dual-hosted git repository.
lahirujayathilake pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git
The following commit(s) were added to refs/heads/main by this push:
new 1a08f2acc Add resource type selection and AWS/SLURM preference support
1a08f2acc is described below
commit 1a08f2acc70c90c5f063f87963d34ff1c6a81d78
Author: lahiruj <[email protected]>
AuthorDate: Tue Nov 18 16:05:11 2025 -0500
Add resource type selection and AWS/SLURM preference support
- Add ResourceType enum model for frontend resource type handling
- Implement AwsComputeResourcePreference and SlurmComputeResourcePreference
models
- Add GroupAccountSSHProvisionerConfig model for SSH account provisioning
- Update GroupComputeResourcePreference to handle resource type and
specific preferences
- ComputePreference.vue UI to support AWS and SLURM resource types
- Update serializers to properly handle resourceType and
specificPreferences types
---
.../ComputePreference.vue | 179 ++++-
.../django_airavata/apps/api/serializers.py | 853 ++++++++++++++++++++-
.../api/static/django_airavata_api/js/index.js | 8 +
.../js/models/AwsComputeResourcePreference.js | 21 +
.../js/models/GroupAccountSSHProvisionerConfig.js | 18 +
.../js/models/GroupComputeResourcePreference.js | 235 +++++-
.../django_airavata_api/js/models/ResourceType.js | 5 +
.../js/models/SlurmComputeResourcePreference.js | 49 ++
.../django_airavata/apps/api/views.py | 156 ++++
9 files changed, 1451 insertions(+), 73 deletions(-)
diff --git
a/airavata-django-portal/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
b/airavata-django-portal/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
index 4c4b41d7f..7bbd2a2e2 100644
---
a/airavata-django-portal/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
+++
b/airavata-django-portal/django_airavata/apps/admin/static/django_airavata_admin/src/components/admin/group_resource_preferences/ComputePreference.vue
@@ -71,17 +71,78 @@
</ssh-credential-selector>
</b-form-group>
<b-form-group
- label="Allocation Project Number"
- label-for="allocation-number"
+ label="Resource Type"
+ label-for="resource-type"
+
:invalid-feedback="validationFeedback.resourceType.invalidFeedback"
+ :state="validationFeedback.resourceType.state"
>
- <b-form-input
- id="allocation-number"
- type="text"
- v-model="data.allocationProjectNumber"
+ <b-form-select
+ id="resource-type"
+ v-model="data.resourceType"
+ :options="resourceTypeOptions"
:disabled="!userHasWriteAccess"
+ :state="validationFeedback.resourceType.state"
+ @change="onResourceTypeChange"
>
- </b-form-input>
+ <template slot="first">
+ <option :value="null">Select a resource type</option>
+ </template>
+ </b-form-select>
</b-form-group>
+ <!-- SLURM-specific fields -->
+ <template v-if="isResourceType('SLURM')">
+ <b-form-group
+ label="Allocation Project Number"
+ label-for="allocation-number"
+ >
+ <b-form-input
+ id="allocation-number"
+ type="text"
+ v-model="data.allocationProjectNumber"
+ :disabled="!userHasWriteAccess"
+ >
+ </b-form-input>
+ </b-form-group>
+ </template>
+ <!-- AWS-specific fields -->
+ <template v-if="isResourceType('AWS')">
+ <b-form-group
+ label="Region"
+ label-for="aws-region"
+ >
+ <b-form-input
+ id="aws-region"
+ type="text"
+ v-model="data.specificPreferences.region"
+ :disabled="!userHasWriteAccess"
+ >
+ </b-form-input>
+ </b-form-group>
+ <b-form-group
+ label="Preferred AMI ID"
+ label-for="preferred-ami-id"
+ >
+ <b-form-input
+ id="preferred-ami-id"
+ type="text"
+ v-model="data.specificPreferences.preferredAmiId"
+ :disabled="!userHasWriteAccess"
+ >
+ </b-form-input>
+ </b-form-group>
+ <b-form-group
+ label="Preferred Instance Type"
+ label-for="preferred-instance-type"
+ >
+ <b-form-input
+ id="preferred-instance-type"
+ type="text"
+ v-model="data.specificPreferences.preferredInstanceType"
+ :disabled="!userHasWriteAccess"
+ >
+ </b-form-input>
+ </b-form-group>
+ </template>
<b-form-group
label="Scratch Location"
label-for="scratch-location"
@@ -133,6 +194,7 @@
<div class="card">
<div class="card-body">
<compute-resource-reservation-list
+ v-if="isResourceType('SLURM')"
:reservations="data.reservations"
:queues="queueNames"
:readonly="!userHasWriteAccess"
@@ -147,40 +209,35 @@
</div>
</div>
<div class="fixed-footer">
- <b-button
- variant="primary"
- @click="save"
- :disabled="!valid || !userHasWriteAccess"
- >Save</b-button
+ <b-button
+ variant="primary"
+ @click="save"
+ :disabled="!valid || !userHasWriteAccess"
+ >Save
+ </b-button
>
- <delete-button
- class="ml-2"
- :disabled="!userHasWriteAccess"
- @delete="remove">
+ <delete-button
+ class="ml-2"
+ :disabled="!userHasWriteAccess"
+ @delete="remove">
Are you sure you want to remove the preferences for compute resource
<strong>{{ computeResource.hostName }}</strong
>?
</delete-button>
<b-button class="ml-2" variant="secondary" @click="cancel"
- >Cancel</b-button
+ >Cancel
+ </b-button
>
</div>
</div>
</template>
<script>
-import DjangoAiravataAPI from "django-airavata-api";
+import DjangoAiravataAPI, {errors, models, services} from
"django-airavata-api";
import SSHCredentialSelector from
"../../credentials/SSHCredentialSelector.vue";
import ComputeResourceReservationList from "./ComputeResourceReservationList";
import ComputeResourcePolicyEditor from "./ComputeResourcePolicyEditor";
-
-import { models, services, errors } from "django-airavata-api";
-import {
- mixins,
- notifications,
- errors as uiErrors,
- components,
-} from "django-airavata-common-ui";
+import {components, errors as uiErrors, mixins, notifications,} from
"django-airavata-common-ui";
export default {
name: "compute-preference",
@@ -210,11 +267,11 @@ export default {
},
mounted: function () {
const computeResourcePromise = this.fetchComputeResource(this.host_id);
- if (this.localGroupResourceProfile){
- this.userHasWriteAccess =
this.localGroupResourceProfile.userHasWriteAccess;
+ if (this.localGroupResourceProfile) {
+ this.userHasWriteAccess =
this.localGroupResourceProfile.userHasWriteAccess;
}
if (!this.value && this.id && this.host_id) {
- services.GroupResourceProfileService.retrieve({ lookup: this.id }).then(
+ services.GroupResourceProfileService.retrieve({lookup: this.id}).then(
(groupResourceProfile) => {
this.localGroupResourceProfile = groupResourceProfile;
this.userHasWriteAccess =
this.localGroupResourceProfile.userHasWriteAccess;
@@ -240,19 +297,19 @@ export default {
} else if (!this.computeResourcePolicy) {
this.createDefaultComputeResourcePolicy(computeResourcePromise);
}
- if (!this.id){
- this.userHasWriteAccess=true;
+ if (!this.id) {
+ this.userHasWriteAccess = true;
}
this.$on("input", this.validate);
-
+
},
data: function () {
return {
data: this.value
? this.value.clone()
: new models.GroupComputeResourcePreference({
- computeResourceId: this.host_id,
- }),
+ computeResourceId: this.host_id,
+ }),
localGroupResourceProfile: this.groupResourceProfile
? this.groupResourceProfile.clone()
: null,
@@ -270,6 +327,10 @@ export default {
reservationsInvalid: false,
computeResourcePolicyInvalid: false,
userHasWriteAccess: false,
+ resourceTypeOptions: models.ResourceType.values.map(rt => ({
+ value: rt,
+ text: rt.name,
+ })),
};
},
computed: {
@@ -331,7 +392,7 @@ export default {
this.validationErrors =
error.details.response.computePreferences[
computePreferencesIndex
- ];
+ ];
} else {
this.validationErrors = null;
notifications.NotificationList.addError(error);
@@ -341,13 +402,13 @@ export default {
saveOrUpdate(groupResourceProfile) {
if (this.id) {
return DjangoAiravataAPI.services.GroupResourceProfileService.update(
- { data: groupResourceProfile, lookup: this.id },
- { ignoreErrors: true }
+ {data: groupResourceProfile, lookup: this.id},
+ {ignoreErrors: true}
);
} else {
return DjangoAiravataAPI.services.GroupResourceProfileService.create(
- { data: groupResourceProfile },
- { ignoreErrors: true }
+ {data: groupResourceProfile},
+ {ignoreErrors: true}
);
}
},
@@ -379,12 +440,12 @@ export default {
if (this.id) {
this.$router.push({
name: "group_resource_preference",
- params: { id: this.id },
+ params: {id: this.id},
});
} else {
this.$router.push({
name: "new_group_resource_preference",
- params: { value: this.localGroupResourceProfile },
+ params: {value: this.localGroupResourceProfile},
});
}
},
@@ -424,13 +485,47 @@ export default {
);
this.data.reservations.splice(reservationIndex, 1, reservation);
},
+ onResourceTypeChange() {
+ if (!this.data.resourceType) {
+ this.data.specificPreferences = null;
+ this.validate();
+ return;
+ }
+ if (this.data.resetSpecificPreferences) {
+ this.data.resetSpecificPreferences();
+ } else {
+ const resourceTypeName = this.data.resourceType.name;
+ const modelClassName =
this._getPreferenceModelClassName(resourceTypeName);
+ if (modelClassName && models[modelClassName]) {
+ const PreferenceModel = models[modelClassName];
+ this.data.specificPreferences = new PreferenceModel();
+ } else {
+ this.data.specificPreferences = null;
+ }
+ }
+ this.validate();
+ },
+ _getPreferenceModelClassName(resourceTypeName) {
+ // Convert resource type name to model class name
+ // 'SLURM' -> 'SlurmComputeResourcePreference'
+ // 'AWS' -> 'AwsComputeResourcePreference'
+ if (!resourceTypeName) return null;
+ const capitalized = resourceTypeName.charAt(0) +
resourceTypeName.slice(1).toLowerCase();
+ return capitalized + 'ComputeResourcePreference';
+ },
+ isResourceType(resourceTypeName) {
+ if (this.data.isResourceType) {
+ return this.data.isResourceType(resourceTypeName);
+ }
+ return this.data.resourceType && this.data.resourceType.name ===
resourceTypeName;
+ },
},
beforeRouteEnter: function (to, from, next) {
// If we don't have the Group Resource Profile id or instance, then the
// Group Resource Profile wasn't created and we need to just go back to
// the dashboard
if (!to.params.id && !to.params.groupResourceProfile) {
- next({ name: "group_resource_preference_dashboard" });
+ next({name: "group_resource_preference_dashboard"});
} else {
next();
}
diff --git a/airavata-django-portal/django_airavata/apps/api/serializers.py
b/airavata-django-portal/django_airavata/apps/api/serializers.py
index 70c6af6f7..128d5b20b 100644
--- a/airavata-django-portal/django_airavata/apps/api/serializers.py
+++ b/airavata-django-portal/django_airavata/apps/api/serializers.py
@@ -26,7 +26,10 @@ from airavata.model.appcatalog.gatewayprofile.ttypes import (
from airavata.model.appcatalog.groupresourceprofile.ttypes import (
ComputeResourceReservation,
GroupComputeResourcePreference,
- GroupResourceProfile
+ GroupResourceProfile,
+ ResourceType,
+ SlurmComputeResourcePreference,
+ AwsComputeResourcePreference
)
from airavata.model.appcatalog.parser.ttypes import Parser
from airavata.model.appcatalog.storageresource.ttypes import (
@@ -751,9 +754,513 @@ class GroupComputeResourcePreferenceSerializer(
return []
+ @staticmethod
+ def _convert_nested_list_fields_to_thrift(slurm_pref):
+ from collections import OrderedDict
+ from airavata.model.appcatalog.groupresourceprofile.ttypes import (
+ ComputeResourceReservation,
+ GroupAccountSSHProvisionerConfig
+ )
+
+ if hasattr(slurm_pref, 'reservations') and slurm_pref.reservations:
+ if isinstance(slurm_pref.reservations, list):
+ converted_reservations = []
+ for res in slurm_pref.reservations:
+ if isinstance(res, (dict, OrderedDict)):
+
converted_reservations.append(ComputeResourceReservation(**res))
+ else:
+ converted_reservations.append(res)
+ slurm_pref.reservations = converted_reservations
+
+ if hasattr(slurm_pref, 'groupSSHAccountProvisionerConfigs') and
slurm_pref.groupSSHAccountProvisionerConfigs:
+ if isinstance(slurm_pref.groupSSHAccountProvisionerConfigs, list):
+ converted_configs = []
+ for cfg in slurm_pref.groupSSHAccountProvisionerConfigs:
+ if isinstance(cfg, (dict, OrderedDict)):
+
converted_configs.append(GroupAccountSSHProvisionerConfig(**cfg))
+ else:
+ converted_configs.append(cfg)
+ slurm_pref.groupSSHAccountProvisionerConfigs =
converted_configs
+
+ @staticmethod
+ def _convert_specific_preferences_dict_to_thrift(pref_instance,
resource_type):
+ from collections import OrderedDict
+
+ if not hasattr(pref_instance, 'specificPreferences'):
+ return
+
+ if isinstance(pref_instance.specificPreferences, (dict, OrderedDict)):
+ specific_prefs_dict = pref_instance.specificPreferences
+
+ union_type_class = None
+ try:
+ from airavata.model.appcatalog.groupresourceprofile.ttypes
import (
+ EnvironmentSpecificPreferences
+ )
+ union_type_class = EnvironmentSpecificPreferences
+ log.debug(
+ "GCPreference: Got union type class from import: %s",
+ union_type_class.__name__,
+ )
+ except ImportError as e:
+ log.error(
+ "GCPreference: Failed to import
EnvironmentSpecificPreferences: %s",
+ str(e),
+ exc_info=True,
+ )
+
+ if union_type_class:
+ pref_instance.specificPreferences = union_type_class()
+
+ if resource_type == ResourceType.SLURM:
+ if 'slurm' in specific_prefs_dict:
+ slurm_data = specific_prefs_dict['slurm']
+ else:
+ slurm_data = specific_prefs_dict
+
+ if slurm_data and isinstance(slurm_data, dict) and
len(slurm_data) > 0:
+ try:
+ from collections import OrderedDict
+ from
airavata.model.appcatalog.groupresourceprofile.ttypes import (
+ ComputeResourceReservation,
+ GroupAccountSSHProvisionerConfig
+ )
+
+ if 'reservations' in slurm_data and
slurm_data['reservations']:
+ reservations_list = slurm_data['reservations']
+ if isinstance(reservations_list, list):
+ converted_reservations = []
+ for res in reservations_list:
+ if isinstance(res, (dict,
OrderedDict)):
+
converted_reservations.append(ComputeResourceReservation(**res))
+ else:
+ converted_reservations.append(res)
+ slurm_data['reservations'] =
converted_reservations
+
+ if 'groupSSHAccountProvisionerConfigs' in
slurm_data and slurm_data['groupSSHAccountProvisionerConfigs']:
+ configs_list =
slurm_data['groupSSHAccountProvisionerConfigs']
+ if isinstance(configs_list, list):
+ converted_configs = []
+ for cfg in configs_list:
+ if isinstance(cfg, (dict,
OrderedDict)):
+
converted_configs.append(GroupAccountSSHProvisionerConfig(**cfg))
+ else:
+ converted_configs.append(cfg)
+
slurm_data['groupSSHAccountProvisionerConfigs'] = converted_configs
+
+ slurm_pref =
SlurmComputeResourcePreference(**slurm_data)
+ pref_instance.specificPreferences.slurm =
slurm_pref
+
GroupComputeResourcePreferenceSerializer._convert_nested_list_fields_to_thrift(slurm_pref)
+ log.info(
+ "GCPreference: Converted specificPreferences
dict to SLURM Thrift union type, computeResourceId=%s",
+ pref_instance.computeResourceId if
hasattr(pref_instance, 'computeResourceId') else 'unknown',
+ )
+ except Exception as e:
+ log.error(
+ "GCPreference: Failed to create
SlurmComputeResourcePreference from dict: %s, computeResourceId=%s",
+ str(e),
+ pref_instance.computeResourceId if
hasattr(pref_instance, 'computeResourceId') else 'unknown',
+ exc_info=True,
+ )
+ else:
+ log.info(
+ "GCPreference: specificPreferences dict is empty,
created empty union type, computeResourceId=%s",
+ pref_instance.computeResourceId if
hasattr(pref_instance, 'computeResourceId') else 'unknown',
+ )
+ elif resource_type == ResourceType.AWS:
+ if 'aws' in specific_prefs_dict:
+ aws_data = specific_prefs_dict['aws']
+ else:
+ aws_data = specific_prefs_dict
+
+ if aws_data and isinstance(aws_data, dict) and
len(aws_data) > 0:
+ try:
+ aws_pref = AwsComputeResourcePreference(**aws_data)
+ pref_instance.specificPreferences.aws = aws_pref
+ log.info(
+ "GCPreference: Converted specificPreferences
dict to AWS Thrift union type, computeResourceId=%s",
+ pref_instance.computeResourceId if
hasattr(pref_instance, 'computeResourceId') else 'unknown',
+ )
+ except Exception as e:
+ log.error(
+ "GCPreference: Failed to create
AwsComputeResourcePreference from dict: %s, computeResourceId=%s",
+ str(e),
+ pref_instance.computeResourceId if
hasattr(pref_instance, 'computeResourceId') else 'unknown',
+ exc_info=True,
+ )
+ else:
+ log.info(
+ "GCPreference: specificPreferences dict is empty,
created empty union type, computeResourceId=%s",
+ pref_instance.computeResourceId if
hasattr(pref_instance, 'computeResourceId') else 'unknown',
+ )
+ else:
+ log.error(
+ "GCPreference: Could not get union type class to convert
specificPreferences dict, computeResourceId=%s",
+ pref_instance.computeResourceId if hasattr(pref_instance,
'computeResourceId') else 'unknown',
+ )
+ union_type_created = False
+ try:
+ test_instance =
GroupComputeResourcePreference(resourceType=resource_type)
+ if test_instance.specificPreferences is not None:
+ pref_instance.specificPreferences =
type(test_instance.specificPreferences)()
+ union_type_created = True
+ log.info(
+ "GCPreference: Created empty union type from test
instance, computeResourceId=%s",
+ pref_instance.computeResourceId if
hasattr(pref_instance, 'computeResourceId') else 'unknown',
+ )
+ except Exception as e:
+ log.warning(
+ "GCPreference: Failed to create union type from test
instance: %s, trying direct construction",
+ str(e),
+ )
+
+ if not union_type_created:
+ if hasattr(pref_instance, 'resourceType') and
pref_instance.resourceType:
+ try:
+ temp =
GroupComputeResourcePreference(resourceType=pref_instance.resourceType)
+ if temp.specificPreferences is not None:
+ pref_instance.specificPreferences =
type(temp.specificPreferences)()
+ union_type_created = True
+ log.info(
+ "GCPreference: Created empty union type
using pref_instance.resourceType, computeResourceId=%s",
+ pref_instance.computeResourceId if
hasattr(pref_instance, 'computeResourceId') else 'unknown',
+ )
+ except Exception as e2:
+ log.error(
+ "GCPreference: All attempts to create union
type failed: %s, computeResourceId=%s",
+ str(e2),
+ pref_instance.computeResourceId if
hasattr(pref_instance, 'computeResourceId') else 'unknown',
+ exc_info=True,
+ )
+
+ if not union_type_created:
+ log.error(
+ "GCPreference: Could not create union type at all!
specificPreferences will remain as dict, computeResourceId=%s",
+ pref_instance.computeResourceId if
hasattr(pref_instance, 'computeResourceId') else 'unknown',
+ )
+ else:
+ if hasattr(pref_instance.specificPreferences, 'slurm') and
pref_instance.specificPreferences.slurm:
+
GroupComputeResourcePreferenceSerializer._convert_nested_list_fields_to_thrift(
+ pref_instance.specificPreferences.slurm
+ )
+
+ def to_representation(self, instance):
+ """
+ Override to extract fields from specificPreferences union type.
+ """
+ ret = super().to_representation(instance)
+
+ if hasattr(instance, 'specificPreferences') and
instance.specificPreferences:
+ if hasattr(instance.specificPreferences, 'slurm') and
instance.specificPreferences.slurm:
+ slurm_pref = instance.specificPreferences.slurm
+ if hasattr(slurm_pref, 'allocationProjectNumber'):
+ ret['allocationProjectNumber'] =
slurm_pref.allocationProjectNumber
+ if 'specificPreferences' in ret and
isinstance(ret['specificPreferences'], dict):
+ if 'slurm' not in ret['specificPreferences']:
+ ret['specificPreferences'] = {'slurm':
ret['specificPreferences']}
+ elif hasattr(instance.specificPreferences, 'aws') and
instance.specificPreferences.aws:
+ aws_pref = instance.specificPreferences.aws
+ aws_fields = {
+ 'region': getattr(aws_pref, 'region', None),
+ 'preferredAmiId': getattr(aws_pref, 'preferredAmiId',
None),
+ 'preferredInstanceType': getattr(aws_pref,
'preferredInstanceType', None),
+ }
+ ret['specificPreferences'] = aws_fields
+
+ return ret
+
+ def create(self, validated_data):
+ """
+ Override create() to properly handle resourceType and
specificPreferences union type.
+ """
+ if isinstance(validated_data, GroupComputeResourcePreference):
+ resource_type = None
+ if not hasattr(validated_data, 'resourceType') or
validated_data.resourceType is None:
+ from collections import OrderedDict
+ if hasattr(validated_data, 'specificPreferences') and
validated_data.specificPreferences:
+ if isinstance(validated_data.specificPreferences, (dict,
OrderedDict)):
+ specific_prefs_dict =
validated_data.specificPreferences
+ if 'slurm' in specific_prefs_dict or
'allocationProjectNumber' in specific_prefs_dict:
+ resource_type = ResourceType.SLURM
+ elif 'aws' in specific_prefs_dict or 'region' in
specific_prefs_dict:
+ resource_type = ResourceType.AWS
+ else:
+ resource_type = ResourceType.SLURM
+ elif hasattr(validated_data.specificPreferences, 'slurm')
and validated_data.specificPreferences.slurm:
+ resource_type = ResourceType.SLURM
+ elif hasattr(validated_data.specificPreferences, 'aws')
and validated_data.specificPreferences.aws:
+ resource_type = ResourceType.AWS
+ else:
+ resource_type = ResourceType.SLURM
+ else:
+ resource_type = ResourceType.SLURM
+
+ if resource_type:
+ validated_data.resourceType = resource_type
+ else:
+ resource_type = validated_data.resourceType
+
+ if resource_type:
+
self._convert_specific_preferences_dict_to_thrift(validated_data, resource_type)
+
+ return validated_data
+
+ data = copy.deepcopy(validated_data)
+
+ resource_type = data.get('resourceType')
+ if resource_type is None:
+ if 'allocationProjectNumber' in data or ('specificPreferences' in
data and isinstance(data.get('specificPreferences'), dict) and 'slurm' in
data.get('specificPreferences', {})):
+ resource_type = ResourceType.SLURM
+ elif 'specificPreferences' in data and
isinstance(data.get('specificPreferences'), dict) and 'aws' in
data.get('specificPreferences', {}):
+ resource_type = ResourceType.AWS
+ elif 'region' in data or 'preferredAmiId' in data or
'preferredInstanceType' in data:
+ resource_type = ResourceType.AWS
+ else:
+ resource_type = ResourceType.SLURM
+
+ if isinstance(resource_type, str):
+ try:
+ resource_type = ResourceType[resource_type]
+ except (KeyError, AttributeError):
+ resource_type = ResourceType.SLURM
+ elif isinstance(resource_type, int):
+ try:
+ resource_type = ResourceType(resource_type)
+ except (ValueError, AttributeError):
+ resource_type = ResourceType.SLURM
+
+ data['resourceType'] = resource_type
+
+ specific_prefs = data.pop('specificPreferences', None)
+
+ slurm_data = {}
+ aws_data = {}
+
+ slurm_fields = ['allocationProjectNumber', 'preferredBatchQueue',
'qualityOfService',
+ 'usageReportingGatewayId', 'sshAccountProvisioner',
+ 'groupSSHAccountProvisionerConfigs',
'sshAccountProvisionerAdditionalInfo',
+ 'reservations']
+ aws_fields = ['region', 'preferredAmiId', 'preferredInstanceType']
+
+ for field in slurm_fields:
+ if field in data:
+ slurm_data[field] = data.pop(field)
+ for field in aws_fields:
+ if field in data:
+ aws_data[field] = data.pop(field)
+
+ thrift_spec = GroupComputeResourcePreference.thrift_spec
+ for field_spec in thrift_spec:
+ if field_spec:
+ field_name = field_spec[2]
+ default_value = field_spec[4]
+ if default_value is not None:
+ if field_name in data and data[field_name] is None:
+ del data[field_name]
+
+ instance = GroupComputeResourcePreference(**data)
+
+ instance.resourceType = resource_type
+
+ union_type_class = None
+ try:
+ from airavata.model.appcatalog.groupresourceprofile.ttypes import (
+ EnvironmentSpecificPreferences
+ )
+ union_type_class = EnvironmentSpecificPreferences
+ except ImportError as e:
+ log.error(
+ "GCPreference create: Failed to import
EnvironmentSpecificPreferences: %s",
+ str(e),
+ exc_info=True,
+ )
+
+ if specific_prefs is None:
+ if resource_type == ResourceType.SLURM and slurm_data:
+ from collections import OrderedDict
+ from airavata.model.appcatalog.groupresourceprofile.ttypes
import (
+ GroupAccountSSHProvisionerConfig
+ )
+
+ if 'reservations' in slurm_data and slurm_data['reservations']:
+ reservations_list = slurm_data['reservations']
+ if isinstance(reservations_list, list):
+ converted_reservations = []
+ for res in reservations_list:
+ if isinstance(res, (dict, OrderedDict)):
+
converted_reservations.append(ComputeResourceReservation(**res))
+ else:
+ converted_reservations.append(res)
+ slurm_data['reservations'] = converted_reservations
+
+ if 'groupSSHAccountProvisionerConfigs' in slurm_data and
slurm_data['groupSSHAccountProvisionerConfigs']:
+ configs_list =
slurm_data['groupSSHAccountProvisionerConfigs']
+ if isinstance(configs_list, list):
+ converted_configs = []
+ for cfg in configs_list:
+ if isinstance(cfg, (dict, OrderedDict)):
+
converted_configs.append(GroupAccountSSHProvisionerConfig(**cfg))
+ else:
+ converted_configs.append(cfg)
+ slurm_data['groupSSHAccountProvisionerConfigs'] =
converted_configs
+
+ slurm_pref = SlurmComputeResourcePreference(**slurm_data)
+ if union_type_class:
+ instance.specificPreferences = union_type_class()
+ instance.specificPreferences.slurm = slurm_pref
+ else:
+ try:
+ test_instance =
GroupComputeResourcePreference(resourceType=resource_type)
+ if test_instance.specificPreferences is not None:
+ instance.specificPreferences =
type(test_instance.specificPreferences)()
+ instance.specificPreferences.slurm = slurm_pref
+ else:
+ log.warning(
+ "GCPreference create: Could not create union
type, instance may be invalid"
+ )
+ except Exception as e:
+ log.error(
+ "GCPreference create: Failed to set
specificPreferences.slurm: %s",
+ str(e),
+ exc_info=True,
+ )
+ elif resource_type == ResourceType.AWS and aws_data:
+ aws_pref = AwsComputeResourcePreference(**aws_data)
+ if union_type_class:
+ instance.specificPreferences = union_type_class()
+ instance.specificPreferences.aws = aws_pref
+ else:
+ try:
+ test_instance =
GroupComputeResourcePreference(resourceType=resource_type)
+ if test_instance.specificPreferences is not None:
+ instance.specificPreferences =
type(test_instance.specificPreferences)()
+ instance.specificPreferences.aws = aws_pref
+ except Exception as e:
+ log.error(
+ "GCPreference create: Failed to set
specificPreferences.aws: %s",
+ str(e),
+ exc_info=True,
+ )
+ elif isinstance(specific_prefs, dict):
+ if 'slurm' in specific_prefs:
+ slurm_dict = specific_prefs['slurm'].copy() if
isinstance(specific_prefs['slurm'], dict) else {}
+ slurm_dict.update(slurm_data)
+ from collections import OrderedDict
+ from airavata.model.appcatalog.groupresourceprofile.ttypes
import (
+ GroupAccountSSHProvisionerConfig
+ )
+
+ if 'reservations' in slurm_dict and slurm_dict['reservations']:
+ reservations_list = slurm_dict['reservations']
+ if isinstance(reservations_list, list):
+ converted_reservations = []
+ for res in reservations_list:
+ if isinstance(res, (dict, OrderedDict)):
+
converted_reservations.append(ComputeResourceReservation(**res))
+ else:
+ converted_reservations.append(res)
+ slurm_dict['reservations'] = converted_reservations
+
+ if 'groupSSHAccountProvisionerConfigs' in slurm_dict and
slurm_dict['groupSSHAccountProvisionerConfigs']:
+ configs_list =
slurm_dict['groupSSHAccountProvisionerConfigs']
+ if isinstance(configs_list, list):
+ converted_configs = []
+ for cfg in configs_list:
+ if isinstance(cfg, (dict, OrderedDict)):
+
converted_configs.append(GroupAccountSSHProvisionerConfig(**cfg))
+ else:
+ converted_configs.append(cfg)
+ slurm_dict['groupSSHAccountProvisionerConfigs'] =
converted_configs
+
+ slurm_pref = SlurmComputeResourcePreference(**slurm_dict)
+ if union_type_class:
+ instance.specificPreferences = union_type_class()
+ instance.specificPreferences.slurm = slurm_pref
+ elif 'aws' in specific_prefs:
+ aws_pref =
AwsComputeResourcePreference(**specific_prefs['aws'])
+ if union_type_class:
+ instance.specificPreferences = union_type_class()
+ instance.specificPreferences.aws = aws_pref
+ elif slurm_data:
+ from collections import OrderedDict
+ from airavata.model.appcatalog.groupresourceprofile.ttypes
import (
+ GroupAccountSSHProvisionerConfig
+ )
+
+ if 'reservations' in slurm_data and slurm_data['reservations']:
+ reservations_list = slurm_data['reservations']
+ if isinstance(reservations_list, list):
+ converted_reservations = []
+ for res in reservations_list:
+ if isinstance(res, (dict, OrderedDict)):
+
converted_reservations.append(ComputeResourceReservation(**res))
+ else:
+ converted_reservations.append(res)
+ slurm_data['reservations'] = converted_reservations
+
+ if 'groupSSHAccountProvisionerConfigs' in slurm_data and
slurm_data['groupSSHAccountProvisionerConfigs']:
+ configs_list =
slurm_data['groupSSHAccountProvisionerConfigs']
+ if isinstance(configs_list, list):
+ converted_configs = []
+ for cfg in configs_list:
+ if isinstance(cfg, (dict, OrderedDict)):
+
converted_configs.append(GroupAccountSSHProvisionerConfig(**cfg))
+ else:
+ converted_configs.append(cfg)
+ slurm_data['groupSSHAccountProvisionerConfigs'] =
converted_configs
+
+ slurm_pref = SlurmComputeResourcePreference(**slurm_data)
+ if union_type_class:
+ instance.specificPreferences = union_type_class()
+ instance.specificPreferences.slurm = slurm_pref
+
+ if not hasattr(instance, 'resourceType') or instance.resourceType is
None:
+ if hasattr(instance, 'specificPreferences') and
instance.specificPreferences:
+ from collections import OrderedDict
+ if isinstance(instance.specificPreferences, (dict,
OrderedDict)):
+ if 'slurm' in instance.specificPreferences or
'allocationProjectNumber' in instance.specificPreferences:
+ instance.resourceType = ResourceType.SLURM
+ elif 'aws' in instance.specificPreferences or 'region' in
instance.specificPreferences:
+ instance.resourceType = ResourceType.AWS
+ else:
+ instance.resourceType = ResourceType.SLURM
+ elif hasattr(instance.specificPreferences, 'slurm') and
instance.specificPreferences.slurm:
+ instance.resourceType = ResourceType.SLURM
+ elif hasattr(instance.specificPreferences, 'aws') and
instance.specificPreferences.aws:
+ instance.resourceType = ResourceType.AWS
+ else:
+ instance.resourceType = ResourceType.SLURM
+ else:
+ instance.resourceType = ResourceType.SLURM
+ log.warning(
+ "GCPreference create: Had to set resourceType=%s at end of
create(), computeResourceId=%s",
+ instance.resourceType.name if hasattr(instance.resourceType,
'name') else instance.resourceType,
+ instance.computeResourceId if hasattr(instance,
'computeResourceId') else 'unknown',
+ )
+
+ if hasattr(instance, 'resourceType') and instance.resourceType:
+ if instance.specificPreferences is None:
+ try:
+ from airavata.model.appcatalog.groupresourceprofile.ttypes
import (
+ EnvironmentSpecificPreferences
+ )
+ instance.specificPreferences =
EnvironmentSpecificPreferences()
+ log.debug(
+ "GCPreference create: Initialized empty
specificPreferences union type, computeResourceId=%s",
+ instance.computeResourceId if hasattr(instance,
'computeResourceId') else 'unknown',
+ )
+ except ImportError as e:
+ log.warning(
+ "GCPreference create: Could not initialize empty
specificPreferences: %s",
+ str(e),
+ )
+ self._convert_specific_preferences_dict_to_thrift(instance,
instance.resourceType)
+
+ return instance
+
class GroupResourceProfileSerializer(
- thrift_utils.create_serializer_class(GroupResourceProfile)):
+ thrift_utils.create_serializer_class(GroupResourceProfile)):
url = FullyEncodedHyperlinkedIdentityField(
view_name='django_airavata_api:group-resource-profile-detail',
lookup_field='groupResourceProfileId',
@@ -766,38 +1273,348 @@ class GroupResourceProfileSerializer(
class Meta:
required = ('groupResourceProfileName',)
+ def create(self, validated_data):
+ """
+ Override create() to preserve Thrift instances in computePreferences.
+ """
+ compute_prefs = validated_data.get('computePreferences')
+ if compute_prefs:
+ all_thrift_instances = all(
+ isinstance(item, GroupComputeResourcePreference)
+ for item in compute_prefs
+ )
+ if all_thrift_instances:
+ validated_data_copy = copy.deepcopy(validated_data)
+ validated_data_copy['computePreferences'] = []
+
+ params = self.process_nested_fields(validated_data_copy)
+ params['computePreferences'] = [copy.deepcopy(pref) for pref
in compute_prefs]
+
+ thrift_spec = GroupResourceProfile.thrift_spec
+ for field_spec in thrift_spec:
+ if field_spec:
+ field_name = field_spec[2]
+ default_value = field_spec[4]
+ if default_value is not None:
+ if field_name in params and params[field_name] is
None:
+ del params[field_name]
+
+ return GroupResourceProfile(**params)
+
+ params = self.process_nested_fields(validated_data)
+
+ if 'computePreferences' in params and params['computePreferences']:
+ compute_prefs = params['computePreferences']
+ processed_compute_prefs = []
+ for pref in compute_prefs:
+ if isinstance(pref, GroupComputeResourcePreference):
+ processed_compute_prefs.append(pref)
+ elif isinstance(pref, dict):
+ serializer = GroupComputeResourcePreferenceSerializer()
+ try:
+ log.debug(
+ "GCPreference create: Converting dict to Thrift
instance, computeResourceId=%s",
+ pref.get('computeResourceId', 'unknown'),
+ )
+ thrift_pref = serializer.create(pref)
+ if isinstance(thrift_pref,
GroupComputeResourcePreference):
+ log.debug(
+ "GCPreference create: Successfully created
Thrift instance, computeResourceId=%s resourceType=%s",
+ thrift_pref.computeResourceId if
hasattr(thrift_pref, 'computeResourceId') else 'unknown',
+ getattr(thrift_pref, 'resourceType', None),
+ )
+ processed_compute_prefs.append(thrift_pref)
+ else:
+ log.warning(
+ "GCPreference create: serializer.create()
returned non-Thrift instance: %s, type=%s",
+ pref.get('computeResourceId', 'unknown'),
+ type(thrift_pref).__name__,
+ )
+ raise ValueError(f"serializer.create() returned
{type(thrift_pref).__name__}, expected GroupComputeResourcePreference")
+ except Exception as e:
+ log.warning(
+ "GCPreference create: Failed to convert dict to
Thrift instance using serializer.create(): %s, trying direct construction",
+ str(e),
+ exc_info=True,
+ )
+ pref_copy = copy.deepcopy(pref)
+ if 'resourceType' not in pref_copy or
pref_copy.get('resourceType') is None:
+ if 'allocationProjectNumber' in pref_copy:
+ pref_copy['resourceType'] = ResourceType.SLURM
+ elif 'region' in pref_copy or 'preferredAmiId' in
pref_copy:
+ pref_copy['resourceType'] = ResourceType.AWS
+ else:
+ pref_copy['resourceType'] = ResourceType.SLURM
# Default
+ try:
+
processed_compute_prefs.append(GroupComputeResourcePreference(**pref_copy))
+ except Exception as e2:
+ log.error(
+ "GCPreference create: Failed to create Thrift
instance directly: %s",
+ str(e2),
+ exc_info=True,
+ )
+ processed_compute_prefs.append(pref)
+ else:
+ processed_compute_prefs.append(pref)
+ params['computePreferences'] = processed_compute_prefs
+
+ thrift_spec = GroupResourceProfile.thrift_spec
+ for field_spec in thrift_spec:
+ if field_spec:
+ field_name = field_spec[2]
+ default_value = field_spec[4]
+ if default_value is not None:
+ if field_name in params and params[field_name] is None:
+ del params[field_name]
+
+ return GroupResourceProfile(**params)
+
+ def process_nested_fields(self, validated_data):
+ compute_prefs = validated_data.get('computePreferences')
+ if compute_prefs is None or compute_prefs == []:
+ validated_data_copy = copy.deepcopy(validated_data)
+ validated_data_copy.pop('computePreferences', None)
+ params = self._process_nested_fields_base(validated_data_copy)
+ params['computePreferences'] = compute_prefs if compute_prefs is
not None else []
+ return params
+
+ if compute_prefs and all(isinstance(item,
GroupComputeResourcePreference) for item in compute_prefs):
+ validated_data_copy = copy.deepcopy(validated_data)
+ validated_data_copy.pop('computePreferences', None)
+ params = self._process_nested_fields_base(validated_data_copy)
+ params['computePreferences'] = compute_prefs
+ return params
+
+ return self._process_nested_fields_base(validated_data)
+
+ def _process_nested_fields_base(self, validated_data):
+ from rest_framework.serializers import ListField, ListSerializer,
Serializer
+ if not isinstance(validated_data, dict):
+ return validated_data
+
+ params = copy.deepcopy(validated_data)
+ fields = self.fields
+
+ for field_name, serializer in fields.items():
+ if (isinstance(serializer, ListField) or isinstance(serializer,
ListSerializer)):
+ if (params.get(field_name, None) is not None or not
serializer.allow_null):
+ if isinstance(serializer.child, Serializer):
+ items = params[field_name]
+ if items and all(not isinstance(item, dict) for item
in items):
+ continue
+
+ if field_name == 'experimentInputs' and 'type' in
serializer.child.fields:
+ for item in params[field_name]:
+ if isinstance(item, dict) and 'type' in item
and isinstance(item['type'], int):
+ item['type'] = DataType(item['type'])
+ elif field_name == 'experimentOutputs' and 'type' in
serializer.child.fields:
+ for item in params[field_name]:
+ if isinstance(item, dict) and 'type' in item
and isinstance(item['type'], int):
+ item['type'] = DataType(item['type'])
+ elif field_name == 'experimentStatus' and 'state' in
serializer.child.fields:
+ for item in params[field_name]:
+ if isinstance(item, dict) and 'state' in item
and isinstance(item['state'], int):
+ item['state'] =
ExperimentState(item['state'])
+
+ processed_items = []
+ for item in params[field_name]:
+ if isinstance(item, dict):
+ if hasattr(serializer.child, 'create'):
+ try:
+
processed_items.append(serializer.child.create(item))
+ except NotImplementedError:
+ processed_items.append(item)
+ else:
+ processed_items.append(item)
+ else:
+ processed_items.append(item)
+ params[field_name] = processed_items
+ else:
+ params[field_name] =
serializer.to_representation(params[field_name])
+ elif isinstance(serializer, Serializer):
+ if field_name in params and params[field_name] is not None:
+ if not isinstance(params[field_name], dict):
+ continue
+ if hasattr(serializer, 'create'):
+ try:
+ params[field_name] =
serializer.create(params[field_name])
+ except NotImplementedError:
+ pass
+
+ return params
+
def update(self, instance, validated_data):
- result = super().update(instance, validated_data)
+ # Merge existing computePreferences with incoming data to preserve
fields that aren't being updated
+ if 'computePreferences' in validated_data and
instance.computePreferences:
+ existing_prefs_by_id = {
+ pref.computeResourceId: pref
+ for pref in instance.computePreferences
+ if hasattr(pref, 'computeResourceId')
+ }
+
+ for incoming_pref in validated_data['computePreferences']:
+ if isinstance(incoming_pref, dict):
+ compute_resource_id =
incoming_pref.get('computeResourceId')
+ if compute_resource_id and compute_resource_id in
existing_prefs_by_id:
+ existing_pref =
existing_prefs_by_id[compute_resource_id]
+ if 'specificPreferences' in incoming_pref and
isinstance(incoming_pref['specificPreferences'], dict):
+ if 'slurm' in incoming_pref['specificPreferences']:
+ incoming_slurm =
incoming_pref['specificPreferences']['slurm']
+ if isinstance(incoming_slurm, dict):
+ existing_slurm = None
+ if (hasattr(existing_pref,
'specificPreferences') and
+ existing_pref.specificPreferences and
+
hasattr(existing_pref.specificPreferences, 'slurm') and
+
existing_pref.specificPreferences.slurm):
+ existing_slurm =
existing_pref.specificPreferences.slurm
+
+ simple_string_fields = [
+ 'preferredBatchQueue',
+ 'qualityOfService',
+ 'usageReportingGatewayId',
+ 'sshAccountProvisioner',
+ 'sshAccountProvisionerAdditionalInfo',
+ ]
+ for field in simple_string_fields:
+ if incoming_slurm.get(field) is None
and existing_slurm:
+ existing_value =
getattr(existing_slurm, field, None)
+ if existing_value is not None:
+ incoming_slurm[field] =
existing_value
+ log.debug(
+ "GCPreference update:
Preserved existing %s=%s for computeResourceId=%s",
+ field,
+ existing_value,
+ compute_resource_id,
+ )
+
+ list_fields = [
+ 'groupSSHAccountProvisionerConfigs',
+ 'reservations',
+ ]
+ for field in list_fields:
+ if incoming_slurm.get(field) is None
and existing_slurm:
+ existing_value =
getattr(existing_slurm, field, None)
+ if existing_value is not None and
isinstance(existing_value, list) and len(existing_value) > 0:
+ converted_list = []
+ for item in existing_value:
+ if hasattr(item,
'__dict__'):
+ if field ==
'reservations':
+ try:
+ serializer =
ComputeResourceReservationSerializer()
+
converted_list.append(serializer.to_representation(item))
+ except Exception
as e:
+ log.warning(
+
"GCPreference update: Could not serialize reservation item: %s",
+ str(e),
+ )
+ item_dict =
{k: v for k, v in item.__dict__.items() if not k.startswith('_')}
+
converted_list.append(item_dict)
+ else:
+ item_dict = {k: v
for k, v in item.__dict__.items() if not k.startswith('_')}
+
converted_list.append(item_dict)
+ else:
+
converted_list.append(item)
+ incoming_slurm[field] =
converted_list
+ log.debug(
+ "GCPreference update:
Preserved existing %s (list with %d items) for computeResourceId=%s",
+ field,
+ len(converted_list),
+ compute_resource_id,
+ )
+
+ if 'computeResourcePolicies' in validated_data and
instance.computeResourcePolicies:
+ existing_policies_by_resource_id = {
+ pol.computeResourceId: pol
+ for pol in instance.computeResourcePolicies
+ if hasattr(pol, 'computeResourceId') and hasattr(pol,
'resourcePolicyId')
+ }
+
+ for incoming_policy in validated_data['computeResourcePolicies']:
+ if isinstance(incoming_policy, dict):
+ compute_resource_id =
incoming_policy.get('computeResourceId')
+ if compute_resource_id and compute_resource_id in
existing_policies_by_resource_id:
+ existing_policy =
existing_policies_by_resource_id[compute_resource_id]
+ if 'resourcePolicyId' not in incoming_policy or
incoming_policy.get('resourcePolicyId') is None:
+ incoming_policy['resourcePolicyId'] =
existing_policy.resourcePolicyId
+ log.debug(
+ "GCPreference update: Preserved existing
resourcePolicyId=%s for computeResourceId=%s",
+ existing_policy.resourcePolicyId,
+ compute_resource_id,
+ )
+
+ if 'batchQueueResourcePolicies' in validated_data and
instance.batchQueueResourcePolicies:
+ existing_bq_policies_by_key = {}
+ for pol in instance.batchQueueResourcePolicies:
+ if hasattr(pol, 'computeResourceId') and hasattr(pol,
'queuename') and hasattr(pol, 'resourcePolicyId'):
+ key = (pol.computeResourceId, pol.queuename)
+ existing_bq_policies_by_key[key] = pol
+
+ for incoming_bq_policy in
validated_data['batchQueueResourcePolicies']:
+ if isinstance(incoming_bq_policy, dict):
+ compute_resource_id =
incoming_bq_policy.get('computeResourceId')
+ queuename = incoming_bq_policy.get('queuename')
+ if compute_resource_id and queuename:
+ key = (compute_resource_id, queuename)
+ if key in existing_bq_policies_by_key:
+ existing_bq_policy =
existing_bq_policies_by_key[key]
+ if 'resourcePolicyId' not in incoming_bq_policy or
incoming_bq_policy.get('resourcePolicyId') is None:
+ incoming_bq_policy['resourcePolicyId'] =
existing_bq_policy.resourcePolicyId
+ log.debug(
+ "GCPreference update: Preserved existing
resourcePolicyId=%s for batchQueueResourcePolicy computeResourceId=%s
queuename=%s",
+ existing_bq_policy.resourcePolicyId,
+ compute_resource_id,
+ queuename,
+ )
+
+ result = self.create(validated_data)
result._removed_compute_resource_preferences = []
result._removed_compute_resource_policies = []
result._removed_batch_queue_resource_policies = []
# Find all compute resource preferences that were removed
for compute_resource_preference in instance.computePreferences:
- existing_compute_resource_preference = next(
- (pref for pref in result.computePreferences
- if pref.computeResourceId ==
- compute_resource_preference.computeResourceId),
- None)
+ existing_compute_resource_preference = None
+ for pref in result.computePreferences:
+ if isinstance(pref, GroupComputeResourcePreference):
+ pref_id = pref.computeResourceId
+ elif isinstance(pref, dict):
+ pref_id = pref.get('computeResourceId')
+ else:
+ continue
+ if pref_id == compute_resource_preference.computeResourceId:
+ existing_compute_resource_preference = pref
+ break
if not existing_compute_resource_preference:
result._removed_compute_resource_preferences.append(
compute_resource_preference)
# Find all compute resource policies that were removed
for compute_resource_policy in instance.computeResourcePolicies:
- existing_compute_resource_policy = next(
- (pol for pol in result.computeResourcePolicies
- if pol.resourcePolicyId ==
- compute_resource_policy.resourcePolicyId),
- None)
+ existing_compute_resource_policy = None
+ for pol in result.computeResourcePolicies:
+ if hasattr(pol, 'resourcePolicyId'):
+ pol_id = pol.resourcePolicyId
+ elif isinstance(pol, dict):
+ pol_id = pol.get('resourcePolicyId')
+ else:
+ continue
+ if pol_id == compute_resource_policy.resourcePolicyId:
+ existing_compute_resource_policy = pol
+ break
if not existing_compute_resource_policy:
result._removed_compute_resource_policies.append(
compute_resource_policy)
# Find all batch queue resource policies that were removed
for batch_queue_resource_policy in instance.batchQueueResourcePolicies:
- existing_batch_queue_resource_policy_for_update = next(
- (bq for bq in result.batchQueueResourcePolicies
- if bq.resourcePolicyId ==
- batch_queue_resource_policy.resourcePolicyId),
- None)
+ existing_batch_queue_resource_policy_for_update = None
+ for bq in result.batchQueueResourcePolicies:
+ if hasattr(bq, 'resourcePolicyId'):
+ bq_id = bq.resourcePolicyId
+ elif isinstance(bq, dict):
+ bq_id = bq.get('resourcePolicyId')
+ else:
+ continue
+ if bq_id == batch_queue_resource_policy.resourcePolicyId:
+ existing_batch_queue_resource_policy_for_update = bq
+ break
if not existing_batch_queue_resource_policy_for_update:
result._removed_batch_queue_resource_policies.append(
batch_queue_resource_policy)
diff --git
a/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/index.js
b/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/index.js
index 9cb8a6550..3d62ba6fc 100644
---
a/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/index.js
+++
b/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/index.js
@@ -23,9 +23,13 @@ import ExtendedUserProfileFieldChoice from
"./models/ExtendedUserProfileFieldCho
import ExtendedUserProfileFieldLink from
"./models/ExtendedUserProfileFieldLink";
import FullExperiment from "./models/FullExperiment";
import Group from "./models/Group";
+import GroupAccountSSHProvisionerConfig from
"./models/GroupAccountSSHProvisionerConfig";
import GroupComputeResourcePreference from
"./models/GroupComputeResourcePreference";
import GroupPermission from "./models/GroupPermission";
import GroupResourceProfile from "./models/GroupResourceProfile";
+import AwsComputeResourcePreference from
"./models/AwsComputeResourcePreference";
+import ResourceType from "./models/ResourceType";
+import SlurmComputeResourcePreference from
"./models/SlurmComputeResourcePreference";
import IAMUserProfile from "./models/IAMUserProfile";
import InputDataObjectType from "./models/InputDataObjectType";
import JobState from "./models/JobState";
@@ -71,6 +75,7 @@ const models = {
ApplicationDeploymentDescription,
ApplicationInterfaceDefinition,
ApplicationModule,
+ AwsComputeResourcePreference,
BaseModel,
BatchQueue,
BatchQueueResourcePolicy,
@@ -88,6 +93,7 @@ const models = {
ExtendedUserProfileFieldLink,
FullExperiment,
Group,
+ GroupAccountSSHProvisionerConfig,
GroupComputeResourcePreference,
GroupPermission,
GroupResourceProfile,
@@ -100,8 +106,10 @@ const models = {
ParallelismType,
Project,
ResourcePermissionType,
+ ResourceType,
SetEnvPaths,
SharedEntity,
+ SlurmComputeResourcePreference,
StoragePreference,
SummaryType,
UserConfigurationData,
diff --git
a/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/models/AwsComputeResourcePreference.js
b/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/models/AwsComputeResourcePreference.js
new file mode 100644
index 000000000..8ba728871
--- /dev/null
+++
b/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/models/AwsComputeResourcePreference.js
@@ -0,0 +1,21 @@
+import BaseModel from "./BaseModel";
+
+const FIELDS = [
+ "region",
+ "preferredAmiId",
+ "preferredInstanceType",
+];
+
+export default class AwsComputeResourcePreference extends BaseModel {
+ constructor(data = {}) {
+ super(FIELDS, data);
+ }
+
+ toJSON() {
+ return { ...this };
+ }
+
+ validate() {
+ return {};
+ }
+}
diff --git
a/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/models/GroupAccountSSHProvisionerConfig.js
b/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/models/GroupAccountSSHProvisionerConfig.js
new file mode 100644
index 000000000..d43346f7e
--- /dev/null
+++
b/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/models/GroupAccountSSHProvisionerConfig.js
@@ -0,0 +1,18 @@
+import BaseModel from "./BaseModel";
+
+const FIELDS = [
+ "resourceId",
+ "groupResourceProfileId",
+ "configName",
+ "configValue",
+];
+
+export default class GroupAccountSSHProvisionerConfig extends BaseModel {
+ constructor(data = {}) {
+ super(FIELDS, data);
+ }
+
+ toJSON() {
+ return { ...this };
+ }
+}
diff --git
a/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/models/GroupComputeResourcePreference.js
b/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/models/GroupComputeResourcePreference.js
index eb1da485f..f13a317fe 100644
---
a/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/models/GroupComputeResourcePreference.js
+++
b/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/models/GroupComputeResourcePreference.js
@@ -1,5 +1,7 @@
import BaseModel from "./BaseModel";
-import ComputeResourceReservation from "./ComputeResourceReservation";
+import ResourceType from "./ResourceType";
+import SlurmComputeResourcePreference from "./SlurmComputeResourcePreference";
+import AwsComputeResourcePreference from "./AwsComputeResourcePreference";
const FIELDS = [
"computeResourceId",
@@ -12,27 +14,225 @@ const FIELDS = [
"loginUserName",
"preferredJobSubmissionProtocol",
"preferredDataMovementProtocol",
- "preferredBatchQueue",
"scratchLocation",
- "allocationProjectNumber",
"resourceSpecificCredentialStoreToken",
- "usageReportingGatewayId",
- "qualityOfService",
- "sshAccountProvisioner",
- "groupSSHAccountProvisionerConfigs",
- "sshAccountProvisionerAdditionalInfo",
{
- name: "reservations",
- type: ComputeResourceReservation,
- list: true,
- default: BaseModel.defaultNewInstance(Array),
+ name: "resourceType",
+ type: ResourceType,
+ required: true,
+ },
+ {
+ name: "specificPreferences",
+ type: null,
},
-
];
+const PREFERENCE_MODEL_MAP = {
+ SLURM: SlurmComputeResourcePreference,
+ AWS: AwsComputeResourcePreference,
+};
+
export default class GroupComputeResourcePreference extends BaseModel {
constructor(data = {}) {
+ const topLevelAllocationProjectNumber = data.allocationProjectNumber;
+ const rawSpecificPreferences = data.specificPreferences;
+
super(FIELDS, data);
+
+ const specificPrefsToUse = rawSpecificPreferences !== undefined &&
rawSpecificPreferences !== null
+ ? rawSpecificPreferences
+ : (data.specificPreferences !== undefined && data.specificPreferences
!== null ? data.specificPreferences : null);
+
+ if (specificPrefsToUse !== null) {
+ this.specificPreferences = specificPrefsToUse;
+ }
+
+ if (this.resourceType && typeof this.resourceType === 'number') {
+ this.resourceType = ResourceType.values.find(rt => rt.value ===
this.resourceType) || this.resourceType;
+ }
+
+ if (topLevelAllocationProjectNumber) {
+ if (this.resourceType && this.resourceType.name === 'SLURM') {
+ if (!this.specificPreferences) {
+ this.specificPreferences = {};
+ }
+ if (typeof this.specificPreferences === 'object' &&
!(this.specificPreferences instanceof BaseModel)) {
+ if (!this.specificPreferences.allocationProjectNumber) {
+ this.specificPreferences.allocationProjectNumber =
topLevelAllocationProjectNumber;
+ }
+ }
+ } else if (!this.resourceType && topLevelAllocationProjectNumber) {
+ this.resourceType = ResourceType.SLURM;
+ if (!this.specificPreferences) {
+ this.specificPreferences = {};
+ }
+ if (typeof this.specificPreferences === 'object' &&
!(this.specificPreferences instanceof BaseModel)) {
+ if (!this.specificPreferences.allocationProjectNumber) {
+ this.specificPreferences.allocationProjectNumber =
topLevelAllocationProjectNumber;
+ }
+ }
+ }
+ }
+
+ this._coerceSpecificPreferences();
+ }
+
+ toJSON() {
+ const json = {...this};
+ if (this.resourceType && this.resourceType.value !== undefined) {
+ json.resourceType = this.resourceType.value;
+ } else if (this.resourceType && this.resourceType.name) {
+ json.resourceType = this.resourceType.name;
+ }
+
+ let specificPrefsPayload = this.specificPreferences;
+ if (
+ this.specificPreferences &&
+ typeof this.specificPreferences.toJSON === "function"
+ ) {
+ specificPrefsPayload = this.specificPreferences.toJSON();
+ }
+
+ if (specificPrefsPayload && this.isResourceType("SLURM")) {
+ json.specificPreferences = {slurm: specificPrefsPayload};
+ } else if (specificPrefsPayload && this.isResourceType("AWS")) {
+ json.specificPreferences = {aws: specificPrefsPayload};
+ } else if (specificPrefsPayload) {
+ json.specificPreferences = specificPrefsPayload;
+ } else {
+ json.specificPreferences = null;
+ }
+
+ return json;
+ }
+
+ _coerceSpecificPreferences() {
+ // Ensure resourceType is properly set
+ if (this.resourceType && typeof this.resourceType === 'number') {
+ this.resourceType = ResourceType.byValue(this.resourceType) ||
ResourceType.values.find(rt => rt.value === this.resourceType) ||
this.resourceType;
+ }
+
+ if (!this.resourceType) {
+ this.specificPreferences = null;
+ return;
+ }
+
+ if (!this.resourceType.name) {
+ return;
+ }
+
+ if (
+ this.specificPreferences &&
+ this.specificPreferences instanceof BaseModel
+ ) {
+ return;
+ }
+ let rawData =
+ this.specificPreferences && typeof this.specificPreferences === "object"
+ ? this.specificPreferences
+ : null;
+
+ if (rawData && !(rawData instanceof BaseModel)) {
+ if (this.resourceType.name === 'SLURM' && 'slurm' in rawData) {
+ rawData = rawData.slurm;
+ } else if (this.resourceType.name === 'AWS') {
+ if ('aws' in rawData) {
+ rawData = rawData.aws;
+ }
+ }
+ }
+
+ const PreferenceModel = PREFERENCE_MODEL_MAP[this.resourceType.name];
+ if (PreferenceModel) {
+ const newPref = rawData
+ ? new PreferenceModel(rawData)
+ : new PreferenceModel();
+ this.specificPreferences = newPref;
+ } else {
+ this.specificPreferences = rawData;
+ }
+ }
+
+ resetSpecificPreferences(data = null) {
+ if (!this.resourceType) {
+ this.specificPreferences = null;
+ return;
+ }
+ if (data && typeof data === "object") {
+ this.specificPreferences = data;
+ } else {
+ this.specificPreferences = null;
+ }
+ this._coerceSpecificPreferences();
+ }
+
+ isResourceType(resourceTypeName) {
+ return (
+ !!this.resourceType && this.resourceType.name === resourceTypeName
+ );
+ }
+
+ _ensureSpecificPreferences() {
+ if (!this.specificPreferences) {
+ this._coerceSpecificPreferences();
+ }
+ }
+
+ _getSlurmField(fieldName, defaultValue) {
+ if (this.isResourceType("SLURM") && this.specificPreferences) {
+ return this.specificPreferences[fieldName];
+ }
+ return defaultValue;
+ }
+
+ _setSlurmField(fieldName, value) {
+ if (!this.isResourceType("SLURM")) {
+ return;
+ }
+ this._ensureSpecificPreferences();
+ if (this.specificPreferences) {
+ this.specificPreferences[fieldName] = value;
+ }
+ }
+
+ get allocationProjectNumber() {
+ return this._getSlurmField("allocationProjectNumber");
+ }
+
+ set allocationProjectNumber(value) {
+ this._setSlurmField("allocationProjectNumber", value);
+ }
+
+ get preferredBatchQueue() {
+ return this._getSlurmField("preferredBatchQueue");
+ }
+
+ set preferredBatchQueue(value) {
+ this._setSlurmField("preferredBatchQueue", value);
+ }
+
+ get qualityOfService() {
+ return this._getSlurmField("qualityOfService");
+ }
+
+ set qualityOfService(value) {
+ this._setSlurmField("qualityOfService", value);
+ }
+
+ get usageReportingGatewayId() {
+ return this._getSlurmField("usageReportingGatewayId");
+ }
+
+ set usageReportingGatewayId(value) {
+ this._setSlurmField("usageReportingGatewayId", value);
+ }
+
+ get reservations() {
+ return this._getSlurmField("reservations", []);
+ }
+
+ set reservations(value) {
+ this._setSlurmField("reservations", value);
}
validate() {
@@ -44,6 +244,15 @@ export default class GroupComputeResourcePreference extends
BaseModel {
validationResults["scratchLocation"] =
"Please provide a scratch location.";
}
+ if (!this.resourceType) {
+ validationResults["resourceType"] = "Please select a resource type.";
+ }
+ if (this.resourceType && this.specificPreferences) {
+ const specificValidation = this.specificPreferences.validate();
+ if (specificValidation && Object.keys(specificValidation).length > 0) {
+ Object.assign(validationResults, specificValidation);
+ }
+ }
return validationResults;
}
}
diff --git
a/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/models/ResourceType.js
b/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/models/ResourceType.js
new file mode 100644
index 000000000..f674c94ed
--- /dev/null
+++
b/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/models/ResourceType.js
@@ -0,0 +1,5 @@
+import BaseEnum from "./BaseEnum";
+
+export default class ResourceType extends BaseEnum {}
+ResourceType.init(["SLURM", "AWS"]);
+
diff --git
a/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/models/SlurmComputeResourcePreference.js
b/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/models/SlurmComputeResourcePreference.js
new file mode 100644
index 000000000..11e350c17
--- /dev/null
+++
b/airavata-django-portal/django_airavata/apps/api/static/django_airavata_api/js/models/SlurmComputeResourcePreference.js
@@ -0,0 +1,49 @@
+import BaseModel from "./BaseModel";
+import ComputeResourceReservation from "./ComputeResourceReservation";
+import GroupAccountSSHProvisionerConfig from
"./GroupAccountSSHProvisionerConfig";
+
+const FIELDS = [
+ "allocationProjectNumber",
+ "preferredBatchQueue",
+ "qualityOfService",
+ "usageReportingGatewayId",
+ "sshAccountProvisioner",
+ {
+ name: "groupSSHAccountProvisionerConfigs",
+ type: GroupAccountSSHProvisionerConfig,
+ list: true,
+ default: BaseModel.defaultNewInstance(Array),
+ },
+ "sshAccountProvisionerAdditionalInfo",
+ {
+ name: "reservations",
+ type: ComputeResourceReservation,
+ list: true,
+ default: BaseModel.defaultNewInstance(Array),
+ },
+];
+
+export default class SlurmComputeResourcePreference extends BaseModel {
+ constructor(data = {}) {
+ super(FIELDS, data);
+ }
+
+ toJSON() {
+ const json = { ...this };
+ if (json.groupSSHAccountProvisionerConfigs) {
+ json.groupSSHAccountProvisionerConfigs =
json.groupSSHAccountProvisionerConfigs.map((cfg) =>
+ typeof cfg.toJSON === "function" ? cfg.toJSON() : cfg
+ );
+ }
+ if (json.reservations) {
+ json.reservations = json.reservations.map((res) =>
+ typeof res.toJSON === "function" ? res.toJSON() : res
+ );
+ }
+ return json;
+ }
+
+ validate() {
+ return {};
+ }
+}
diff --git a/airavata-django-portal/django_airavata/apps/api/views.py
b/airavata-django-portal/django_airavata/apps/api/views.py
index 9130544c7..1cca33e26 100644
--- a/airavata-django-portal/django_airavata/apps/api/views.py
+++ b/airavata-django-portal/django_airavata/apps/api/views.py
@@ -24,6 +24,10 @@ from airavata.model.experiment.ttypes import (
ExperimentModel,
ExperimentSearchFields
)
+from airavata.model.appcatalog.groupresourceprofile.ttypes import (
+ GroupComputeResourcePreference,
+ ResourceType
+)
from airavata.model.group.ttypes import ResourcePermissionType
from airavata.model.user.ttypes import Status
from airavata_django_portal_sdk import (
@@ -950,6 +954,8 @@ class GroupResourceProfileViewSet(APIBackedViewSet):
group_resource_profile.creationTime =
new_group_resource_profile.creationTime
def perform_update(self, serializer):
+ original_instance = serializer.instance
+
grp = serializer.save()
for removed_compute_resource_preference \
in grp._removed_compute_resource_preferences:
@@ -967,6 +973,156 @@ class GroupResourceProfileViewSet(APIBackedViewSet):
self.request.airavata_client.removeGroupBatchQueueResourcePolicy(
self.authz_token,
removed_batch_queue_resource_policy.resourcePolicyId)
+ if hasattr(grp, 'computePreferences') and grp.computePreferences:
+ from collections import OrderedDict
+ from django_airavata.apps.api.serializers import
GroupComputeResourcePreferenceSerializer
+
+ for pref in grp.computePreferences:
+ if isinstance(pref, GroupComputeResourcePreference):
+ if not hasattr(pref, 'resourceType') or pref.resourceType
is None:
+ resource_type = None
+ if hasattr(pref, 'specificPreferences') and
pref.specificPreferences:
+ if isinstance(pref.specificPreferences, (dict,
OrderedDict)):
+ specific_prefs_dict = pref.specificPreferences
+ if 'slurm' in specific_prefs_dict or
'allocationProjectNumber' in specific_prefs_dict:
+ resource_type = ResourceType.SLURM
+ elif 'aws' in specific_prefs_dict or 'region'
in specific_prefs_dict:
+ resource_type = ResourceType.AWS
+ else:
+ resource_type = ResourceType.SLURM
+ elif hasattr(pref.specificPreferences, 'slurm')
and pref.specificPreferences.slurm:
+ resource_type = ResourceType.SLURM
+ elif hasattr(pref.specificPreferences, 'aws') and
pref.specificPreferences.aws:
+ resource_type = ResourceType.AWS
+ else:
+ resource_type = ResourceType.SLURM
+ else:
+ resource_type = ResourceType.SLURM
+ pref.resourceType = resource_type
+
+ resource_type = pref.resourceType if hasattr(pref,
'resourceType') and pref.resourceType else None
+ if resource_type:
+ if hasattr(pref, 'specificPreferences') and
isinstance(pref.specificPreferences, (dict, OrderedDict)):
+
GroupComputeResourcePreferenceSerializer._convert_specific_preferences_dict_to_thrift(
+ pref, resource_type
+ )
+ elif hasattr(pref, 'specificPreferences') and
pref.specificPreferences:
+
GroupComputeResourcePreferenceSerializer._convert_specific_preferences_dict_to_thrift(
+ pref, resource_type
+ )
+
+ from collections import OrderedDict
+ from airavata.model.appcatalog.groupresourceprofile.ttypes import (
+ ComputeResourcePolicy,
+ BatchQueueResourcePolicy
+ )
+
+ if hasattr(grp, 'computeResourcePolicies') and
grp.computeResourcePolicies:
+ existing_policies_by_resource_id = {}
+ if original_instance and hasattr(original_instance,
'computeResourcePolicies'):
+ for existing_policy in
original_instance.computeResourcePolicies:
+ if hasattr(existing_policy, 'computeResourceId') and
hasattr(existing_policy, 'resourcePolicyId'):
+
existing_policies_by_resource_id[existing_policy.computeResourceId] =
existing_policy
+
+ indices_to_remove = []
+ for idx, policy in enumerate(grp.computeResourcePolicies):
+ if isinstance(policy, (dict, OrderedDict)):
+ try:
+ if isinstance(policy, OrderedDict):
+ policy = dict(policy)
+
+ compute_resource_id = policy.get('computeResourceId')
+ current_resource_policy_id =
policy.get('resourcePolicyId')
+
+ if not current_resource_policy_id:
+ if compute_resource_id and compute_resource_id in
existing_policies_by_resource_id:
+ existing_policy =
existing_policies_by_resource_id[compute_resource_id]
+ policy['resourcePolicyId'] =
existing_policy.resourcePolicyId
+ elif original_instance and
hasattr(original_instance, 'computeResourcePolicies') and idx <
len(original_instance.computeResourcePolicies):
+ existing_policy_by_idx =
original_instance.computeResourcePolicies[idx]
+ if hasattr(existing_policy_by_idx,
'resourcePolicyId') and existing_policy_by_idx.resourcePolicyId:
+ policy['resourcePolicyId'] =
existing_policy_by_idx.resourcePolicyId
+
+ if not policy.get('resourcePolicyId'):
+ indices_to_remove.append(idx)
+ continue
+
+ grp.computeResourcePolicies[idx] =
ComputeResourcePolicy(**policy)
+ except Exception as e:
+ log.error(
+ "GCPreference perform_update: Failed to convert
computeResourcePolicies[%d] OrderedDict to Thrift: %s, policy keys: %s",
+ idx,
+ str(e),
+ list(policy.keys()) if isinstance(policy, dict)
else list(policy.keys()),
+ exc_info=True,
+ )
+ raise
+
+ for idx in reversed(indices_to_remove):
+ grp.computeResourcePolicies.pop(idx)
+
+ if hasattr(grp, 'batchQueueResourcePolicies') and
grp.batchQueueResourcePolicies:
+ existing_bq_policies_by_key = {}
+ if original_instance and hasattr(original_instance,
'batchQueueResourcePolicies'):
+ for existing_bq_policy in
original_instance.batchQueueResourcePolicies:
+ if (hasattr(existing_bq_policy, 'computeResourceId') and
+ hasattr(existing_bq_policy, 'queuename') and
+ hasattr(existing_bq_policy, 'resourcePolicyId')):
+ key = (existing_bq_policy.computeResourceId,
existing_bq_policy.queuename)
+ existing_bq_policies_by_key[key] = existing_bq_policy
+
+ for idx, policy in enumerate(grp.batchQueueResourcePolicies):
+ if isinstance(policy, (dict, OrderedDict)):
+ try:
+ compute_resource_id = policy.get('computeResourceId')
+ queuename = policy.get('queuename')
+ if compute_resource_id and queuename:
+ key = (compute_resource_id, queuename)
+ if key in existing_bq_policies_by_key:
+ existing_bq_policy =
existing_bq_policies_by_key[key]
+ if 'resourcePolicyId' not in policy or
policy.get('resourcePolicyId') is None:
+ policy['resourcePolicyId'] =
existing_bq_policy.resourcePolicyId
+ grp.batchQueueResourcePolicies[idx] =
BatchQueueResourcePolicy(**policy)
+ except Exception as e:
+ log.error(
+ "GCPreference perform_update: Failed to convert
batchQueueResourcePolicies[%d] OrderedDict to Thrift: %s",
+ idx,
+ str(e),
+ exc_info=True,
+ )
+
+ if hasattr(grp, 'computePreferences') and grp.computePreferences:
+ for idx, pref in enumerate(grp.computePreferences):
+ if isinstance(pref, (dict, OrderedDict)):
+ from django_airavata.apps.api.serializers import
GroupComputeResourcePreferenceSerializer
+ serializer = GroupComputeResourcePreferenceSerializer()
+ try:
+ pref = serializer.create(pref)
+ grp.computePreferences[idx] = pref
+ except Exception as e:
+ log.error(
+ "GCPreference perform_update: Failed to convert
OrderedDict to Thrift: %s",
+ str(e),
+ exc_info=True,
+ )
+
+ if isinstance(pref, GroupComputeResourcePreference):
+ if hasattr(pref, 'specificPreferences') and
pref.specificPreferences:
+ if hasattr(pref.specificPreferences, 'slurm') and
pref.specificPreferences.slurm:
+
GroupComputeResourcePreferenceSerializer._convert_nested_list_fields_to_thrift(
+ pref.specificPreferences.slurm
+ )
+ if hasattr(pref.specificPreferences.slurm,
'reservations') and pref.specificPreferences.slurm.reservations:
+ for res_idx, res in
enumerate(pref.specificPreferences.slurm.reservations):
+ if isinstance(res, (dict, OrderedDict)):
+ from
airavata.model.appcatalog.groupresourceprofile.ttypes import
ComputeResourceReservation
+
pref.specificPreferences.slurm.reservations[res_idx] =
ComputeResourceReservation(**res)
+ if hasattr(pref.specificPreferences.slurm,
'groupSSHAccountProvisionerConfigs') and
pref.specificPreferences.slurm.groupSSHAccountProvisionerConfigs:
+ for cfg_idx, cfg in
enumerate(pref.specificPreferences.slurm.groupSSHAccountProvisionerConfigs):
+ if isinstance(cfg, (dict, OrderedDict)):
+ from
airavata.model.appcatalog.groupresourceprofile.ttypes import
GroupAccountSSHProvisionerConfig
+
pref.specificPreferences.slurm.groupSSHAccountProvisionerConfigs[cfg_idx] =
GroupAccountSSHProvisionerConfig(**cfg)
+
self.request.airavata_client.updateGroupResourceProfile(
self.authz_token, grp)