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/06/18 19:38:30 UTC

[airavata-django-portal] 13/20: 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

commit cf98fdbd3abfb206a315439b2ed1dae342dda4bf
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 c0804f1..59421a4 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,
+  });
+}