Skip to content

Commit

Permalink
Enhance the listAffinityGroups API by adding the dedicated resource…
Browse files Browse the repository at this point in the history
…s 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
  • Loading branch information
bernardodemarco authored Sep 10, 2024
1 parent b1f683d commit 6ec3c48
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 19 deletions.
4 changes: 4 additions & 0 deletions api/src/main/java/com/cloud/dc/DedicatedResources.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
import org.apache.cloudstack.api.InternalIdentity;

public interface DedicatedResources extends InfrastructureEntity, InternalIdentity, Identity {
enum Type {
Zone, Pod, Cluster, Host
}

@Override
long getId();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
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;

Expand Down Expand Up @@ -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() {
}

Expand Down Expand Up @@ -171,4 +176,12 @@ public void addVMId(String vmId) {
this.vmIdList.add(vmId);
}

public void addDedicatedResource(DedicatedResourceResponse dedicatedResourceResponse) {
if (this.dedicatedResources == null) {
this.dedicatedResources = new ArrayList<>();
}

this.dedicatedResources.add(dedicatedResourceResponse);
}

}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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;
Expand Down Expand Up @@ -236,7 +237,7 @@ public List<DedicatedResourceVO> dedicateZone(final Long zoneId, final Long doma
@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.");
Expand Down Expand Up @@ -372,10 +373,10 @@ public List<DedicatedResourceVO> dedicatePod(final Long podId, final Long domain
@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 {
Expand Down Expand Up @@ -485,10 +486,10 @@ public List<DedicatedResourceVO> dedicateCluster(final Long clusterId, final Lon
@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 {
Expand Down Expand Up @@ -582,10 +583,10 @@ public List<DedicatedResourceVO> dedicateHost(final Long hostId, final Long doma
@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 {
Expand All @@ -607,7 +608,7 @@ public List<DedicatedResourceVO> doInTransaction(TransactionStatus status) {

}

private AffinityGroup findOrCreateDedicatedAffinityGroup(Long domainId, Long accountId) {
private AffinityGroup findOrCreateDedicatedAffinityGroup(Long domainId, Long accountId, DedicatedResources.Type dedicatedResource) {
if (domainId == null) {
return null;
}
Expand All @@ -624,24 +625,25 @@ private AffinityGroup findOrCreateDedicatedAffinityGroup(Long domainId, Long acc
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,46 @@

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;

Expand Down Expand Up @@ -64,6 +89,14 @@ public AffinityGroupResponse newAffinityGroupResponse(AffinityGroupJoinVO vag) {

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) {
Expand All @@ -76,6 +109,32 @@ public AffinityGroupResponse newAffinityGroupResponse(AffinityGroupJoinVO vag) {
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
Expand Down
1 change: 1 addition & 0 deletions ui/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions ui/public/locales/pt_BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
20 changes: 20 additions & 0 deletions ui/src/components/view/DetailsTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion ui/src/config/section/compute.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit 6ec3c48

Please sign in to comment.