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
This commit is contained in:
Bernardo De Marco Gonçalves 2024-09-10 08:12:41 -03:00 committed by GitHub
parent b1f683db6b
commit 6ec3c486dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 163 additions and 19 deletions

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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

View File

@ -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",

View File

@ -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",

View File

@ -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
}
}
}

View File

@ -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',