You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2021/05/12 21:06:14 UTC
[airavata-django-portal] branch airavata-3453 updated:
AIRAVATA-3453 Implemented queue settings editor
This is an automated email from the ASF dual-hosted git repository.
machristie pushed a commit to branch airavata-3453
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git
The following commit(s) were added to refs/heads/airavata-3453 by this push:
new c1e7694 AIRAVATA-3453 Implemented queue settings editor
c1e7694 is described below
commit c1e7694cc4fdc96491dd0996c37928a0e61c33ca
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed May 12 17:06:00 2021 -0400
AIRAVATA-3453 Implemented queue settings editor
---
.../api/static/django_airavata_api/js/index.js | 2 +
.../js/web-components/QueueSettingsEditor.vue | 231 ++++++++++++++++++++-
.../js/web-components/ResourceSelectionEditor.vue | 229 ++++++++++++++++++--
.../js/web-components/store.js | 12 ++
4 files changed, 455 insertions(+), 19 deletions(-)
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/index.js b/django_airavata/apps/api/static/django_airavata_api/js/index.js
index 489589d..0716823 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/index.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/index.js
@@ -10,6 +10,7 @@ import BaseModel from "./models/BaseModel";
import BatchQueue from "./models/BatchQueue";
import BatchQueueResourcePolicy from "./models/BatchQueueResourcePolicy";
import CommandObject from "./models/CommandObject";
+import ComputationalResourceSchedulingModel from "./models/ComputationalResourceSchedulingModel";
import ComputeResourcePolicy from "./models/ComputeResourcePolicy";
import ComputeResourceReservation from "./models/ComputeResourceReservation";
import DataProduct from "./models/DataProduct";
@@ -69,6 +70,7 @@ const models = {
BatchQueue,
BatchQueueResourcePolicy,
CommandObject,
+ ComputationalResourceSchedulingModel,
ComputeResourcePolicy,
ComputeResourceReservation,
DataProduct,
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/QueueSettingsEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/QueueSettingsEditor.vue
index a02d2bb..6f04b3d 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/QueueSettingsEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/QueueSettingsEditor.vue
@@ -1,13 +1,236 @@
<template>
- <div>QueueSettingsEditor</div>
+ <div>
+ <div class="card border-default">
+ <b-link
+ @click="showConfiguration = !showConfiguration"
+ class="card-link text-dark"
+ >
+ <div class="card-body">
+ <h5 class="card-title mb-4">
+ Settings for queue {{ computationalResourceScheduling.queueName }}
+ </h5>
+ <div class="row">
+ <div class="col">
+ <h3 class="h5 mb-0">
+ {{ computationalResourceScheduling.nodeCount }}
+ </h3>
+ <span class="text-muted text-uppercase">NODE COUNT</span>
+ </div>
+ <div class="col">
+ <h3 class="h5 mb-0">
+ {{ computationalResourceScheduling.totalCPUCount }}
+ </h3>
+ <span class="text-muted text-uppercase">CORE COUNT</span>
+ </div>
+ <div class="col">
+ <h3 class="h5 mb-0">
+ {{ computationalResourceScheduling.wallTimeLimit }} minutes
+ </h3>
+ <span class="text-muted text-uppercase">TIME LIMIT</span>
+ </div>
+ <div class="col" v-if="maxMemory > 0">
+ <h3 class="h5 mb-0">
+ {{ computationalResourceScheduling.totalPhysicalMemory }} MB
+ </h3>
+ <span class="text-muted text-uppercase">PHYSICAL MEMORY</span>
+ </div>
+ </div>
+ </div>
+ </b-link>
+ </div>
+ <div v-if="showConfiguration">
+ <b-form-group label="Select a Queue" label-for="queue">
+ <b-form-select
+ id="queue"
+ v-model="computationalResourceScheduling.queueName"
+ :options="queueOptions"
+ required
+ @change="queueChanged"
+ >
+ </b-form-select>
+ <div slot="description">{{ queueDescription }}</div>
+ </b-form-group>
+ <b-form-group label="Node Count" label-for="node-count">
+ <b-form-input
+ id="node-count"
+ type="number"
+ min="1"
+ :max="maxAllowedNodes"
+ v-model="computationalResourceScheduling.nodeCount"
+ required
+ @input.native.stop="emitValueChanged"
+ >
+ </b-form-input>
+ <div slot="description">
+ <i class="fa fa-info-circle" aria-hidden="true"></i>
+ Max Allowed Nodes = {{ maxAllowedNodes }}
+ </div>
+ </b-form-group>
+ <b-form-group label="Total Core Count" label-for="core-count">
+ <b-form-input
+ id="core-count"
+ type="number"
+ min="1"
+ :max="maxAllowedCores"
+ v-model="computationalResourceScheduling.totalCPUCount"
+ required
+ @input.native.stop="emitValueChanged"
+ >
+ </b-form-input>
+ <div slot="description">
+ <i class="fa fa-info-circle" aria-hidden="true"></i>
+ Max Allowed Cores = {{ maxAllowedCores }}
+ </div>
+ </b-form-group>
+ <b-form-group label="Wall Time Limit" label-for="walltime-limit">
+ <b-input-group append="minutes">
+ <b-form-input
+ id="walltime-limit"
+ type="number"
+ min="1"
+ :max="maxAllowedWalltime"
+ v-model="computationalResourceScheduling.wallTimeLimit"
+ required
+ @input.native.stop="emitValueChanged"
+ >
+ </b-form-input>
+ </b-input-group>
+ <div slot="description">
+ <i class="fa fa-info-circle" aria-hidden="true"></i>
+ Max Allowed Wall Time = {{ maxAllowedWalltime }} minutes
+ </div>
+ </b-form-group>
+ <b-form-group
+ v-if="maxMemory > 0"
+ label="Total Physical Memory"
+ label-for="total-physical-memory"
+ >
+ <b-input-group append="MB">
+ <b-form-input
+ id="total-physical-memory"
+ type="number"
+ min="0"
+ :max="maxMemory"
+ v-model="computationalResourceScheduling.totalPhysicalMemory"
+ @input.native.stop="emitValueChanged"
+ >
+ </b-form-input>
+ </b-input-group>
+ <div slot="description">
+ <i class="fa fa-info-circle" aria-hidden="true"></i>
+ Max Physical Memory = {{ maxMemory }} MB
+ </div>
+ </b-form-group>
+ <div>
+ <a
+ class="text-secondary action-link"
+ href="#"
+ @click.prevent="showConfiguration = false"
+ >
+ <i class="fa fa-times text-secondary" aria-hidden="true"></i>
+ Hide Settings</a
+ >
+ </div>
+ </div>
+ </div>
</template>
<script>
-export default {
+import { models, utils } from "django-airavata-api";
+import Vue from "vue";
+import { BootstrapVue } from "bootstrap-vue";
+Vue.use(BootstrapVue);
-}
+export default {
+ props: {
+ value: {
+ type: models.ComputationalResourceSchedulingModel,
+ // required: true,
+ },
+ queues: {
+ type: Array, // of BatchQueue
+ // required: true,
+ },
+ maxAllowedNodes: {
+ type: Number,
+ required: true,
+ },
+ maxAllowedCores: {
+ type: Number,
+ required: true,
+ },
+ maxAllowedWalltime: {
+ type: Number,
+ required: true,
+ },
+ maxMemory: {
+ type: Number,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ computationalResourceScheduling: this.cloneValue(),
+ showConfiguration: false,
+ };
+ },
+ computed: {
+ queueOptions() {
+ if (!this.queues) {
+ return [];
+ }
+ const queueOptions = this.queues.map((q) => {
+ return {
+ value: q.queueName,
+ text: q.queueName,
+ };
+ });
+ utils.StringUtils.sortIgnoreCase(queueOptions, (q) => q.text);
+ return queueOptions;
+ },
+ queue() {
+ return this.queues
+ ? this.queues.find((q) => q.queueName === this.queueName)
+ : null;
+ },
+ queueName() {
+ return this.computationalResourceScheduling
+ ? this.computationalResourceScheduling.queueName
+ : null;
+ },
+ queueDescription() {
+ return this.queue ? this.queue.queueDescription : null;
+ },
+ },
+ methods: {
+ cloneValue() {
+ return this.value
+ ? this.value.clone()
+ : new models.ComputationalResourceSchedulingModel();
+ },
+ emitValueChanged() {
+ const inputEvent = new CustomEvent("input", {
+ detail: [this.computationalResourceScheduling.clone()],
+ composed: true,
+ bubbles: true,
+ });
+ this.$el.dispatchEvent(inputEvent);
+ },
+ queueChanged() {
+ this.emitValueChanged();
+ },
+ },
+ watch: {
+ value: {
+ handler() {
+ this.computationalResourceScheduling = this.cloneValue();
+ },
+ deep: true,
+ },
+ },
+};
</script>
<style>
-
+@import url("./styles.css");
</style>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ResourceSelectionEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ResourceSelectionEditor.vue
index d6824ec..f45c133 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ResourceSelectionEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/ResourceSelectionEditor.vue
@@ -17,9 +17,8 @@
</div>
<div @input.stop="updateComputationalResourceScheduling">
<adpf-queue-settings-editor
+ ref="queueSettingsEditor"
slot="resource-selection-queue-settings"
- :value="userConfigurationData.computationalResourceScheduling"
- :queues="queues"
:max-allowed-nodes="maxAllowedNodes"
:max-allowed-cores="maxAllowedCores"
:max-allowed-walltime="maxAllowedWalltime"
@@ -32,8 +31,10 @@
<script>
import { models } from "django-airavata-api";
import {
+ getAppDeploymentQueues,
getApplicationDeployments,
getDefaultComputeResourceId,
+ getGroupResourceProfile,
} from "./store";
export default {
// TODO: better name? UserConfigurationDataEditor?
@@ -50,11 +51,8 @@ export default {
return {
userConfigurationData: this.cloneValue(),
applicationDeployments: [],
- queues: [],
- maxAllowedNodes: 0,
- maxAllowedCores: 0,
- maxAllowedWalltime: 0,
- maxMemory: 0,
+ appDeploymentQueues: [],
+ groupResourceProfile: null,
defaultComputeResourceId: null,
};
},
@@ -74,51 +72,241 @@ export default {
.resourceHostId
: null;
},
+ applicationDeployment() {
+ if (this.applicationDeployments && this.resourceHostId) {
+ return this.applicationDeployments.find(
+ (ad) => ad.computeHostId === this.resourceHostId
+ );
+ } else {
+ return null;
+ }
+ },
+ computeResourcePolicy() {
+ if (!this.groupResourceProfile || !this.resourceHostId) {
+ return null;
+ }
+ return this.groupResourceProfile.computeResourcePolicies.find(
+ (crp) => crp.computeResourceId === this.resourceHostId
+ );
+ },
+ batchQueueResourcePolicies: function () {
+ if (!this.groupResourceProfile || !this.resourceHostId) {
+ return null;
+ }
+ return this.groupResourceProfile.batchQueueResourcePolicies.filter(
+ (bqrp) => bqrp.computeResourceId === this.resourceHostId
+ );
+ },
+ batchQueueResourcePolicy() {
+ if (!this.batchQueueResourcePolicies || !this.queueName) {
+ return null;
+ }
+ return this.batchQueueResourcePolicies.find(
+ (bqrp) => bqrp.queuename === this.queueName
+ );
+ },
+ queueName() {
+ return this.userConfigurationData &&
+ this.userConfigurationData.computationalResourceScheduling
+ ? this.userConfigurationData.computationalResourceScheduling.queueName
+ : null;
+ },
+ queues() {
+ return this.appDeploymentQueues
+ ? this.appDeploymentQueues.filter((q) =>
+ this.isQueueInComputeResourcePolicy(q.queueName)
+ )
+ : [];
+ },
+ queue() {
+ return this.queues && this.queueName
+ ? this.queues.find((q) => q.queueName === this.queueName)
+ : null;
+ },
+ maxAllowedCores: function () {
+ if (!this.queue) {
+ return 0;
+ }
+ const batchQueueResourcePolicy = this.batchQueueResourcePolicy;
+ if (batchQueueResourcePolicy) {
+ return Math.min(
+ batchQueueResourcePolicy.maxAllowedCores,
+ this.queue.maxProcessors
+ );
+ }
+ return this.queue.maxProcessors;
+ },
+ maxAllowedNodes: function () {
+ if (!this.queue) {
+ return 0;
+ }
+ const batchQueueResourcePolicy = this.batchQueueResourcePolicy;
+ if (batchQueueResourcePolicy) {
+ return Math.min(
+ batchQueueResourcePolicy.maxAllowedNodes,
+ this.queue.maxNodes
+ );
+ }
+ return this.queue.maxNodes;
+ },
+ maxAllowedWalltime: function () {
+ if (!this.queue) {
+ return 0;
+ }
+ const batchQueueResourcePolicy = this.batchQueueResourcePolicy;
+ if (batchQueueResourcePolicy) {
+ return Math.min(
+ batchQueueResourcePolicy.maxAllowedWalltime,
+ this.queue.maxRunTime
+ );
+ }
+ return this.queue.maxRunTime;
+ },
+ maxMemory() {
+ return this.queue ? this.queue.maxMemory : 0;
+ },
},
methods: {
emitValueChanged: function () {
const inputEvent = new CustomEvent("input", {
- detail: [this.userConfigurationData],
+ detail: [this.userConfigurationData.clone()],
composed: true,
bubbles: true,
});
this.$el.dispatchEvent(inputEvent);
},
- updateGroupResourceProfileId(event) {
+ async updateGroupResourceProfileId(event) {
const [groupResourceProfileId] = event.detail;
this.userConfigurationData.groupResourceProfileId = groupResourceProfileId;
this.emitValueChanged();
- this.loadApplicationDeployments();
+ await this.loadGroupResourceProfile();
+ await this.loadApplicationDeployments();
+ // allowed queues may have changed
+ // TODO: reapply batchQueueResourcePolicy if batchQueueResourcePolicy
},
updateComputeResourceHostId(event) {
const [computeResourceHostId] = event.detail;
this.userConfigurationData.computationalResourceScheduling.resourceHostId = computeResourceHostId;
this.emitValueChanged();
- // TODO: recalculate queues for the selected host
+ this.loadAppDeploymentQueues();
},
updateComputationalResourceScheduling(event) {
const [computationalResourceScheduling] = event.detail;
+ const queueChanged =
+ this.queueName !== computationalResourceScheduling.queueName;
this.userConfigurationData.computationalResourceScheduling = computationalResourceScheduling;
+ if (queueChanged) {
+ this.initializeQueue();
+ }
this.emitValueChanged();
- // TODO: recalculate maxes for the selected queue, etc.
},
async loadApplicationDeployments() {
this.applicationDeployments = await getApplicationDeployments(
this.applicationModuleId,
this.groupResourceProfileId
);
+ // Make sure that resource host id is in the list of app deployments
+ this.initializeResourceHostId();
+ },
+ initializeResourceHostId() {
+ // if there isn't a selected compute resource or there is but it isn't in
+ // the list of app deployments, set a default one
if (
- !this.userConfigurationData.computationalResourceScheduling
- .computeHostId
+ !this.resourceHostId ||
+ !this.computeResources.find((crid) => crid === this.resourceHostId)
) {
this.userConfigurationData.computationalResourceScheduling.resourceHostId = this.getDefaultResourceHostId();
+ this.emitValueChanged();
+ }
+ },
+ async loadAppDeploymentQueues() {
+ const applicationDeployment = this.applicationDeployment;
+ this.appDeploymentQueues = await getAppDeploymentQueues(
+ applicationDeployment.appDeploymentId
+ );
+ // set to the default queue or the first one
+ const defaultQueue = this.getDefaultQueue();
+ if (defaultQueue) {
+ this.userConfigurationData.computationalResourceScheduling.queueName =
+ defaultQueue.queueName;
+ } else {
+ this.userConfigurationData.computationalResourceScheduling.queueName = null;
+ }
+ this.initializeQueue();
+ },
+ isQueueInComputeResourcePolicy: function (queueName) {
+ if (!this.computeResourcePolicy) {
+ return true;
+ }
+ return this.computeResourcePolicy.allowedBatchQueues.includes(queueName);
+ },
+ initializeQueue() {
+ const queue = this.queue;
+ if (queue) {
+ const crs = this.userConfigurationData.computationalResourceScheduling;
+ crs.queueName = queue.queueName;
+ crs.totalCPUCount = this.getDefaultCPUCount(queue);
+ crs.nodeCount = this.getDefaultNodeCount(queue);
+ crs.wallTimeLimit = this.getDefaultWalltime(queue);
+ if (this.maxMemory === 0) {
+ crs.totalPhysicalMemory = 0;
+ }
+ } else {
+ const crs = this.userConfigurationData.computationalResourceScheduling;
+ crs.queueName = null;
+ crs.totalCPUCount = 0;
+ crs.nodeCount = 0;
+ crs.wallTimeLimit = 0;
+ crs.totalPhysicalMemory = 0;
}
+ this.emitValueChanged();
+ },
+ getDefaultQueue() {
+ const defaultQueue = this.queues.find((q) => q.isDefaultQueue);
+ if (defaultQueue) {
+ return defaultQueue;
+ } else if (this.queues.length > 0) {
+ return this.queues[0];
+ } else {
+ return null;
+ }
+ },
+ getDefaultCPUCount(queue) {
+ const batchQueueResourcePolicy = this.batchQueueResourcePolicy;
+ if (batchQueueResourcePolicy) {
+ return Math.min(
+ batchQueueResourcePolicy.maxAllowedCores,
+ queue.defaultCPUCount
+ );
+ }
+ return queue.defaultCPUCount;
+ },
+ getDefaultNodeCount(queue) {
+ const batchQueueResourcePolicy = this.batchQueueResourcePolicy;
+ if (batchQueueResourcePolicy) {
+ return Math.min(
+ batchQueueResourcePolicy.maxAllowedNodes,
+ queue.defaultNodeCount
+ );
+ }
+ return queue.defaultNodeCount;
+ },
+ getDefaultWalltime(queue) {
+ const batchQueueResourcePolicy = this.batchQueueResourcePolicy;
+ if (batchQueueResourcePolicy) {
+ return Math.min(
+ batchQueueResourcePolicy.maxAllowedWalltime,
+ queue.defaultWalltime
+ );
+ }
+ return queue.defaultWalltime;
},
cloneValue() {
- return this.value ? new models.UserConfigurationData(this.value) : null;
+ return this.value ? this.value.clone() : null;
},
async loadData() {
if (this.groupResourceProfileId) {
+ this.loadGroupResourceProfile();
this.loadApplicationDeployments();
}
this.loadDefaultComputeResourceId();
@@ -126,6 +314,11 @@ export default {
async loadDefaultComputeResourceId() {
this.defaultComputeResourceId = await getDefaultComputeResourceId();
},
+ async loadGroupResourceProfile() {
+ this.groupResourceProfile = await getGroupResourceProfile(
+ this.groupResourceProfileId
+ );
+ },
getDefaultResourceHostId() {
if (
this.defaultComputeResourceId &&
@@ -137,12 +330,16 @@ export default {
} else if (this.computeResources.length > 0) {
// Just pick the first one
return this.computeResources[0];
+ } else {
+ return null;
}
},
bindWebComponentProps() {
this.$nextTick(() => {
this.$refs.computeResourceSelector.computeResources = this.computeResources;
this.$refs.computeResourceSelector.value = this.resourceHostId;
+ this.$refs.queueSettingsEditor.value = this.userConfigurationData.computationalResourceScheduling;
+ this.$refs.queueSettingsEditor.queues = this.queues;
});
},
},
@@ -153,6 +350,8 @@ export default {
},
computeResources: "bindWebComponentProps",
resourceHostId: "bindWebComponentProps",
+ queueName: "bindWebComponentProps",
+ queues: "bindWebComponentProps",
},
};
</script>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js
index 0388289..dbf9818 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/web-components/store.js
@@ -69,6 +69,12 @@ export async function getGroupResourceProfiles() {
return await services.GroupResourceProfileService.list();
}
+export async function getGroupResourceProfile(groupResourceProfileId) {
+ return await services.GroupResourceProfileService.retrieve({
+ lookup: groupResourceProfileId,
+ });
+}
+
export async function getApplicationDeployments(
applicationId,
groupResourceProfileId
@@ -94,3 +100,9 @@ export async function getComputeResourceNames() {
// TODO: cache these
return await services.ComputeResourceService.names();
}
+
+export async function getAppDeploymentQueues(appDeploymentId) {
+ return await services.ApplicationDeploymentService.getQueues({
+ lookup: appDeploymentId,
+ });
+}