You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by jo...@apache.org on 2024/09/10 11:12:47 UTC

(cloudstack) branch main updated: Enhance the `listAffinityGroups` API by adding the dedicated resources related to an affinity group (#9188)

This is an automated email from the ASF dual-hosted git repository.

joao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new 6ec3c486ddb Enhance the `listAffinityGroups` API by adding the dedicated resources related to an affinity group (#9188)
6ec3c486ddb is described below

commit 6ec3c486ddb4d5daeba59e68e2634a446564f6dc
Author: Bernardo De Marco Gonçalves <be...@gmail.com>
AuthorDate: Tue Sep 10 08:12:41 2024 -0300

    Enhance the `listAffinityGroups` API by adding the dedicated resources related to an affinity group (#9188)
    
    * add dedicated resource response
    
    * populate dedicatedresources field
    
    * change affinity group name and description when it contains dedicated resources
    
    * display dedicatedresources on UI
    
    * add end of line to DedicatedResourceResponse class
    
    * remove unnecessary fully qualified names
---
 .../main/java/com/cloud/dc/DedicatedResources.java |  4 ++
 .../cloudstack/affinity/AffinityGroupResponse.java | 13 +++++
 .../dedicated/DedicatedResourceResponse.java       | 44 ++++++++++++++++
 .../dedicated/DedicatedResourceManagerImpl.java    | 36 +++++++------
 .../api/query/dao/AffinityGroupJoinDaoImpl.java    | 61 +++++++++++++++++++++-
 ui/public/locales/en.json                          |  1 +
 ui/public/locales/pt_BR.json                       |  1 +
 ui/src/components/view/DetailsTab.vue              | 20 +++++++
 ui/src/config/section/compute.js                   |  2 +-
 9 files changed, 163 insertions(+), 19 deletions(-)

diff --git a/api/src/main/java/com/cloud/dc/DedicatedResources.java b/api/src/main/java/com/cloud/dc/DedicatedResources.java
index 63188ca0b0e..23e6cc88a1e 100644
--- a/api/src/main/java/com/cloud/dc/DedicatedResources.java
+++ b/api/src/main/java/com/cloud/dc/DedicatedResources.java
@@ -21,6 +21,10 @@ import org.apache.cloudstack.api.Identity;
 import org.apache.cloudstack.api.InternalIdentity;
 
 public interface DedicatedResources extends InfrastructureEntity, InternalIdentity, Identity {
+    enum Type {
+        Zone, Pod, Cluster, Host
+    }
+
     @Override
     long getId();
 
diff --git a/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupResponse.java b/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupResponse.java
index 6cda8d07bd8..69f391a5656 100644
--- a/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupResponse.java
+++ b/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupResponse.java
@@ -25,6 +25,7 @@ import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.api.BaseResponse;
 import org.apache.cloudstack.api.EntityReference;
 import org.apache.cloudstack.api.response.ControlledViewEntityResponse;
+import org.apache.cloudstack.dedicated.DedicatedResourceResponse;
 
 import com.cloud.serializer.Param;
 
@@ -76,6 +77,10 @@ public class AffinityGroupResponse extends BaseResponse implements ControlledVie
     @Param(description = "virtual machine IDs associated with this affinity group")
     private List<String> vmIdList;
 
+    @SerializedName("dedicatedresources")
+    @Param(description = "dedicated resources associated with this affinity group")
+    private List<DedicatedResourceResponse> dedicatedResources;
+
     public AffinityGroupResponse() {
     }
 
@@ -171,4 +176,12 @@ public class AffinityGroupResponse extends BaseResponse implements ControlledVie
         this.vmIdList.add(vmId);
     }
 
+    public void addDedicatedResource(DedicatedResourceResponse dedicatedResourceResponse) {
+        if (this.dedicatedResources == null) {
+            this.dedicatedResources = new ArrayList<>();
+        }
+
+        this.dedicatedResources.add(dedicatedResourceResponse);
+    }
+
 }
diff --git a/api/src/main/java/org/apache/cloudstack/dedicated/DedicatedResourceResponse.java b/api/src/main/java/org/apache/cloudstack/dedicated/DedicatedResourceResponse.java
new file mode 100644
index 00000000000..fc116cbb91e
--- /dev/null
+++ b/api/src/main/java/org/apache/cloudstack/dedicated/DedicatedResourceResponse.java
@@ -0,0 +1,44 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.dedicated;
+
+import com.cloud.dc.DedicatedResources;
+import com.cloud.serializer.Param;
+
+import com.google.gson.annotations.SerializedName;
+
+import org.apache.cloudstack.api.BaseResponse;
+
+public class DedicatedResourceResponse extends BaseResponse {
+    @SerializedName("resourceid")
+    @Param(description = "the ID of the resource")
+    private String resourceId;
+
+    @SerializedName("resourcename")
+    @Param(description = "the name of the resource")
+    private String resourceName;
+
+    @SerializedName("resourcetype")
+    @Param(description = "the type of the resource")
+    private DedicatedResources.Type resourceType;
+
+    public DedicatedResourceResponse(String resourceId, String resourceName, DedicatedResources.Type resourceType) {
+        this.resourceId = resourceId;
+        this.resourceName = resourceName;
+        this.resourceType = resourceType;
+    }
+}
diff --git a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/dedicated/DedicatedResourceManagerImpl.java b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/dedicated/DedicatedResourceManagerImpl.java
index 9060eccb64a..4f1db396b7c 100644
--- a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/dedicated/DedicatedResourceManagerImpl.java
+++ b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/dedicated/DedicatedResourceManagerImpl.java
@@ -23,6 +23,7 @@ import java.util.Map;
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.cloudstack.affinity.AffinityGroup;
 import org.apache.cloudstack.affinity.AffinityGroupService;
 import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
@@ -236,7 +237,7 @@ public class DedicatedResourceManagerImpl implements DedicatedService {
             @Override
             public List<DedicatedResourceVO> doInTransaction(TransactionStatus status) {
                 // find or create the affinity group by name under this account/domain
-                AffinityGroup group = findOrCreateDedicatedAffinityGroup(domainId, accountIdFinal);
+                AffinityGroup group = findOrCreateDedicatedAffinityGroup(domainId, accountIdFinal, DedicatedResources.Type.Zone);
                 if (group == null) {
                     logger.error("Unable to dedicate zone due to, failed to create dedication affinity group");
                     throw new CloudRuntimeException("Failed to dedicate zone. Please contact Cloud Support.");
@@ -372,10 +373,10 @@ public class DedicatedResourceManagerImpl implements DedicatedService {
             @Override
             public List<DedicatedResourceVO> doInTransaction(TransactionStatus status) {
                 // find or create the affinity group by name under this account/domain
-                AffinityGroup group = findOrCreateDedicatedAffinityGroup(domainId, accountIdFinal);
+                AffinityGroup group = findOrCreateDedicatedAffinityGroup(domainId, accountIdFinal, DedicatedResources.Type.Pod);
                 if (group == null) {
-                    logger.error("Unable to dedicate zone due to, failed to create dedication affinity group");
-                    throw new CloudRuntimeException("Failed to dedicate zone. Please contact Cloud Support.");
+                    logger.error("Unable to dedicate pod due to, failed to create dedication affinity group");
+                    throw new CloudRuntimeException("Failed to dedicate pod. Please contact Cloud Support.");
                 }
                 DedicatedResourceVO dedicatedResource = new DedicatedResourceVO(null, podId, null, null, null, null, group.getId());
                 try {
@@ -485,10 +486,10 @@ public class DedicatedResourceManagerImpl implements DedicatedService {
             @Override
             public List<DedicatedResourceVO> doInTransaction(TransactionStatus status) {
                 // find or create the affinity group by name under this account/domain
-                AffinityGroup group = findOrCreateDedicatedAffinityGroup(domainId, accountIdFinal);
+                AffinityGroup group = findOrCreateDedicatedAffinityGroup(domainId, accountIdFinal, DedicatedResources.Type.Cluster);
                 if (group == null) {
-                    logger.error("Unable to dedicate zone due to, failed to create dedication affinity group");
-                    throw new CloudRuntimeException("Failed to dedicate zone. Please contact Cloud Support.");
+                    logger.error("Unable to dedicate cluster due to, failed to create dedication affinity group");
+                    throw new CloudRuntimeException("Failed to dedicate cluster. Please contact Cloud Support.");
                 }
                 DedicatedResourceVO dedicatedResource = new DedicatedResourceVO(null, null, clusterId, null, null, null, group.getId());
                 try {
@@ -582,10 +583,10 @@ public class DedicatedResourceManagerImpl implements DedicatedService {
             @Override
             public List<DedicatedResourceVO> doInTransaction(TransactionStatus status) {
                 // find or create the affinity group by name under this account/domain
-                AffinityGroup group = findOrCreateDedicatedAffinityGroup(domainId, accountIdFinal);
+                AffinityGroup group = findOrCreateDedicatedAffinityGroup(domainId, accountIdFinal, DedicatedResources.Type.Host);
                 if (group == null) {
-                    logger.error("Unable to dedicate zone due to, failed to create dedication affinity group");
-                    throw new CloudRuntimeException("Failed to dedicate zone. Please contact Cloud Support.");
+                    logger.error("Unable to dedicate host due to, failed to create dedication affinity group");
+                    throw new CloudRuntimeException("Failed to dedicate host. Please contact Cloud Support.");
                 }
                 DedicatedResourceVO dedicatedResource = new DedicatedResourceVO(null, null, null, hostId, null, null, group.getId());
                 try {
@@ -607,7 +608,7 @@ public class DedicatedResourceManagerImpl implements DedicatedService {
 
     }
 
-    private AffinityGroup findOrCreateDedicatedAffinityGroup(Long domainId, Long accountId) {
+    private AffinityGroup findOrCreateDedicatedAffinityGroup(Long domainId, Long accountId, DedicatedResources.Type dedicatedResource) {
         if (domainId == null) {
             return null;
         }
@@ -624,24 +625,25 @@ public class DedicatedResourceManagerImpl implements DedicatedService {
             if (group != null) {
                 return group;
             }
-            // default to a groupname with account/domain information
-            affinityGroupName = "DedicatedGrp-" + accountName;
 
+            // defaults to a groupName with resourceType and account/domain information
+            affinityGroupName = String.format("Dedicated%sGrp-%s", dedicatedResource, accountName);
         } else {
             // domain level group
             group = _affinityGroupDao.findDomainLevelGroupByType(domainId, "ExplicitDedication");
             if (group != null) {
                 return group;
             }
-            // default to a groupname with account/domain information
+
+            // defaults to a groupName with resourceType and account/domain information
             String domainName = _domainDao.findById(domainId).getName();
-            affinityGroupName = "DedicatedGrp-domain-" + domainName;
+            affinityGroupName = String.format("Dedicated%sGrp-domain-%s", dedicatedResource, domainName);
         }
 
-        group = _affinityGroupService.createAffinityGroup(accountName, null, domainId, affinityGroupName, "ExplicitDedication", "dedicated resources group");
+        String description = String.format("Dedicated %s group", StringUtils.lowerCase(dedicatedResource.toString()));
+        group = _affinityGroupService.createAffinityGroup(accountName, null, domainId, affinityGroupName, "ExplicitDedication", description);
 
         return group;
-
     }
 
     private List<UserVmVO> getVmsOnHost(long hostId) {
diff --git a/server/src/main/java/com/cloud/api/query/dao/AffinityGroupJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/AffinityGroupJoinDaoImpl.java
index 2a876ea8226..a5fd2bf11f1 100644
--- a/server/src/main/java/com/cloud/api/query/dao/AffinityGroupJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/AffinityGroupJoinDaoImpl.java
@@ -21,21 +21,46 @@ import java.util.List;
 
 import javax.inject.Inject;
 
-
 import org.apache.cloudstack.affinity.AffinityGroup;
 import org.apache.cloudstack.affinity.AffinityGroupResponse;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.context.CallContext;
+import org.apache.cloudstack.dedicated.DedicatedResourceResponse;
 
 import com.cloud.api.ApiResponseHelper;
 import com.cloud.api.query.vo.AffinityGroupJoinVO;
 import com.cloud.utils.db.GenericDaoBase;
 import com.cloud.utils.db.SearchBuilder;
 import com.cloud.utils.db.SearchCriteria;
+import com.cloud.dc.DataCenter;
+import com.cloud.dc.DedicatedResourceVO;
+import com.cloud.dc.DedicatedResources;
+import com.cloud.dc.HostPodVO;
+import com.cloud.dc.dao.ClusterDao;
+import com.cloud.dc.dao.DataCenterDao;
+import com.cloud.dc.dao.DedicatedResourceDao;
+import com.cloud.dc.dao.HostPodDao;
+import com.cloud.host.Host;
+import com.cloud.host.dao.HostDao;
+import com.cloud.org.Cluster;
+import com.cloud.user.AccountManager;
 
 public class AffinityGroupJoinDaoImpl extends GenericDaoBase<AffinityGroupJoinVO, Long> implements AffinityGroupJoinDao {
 
     @Inject
     private ConfigurationDao _configDao;
+    @Inject
+    private DedicatedResourceDao dedicatedResourceDao;
+    @Inject
+    private DataCenterDao dataCenterDao;
+    @Inject
+    private HostPodDao podDao;
+    @Inject
+    private ClusterDao clusterDao;
+    @Inject
+    private HostDao hostDao;
+    @Inject
+    private AccountManager accountManager;
 
     private final SearchBuilder<AffinityGroupJoinVO> agSearch;
 
@@ -64,6 +89,14 @@ public class AffinityGroupJoinDaoImpl extends GenericDaoBase<AffinityGroupJoinVO
 
         ApiResponseHelper.populateOwner(agResponse, vag);
 
+        Long callerId = CallContext.current().getCallingAccountId();
+        boolean isCallerRootAdmin = accountManager.isRootAdmin(callerId);
+        boolean containsDedicatedResources = vag.getType().equals("ExplicitDedication");
+        if (isCallerRootAdmin && containsDedicatedResources) {
+            List<DedicatedResourceVO> dedicatedResources = dedicatedResourceDao.listByAffinityGroupId(vag.getId());
+            this.populateDedicatedResourcesField(dedicatedResources, agResponse);
+        }
+
         // update vm information
         long instanceId = vag.getVmId();
         if (instanceId > 0) {
@@ -76,6 +109,32 @@ public class AffinityGroupJoinDaoImpl extends GenericDaoBase<AffinityGroupJoinVO
         return agResponse;
     }
 
+    private void populateDedicatedResourcesField(List<DedicatedResourceVO> dedicatedResources, AffinityGroupResponse agResponse) {
+        if (dedicatedResources.isEmpty()) {
+            return;
+        }
+
+        for (DedicatedResourceVO resource : dedicatedResources) {
+            DedicatedResourceResponse dedicatedResourceResponse = null;
+
+            if (resource.getDataCenterId() != null) {
+                DataCenter dataCenter = dataCenterDao.findById(resource.getDataCenterId());
+                dedicatedResourceResponse = new DedicatedResourceResponse(dataCenter.getUuid(), dataCenter.getName(), DedicatedResources.Type.Zone);
+            } else if (resource.getPodId() != null) {
+                HostPodVO pod = podDao.findById(resource.getPodId());
+                dedicatedResourceResponse = new DedicatedResourceResponse(pod.getUuid(), pod.getName(), DedicatedResources.Type.Pod);
+            } else if (resource.getClusterId() != null) {
+                Cluster cluster = clusterDao.findById(resource.getClusterId());
+                dedicatedResourceResponse = new DedicatedResourceResponse(cluster.getUuid(), cluster.getName(), DedicatedResources.Type.Cluster);
+            } else if (resource.getHostId() != null) {
+                Host host = hostDao.findById(resource.getHostId());
+                dedicatedResourceResponse = new DedicatedResourceResponse(host.getUuid(), host.getName(), DedicatedResources.Type.Host);
+            }
+
+            agResponse.addDedicatedResource(dedicatedResourceResponse);
+        }
+    }
+
     @Override
     public AffinityGroupResponse setAffinityGroupResponse(AffinityGroupResponse vagData, AffinityGroupJoinVO vag) {
         // update vm information
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 6ca710d4095..7336c038c89 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -673,6 +673,7 @@
 "label.dedicate.zone": "Dedicate zone",
 "label.dedicated": "Dedicated",
 "label.dedicated.vlan.vni.ranges": "Dedicated VLAN/VNI ranges",
+"label.dedicatedresources": "Dedicated resources",
 "label.default": "Default",
 "label.default.use": "Default use",
 "label.default.view": "Default view",
diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json
index 79333c100d3..f02aee747eb 100644
--- a/ui/public/locales/pt_BR.json
+++ b/ui/public/locales/pt_BR.json
@@ -472,6 +472,7 @@
 "label.dedicate.zone": "Zona dedicada",
 "label.dedicated": "Dedicado",
 "label.dedicated.vlan.vni.ranges": "Intervalo(s) de VLAN/VNI dedicados",
+"label.dedicatedresources": "Recursos dedicados",
 "label.default": "Padr\u00e3o",
 "label.default.use": "Uso padr\u00e3o",
 "label.default.view": "Visualiza\u00e7\u00e3o padr\u00e3o",
diff --git a/ui/src/components/view/DetailsTab.vue b/ui/src/components/view/DetailsTab.vue
index 3f6f37aa08b..f5f180c8f19 100644
--- a/ui/src/components/view/DetailsTab.vue
+++ b/ui/src/components/view/DetailsTab.vue
@@ -115,6 +115,15 @@
           <div v-else-if="item === 'payload'" style="white-space: pre-wrap;">
             {{ JSON.stringify(JSON.parse(dataResource[item]), null, 4) || dataResource[item] }}
           </div>
+          <div v-else-if="item === 'dedicatedresources'">
+            <div v-for="(resource, idx) in sortDedicatedResourcesByName(dataResource[item])" :key="idx">
+              <div>
+                <router-link :to="getResourceLink(resource.resourcetype, resource.resourceid)">
+                  {{ resource.resourcename }}
+                </router-link>
+              </div>
+            </div>
+          </div>
           <div v-else>{{ dataResource[item] }}</div>
         </div>
       </a-list-item>
@@ -150,6 +159,7 @@
 import DedicateData from './DedicateData'
 import HostInfo from '@/views/infra/HostInfo'
 import VmwareData from './VmwareData'
+import { genericCompare } from '@/utils/sort'
 
 export default {
   name: 'DetailsTab',
@@ -386,6 +396,16 @@ export default {
     },
     getDetailTitle (detail) {
       return `label.${String(this.detailsTitles[detail]).toLowerCase()}`
+    },
+    getResourceLink (type, id) {
+      return `/${type.toLowerCase()}/${id}`
+    },
+    sortDedicatedResourcesByName (resources) {
+      resources.sort((resource, otherResource) => {
+        return genericCompare(resource.resourcename, otherResource.resourcename)
+      })
+
+      return resources
     }
   }
 }
diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js
index b992229962b..c6d16d6ea78 100644
--- a/ui/src/config/section/compute.js
+++ b/ui/src/config/section/compute.js
@@ -988,7 +988,7 @@ export default {
         }
         return fields
       },
-      details: ['name', 'id', 'description', 'type', 'account', 'domain'],
+      details: ['name', 'id', 'description', 'type', 'account', 'domain', 'dedicatedresources'],
       related: [{
         name: 'vm',
         title: 'label.instances',