Move subdomains between domains (#7446)

Sometimes users have the need to move resources between domains, for example, in a big company, a department may be moved from one part of the company to another, changing the company's department hierarchy, the easiest way of reflecting this change on the company's cloud environment would be to move subdomains between domains, but currently ACS offers no option to do that.
This PR adds the moveDomain API, which will move domains between subdomains. Furthermore, if the domain that is being moved has any subdomains, those will also be moved, maintaining the current subdomain tree.
This commit is contained in:
João Jandre 2023-12-10 03:48:15 -03:00 committed by GitHub
parent 4bdf35b7b0
commit 12dcf5cc6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 566 additions and 2 deletions

View File

@ -320,6 +320,7 @@ public class EventTypes {
public static final String EVENT_DOMAIN_CREATE = "DOMAIN.CREATE";
public static final String EVENT_DOMAIN_DELETE = "DOMAIN.DELETE";
public static final String EVENT_DOMAIN_UPDATE = "DOMAIN.UPDATE";
public static final String EVENT_DOMAIN_MOVE = "DOMAIN.MOVE";
// Snapshots
public static final String EVENT_SNAPSHOT_COPY = "SNAPSHOT.COPY";
@ -878,6 +879,7 @@ public class EventTypes {
entityEventDetails.put(EVENT_DOMAIN_CREATE, Domain.class);
entityEventDetails.put(EVENT_DOMAIN_DELETE, Domain.class);
entityEventDetails.put(EVENT_DOMAIN_UPDATE, Domain.class);
entityEventDetails.put(EVENT_DOMAIN_MOVE, Domain.class);
// Snapshots
entityEventDetails.put(EVENT_SNAPSHOT_CREATE, Snapshot.class);

View File

@ -20,9 +20,11 @@ import java.util.List;
import org.apache.cloudstack.api.command.admin.domain.ListDomainChildrenCmd;
import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmd;
import org.apache.cloudstack.api.command.admin.domain.MoveDomainCmd;
import com.cloud.domain.Domain;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.utils.Pair;
public interface DomainService {
@ -66,4 +68,5 @@ public interface DomainService {
*/
Domain findDomainByIdOrPath(Long id, String domainPath);
Domain moveDomainAndChildrenToNewParentDomain(MoveDomainCmd cmd) throws ResourceAllocationException;
}

View File

@ -0,0 +1,73 @@
// 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.api.command.admin.domain;
import com.cloud.domain.Domain;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.user.Account;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DomainResponse;
@APICommand(name = "moveDomain", description = "Moves a domain and its children to a new parent domain.", since = "4.19.0.0", responseObject = DomainResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin})
public class MoveDomainCmd extends BaseCmd {
private static final String APINAME = "moveDomain";
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = true, entityType = DomainResponse.class, description = "The ID of the domain to be moved.")
private Long domainId;
@Parameter(name = ApiConstants.PARENT_DOMAIN_ID, type = CommandType.UUID, required = true, entityType = DomainResponse.class,
description = "The ID of the new parent domain of the domain to be moved.")
private Long parentDomainId;
public Long getDomainId() {
return domainId;
}
public Long getParentDomainId() {
return parentDomainId;
}
@Override
public String getCommandName() {
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
}
@Override
public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM;
}
@Override
public void execute() throws ResourceAllocationException {
Domain domain = _domainService.moveDomainAndChildrenToNewParentDomain(this);
if (domain != null) {
DomainResponse response = _responseGenerator.createDomainResponse(domain);
response.setResponseName(getCommandName());
this.setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to move the domain.");
}
}
}

View File

@ -17,6 +17,7 @@
package com.cloud.network.dao;
import java.util.List;
import java.util.Map;
import com.cloud.utils.db.GenericDao;
@ -26,4 +27,6 @@ public interface NetworkDomainDao extends GenericDao<NetworkDomainVO, Long> {
NetworkDomainVO getDomainNetworkMapByNetworkId(long networkId);
List<Long> listNetworkIdsByDomain(long domainId);
Map<Long, List<String>> listDomainsOfSharedNetworksUsedByDomainPath(String domainPath);
}

View File

@ -16,10 +16,17 @@
// under the License.
package com.cloud.network.dao;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.cloud.utils.db.TransactionLegacy;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import com.cloud.utils.db.DB;
@ -31,9 +38,23 @@ import com.cloud.utils.db.SearchCriteria.Op;
@Component
@DB()
public class NetworkDomainDaoImpl extends GenericDaoBase<NetworkDomainVO, Long> implements NetworkDomainDao {
public static Logger logger = Logger.getLogger(NetworkDomainDaoImpl.class.getName());
final SearchBuilder<NetworkDomainVO> AllFieldsSearch;
final SearchBuilder<NetworkDomainVO> DomainsSearch;
private static final String LIST_DOMAINS_OF_SHARED_NETWORKS_USED_BY_DOMAIN_PATH = "SELECT shared_nw.domain_id, \n" +
"GROUP_CONCAT('VM:', vm.uuid, ' | NW:' , network.uuid) \n" +
"FROM cloud.domain_network_ref AS shared_nw\n" +
"INNER JOIN cloud.nics AS nic ON (nic.network_id = shared_nw.network_id AND nic.removed IS NULL)\n" +
"INNER JOIN cloud.vm_instance AS vm ON (vm.id = nic.instance_id)\n" +
"INNER JOIN cloud.domain AS domain ON (domain.id = vm.domain_id)\n" +
"INNER JOIN cloud.domain AS domain_sn ON (domain_sn.id = shared_nw.domain_id)\n" +
"INNER JOIN cloud.networks AS network ON (shared_nw.network_id = network.id)\n" +
"WHERE shared_nw.subdomain_access = 1\n" +
"AND domain.path LIKE ?\n" +
"AND domain_sn.path NOT LIKE ?\n" +
"GROUP BY shared_nw.network_id";
protected NetworkDomainDaoImpl() {
super();
@ -71,4 +92,37 @@ public class NetworkDomainDaoImpl extends GenericDaoBase<NetworkDomainVO, Long>
}
return networkIdsToReturn;
}
@Override
public Map<Long, List<String>> listDomainsOfSharedNetworksUsedByDomainPath(String domainPath) {
logger.debug(String.format("Retrieving the domains of the shared networks with subdomain access used by domain with path [%s].", domainPath));
TransactionLegacy txn = TransactionLegacy.currentTxn();
try (PreparedStatement pstmt = txn.prepareStatement(LIST_DOMAINS_OF_SHARED_NETWORKS_USED_BY_DOMAIN_PATH)) {
Map<Long, List<String>> domainsOfSharedNetworksUsedByDomainPath = new HashMap<>();
String domainSearch = domainPath.concat("%");
pstmt.setString(1, domainSearch);
pstmt.setString(2, domainSearch);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
Long domainId = rs.getLong(1);
List<String> vmUuidsAndNetworkUuids = Arrays.asList(rs.getString(2).split(","));
domainsOfSharedNetworksUsedByDomainPath.put(domainId, vmUuidsAndNetworkUuids);
}
}
return domainsOfSharedNetworksUsedByDomainPath;
} catch (SQLException e) {
logger.error(String.format("Failed to retrieve the domains of the shared networks with subdomain access used by domain with path [%s] due to [%s]. Returning an empty "
+ "list of domains.", domainPath, e.getMessage()));
logger.debug(String.format("Failed to retrieve the domains of the shared networks with subdomain access used by domain with path [%s]. Returning an empty "
+ "list of domains.", domainPath), e);
return new HashMap<>();
}
}
}

View File

@ -17,6 +17,7 @@
package org.apache.cloudstack.affinity.dao;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
@ -28,4 +29,6 @@ public interface AffinityGroupDomainMapDao extends GenericDao<AffinityGroupDomai
List<AffinityGroupDomainMapVO> listByDomain(Object... domainId);
Map<Long, List<String>> listDomainsOfAffinityGroupsUsedByDomainPath(String domainPath);
}

View File

@ -16,23 +16,46 @@
// under the License.
package org.apache.cloudstack.affinity.dao;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
import org.apache.log4j.Logger;
import com.cloud.network.dao.NetworkDomainDaoImpl;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.SearchCriteria.Op;
import com.cloud.utils.db.TransactionLegacy;
public class AffinityGroupDomainMapDaoImpl extends GenericDaoBase<AffinityGroupDomainMapVO, Long> implements AffinityGroupDomainMapDao {
public static Logger logger = Logger.getLogger(NetworkDomainDaoImpl.class.getName());
private SearchBuilder<AffinityGroupDomainMapVO> ListByAffinityGroup;
private SearchBuilder<AffinityGroupDomainMapVO> DomainsSearch;
private static final String LIST_DOMAINS_WITH_AFFINITY_GROUPS_WITH_SUBDOMAIN_ACCESS_USED_BY_DOMAIN_PATH = "SELECT affinity_group_domain_map.domain_id, \n" +
"GROUP_CONCAT('VM:', vm.uuid, ' | AG:' , affinity_group.uuid) \n" +
"FROM cloud.affinity_group_domain_map AS affinity_group_domain_map\n" +
"INNER JOIN cloud.affinity_group_vm_map AS affinity_group_vm_map ON (cloud.affinity_group_domain_map.affinity_group_id = affinity_group_vm_map.affinity_group_id)\n" +
"INNER JOIN cloud.vm_instance AS vm ON (vm.id = affinity_group_vm_map.instance_id)\n" +
"INNER JOIN cloud.domain AS domain ON (domain.id = vm.domain_id)\n" +
"INNER JOIN cloud.domain AS domain_sn ON (domain_sn.id = affinity_group_domain_map.domain_id)\n" +
"INNER JOIN cloud.affinity_group AS affinity_group ON (affinity_group.id = affinity_group_domain_map.affinity_group_id)\n" +
"WHERE affinity_group_domain_map.subdomain_access = 1\n" +
"AND domain.path LIKE ?\n" +
"AND domain_sn.path NOT LIKE ?\n" +
"GROUP BY affinity_group.id";
public AffinityGroupDomainMapDaoImpl() {
}
@ -62,4 +85,38 @@ public class AffinityGroupDomainMapDaoImpl extends GenericDaoBase<AffinityGroupD
return listBy(sc);
}
@Override
public Map<Long, List<String>> listDomainsOfAffinityGroupsUsedByDomainPath(String domainPath) {
logger.debug(String.format("Retrieving the domains of the affinity groups with subdomain access used by domain with path [%s].", domainPath));
TransactionLegacy txn = TransactionLegacy.currentTxn();
try (PreparedStatement pstmt = txn.prepareStatement(LIST_DOMAINS_WITH_AFFINITY_GROUPS_WITH_SUBDOMAIN_ACCESS_USED_BY_DOMAIN_PATH)) {
Map<Long, List<String>> domainsOfAffinityGroupsUsedByDomainPath = new HashMap<>();
String domainSearch = domainPath.concat("%");
pstmt.setString(1, domainSearch);
pstmt.setString(2, domainSearch);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
Long domainId = rs.getLong(1);
List<String> vmUuidsAndAffinityGroupUuids = Arrays.asList(rs.getString(2).split(","));
domainsOfAffinityGroupsUsedByDomainPath.put(domainId, vmUuidsAndAffinityGroupUuids);
}
}
return domainsOfAffinityGroupsUsedByDomainPath;
} catch (SQLException e) {
logger.error(String.format("Failed to retrieve the domains of the affinity groups with subdomain access used by domain with path [%s] due to [%s]. Returning an " +
"empty list of domains.", domainPath, e.getMessage()));
logger.debug(String.format("Failed to retrieve the domains of the affinity groups with subdomain access used by domain with path [%s]. Returning an empty "
+ "list of domains.", domainPath), e);
return new HashMap<>();
}
}
}

View File

@ -18,6 +18,7 @@
package com.cloud.api.query.dao;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.api.response.NetworkOfferingResponse;
@ -52,4 +53,6 @@ public interface NetworkOfferingJoinDao extends GenericDao<NetworkOfferingJoinVO
NetworkOfferingResponse newNetworkOfferingResponse(NetworkOffering nof);
NetworkOfferingJoinVO newNetworkOfferingView(NetworkOffering nof);
Map<Long, List<String>> listDomainsOfNetworkOfferingsUsedByDomainPath(String domainPath);
}

View File

@ -17,8 +17,15 @@
package com.cloud.api.query.dao;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.cloud.utils.db.TransactionLegacy;
import org.apache.commons.lang3.StringUtils;
import org.apache.cloudstack.api.response.NetworkOfferingResponse;
import org.apache.log4j.Logger;
@ -44,6 +51,17 @@ public class NetworkOfferingJoinDaoImpl extends GenericDaoBase<NetworkOfferingJo
_count = "select count(distinct id) from network_offering_view WHERE ";
}
private static final String LIST_DOMAINS_OF_NETWORK_OFFERINGS_USED_BY_DOMAIN_PATH = "SELECT nov.domain_id, \n" +
" GROUP_CONCAT('Network:', net.uuid) \n" +
" FROM cloud.network_offering_view AS nov\n" +
" INNER JOIN cloud.networks AS net ON (net.network_offering_id = nov.id) \n" +
" INNER JOIN cloud.domain AS domain ON (domain.id = net.domain_id) \n" +
" INNER JOIN cloud.domain AS domain_so ON (domain_so.id = nov.domain_id) \n" +
" WHERE domain.path LIKE ? \n" +
" AND domain_so.path NOT LIKE ? \n" +
" AND net.removed IS NULL \n" +
" GROUP BY nov.id";
@Override
public List<NetworkOfferingJoinVO> findByDomainId(long domainId, Boolean includeAllDomainOffering) {
SearchBuilder<NetworkOfferingJoinVO> sb = createSearchBuilder();
@ -120,4 +138,39 @@ public class NetworkOfferingJoinDaoImpl extends GenericDaoBase<NetworkOfferingJo
assert offerings != null && offerings.size() == 1 : "No network offering found for offering id " + offering.getId();
return offerings.get(0);
}
@Override
public Map<Long, List<String>> listDomainsOfNetworkOfferingsUsedByDomainPath(String domainPath) {
s_logger.debug(String.format("Retrieving the domains of the network offerings used by domain with path [%s].", domainPath));
TransactionLegacy txn = TransactionLegacy.currentTxn();
try (PreparedStatement pstmt = txn.prepareStatement(LIST_DOMAINS_OF_NETWORK_OFFERINGS_USED_BY_DOMAIN_PATH)) {
Map<Long, List<String>> domainsOfNetworkOfferingsUsedByDomainPath = new HashMap<>();
String domainSearch = domainPath.concat("%");
pstmt.setString(1, domainSearch);
pstmt.setString(2, domainSearch);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
Long domainId = rs.getLong(1);
List<String> networkUuids = Arrays.asList(rs.getString(2).split(","));
domainsOfNetworkOfferingsUsedByDomainPath.put(domainId, networkUuids);
}
}
return domainsOfNetworkOfferingsUsedByDomainPath;
} catch (SQLException e) {
s_logger.error(String.format("Failed to retrieve the domains of the network offerings used by domain with path [%s] due to [%s]. Returning an empty "
+ "list of domains.", domainPath, e.getMessage()));
s_logger.debug(String.format("Failed to retrieve the domains of the network offerings used by domain with path [%s]. Returning an empty " +
"list of domains.", domainPath), e);
return new HashMap<>();
}
}
}

View File

@ -17,6 +17,7 @@
package com.cloud.api.query.dao;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
@ -31,4 +32,6 @@ public interface ServiceOfferingJoinDao extends GenericDao<ServiceOfferingJoinVO
ServiceOfferingResponse newServiceOfferingResponse(ServiceOfferingJoinVO offering);
ServiceOfferingJoinVO newServiceOfferingView(ServiceOffering offering);
Map<Long, List<String>> listDomainsOfServiceOfferingsUsedByDomainPath(String domainPath);
}

View File

@ -16,12 +16,18 @@
// under the License.
package com.cloud.api.query.dao;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.cloud.dc.VsphereStoragePolicyVO;
import com.cloud.dc.dao.VsphereStoragePolicyDao;
import com.cloud.user.AccountManager;
import com.cloud.utils.db.TransactionLegacy;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import com.cloud.storage.DiskOfferingVO;
@ -61,6 +67,18 @@ public class ServiceOfferingJoinDaoImpl extends GenericDaoBase<ServiceOfferingJo
*/
private static final long GB_TO_BYTES = 1073741824;
private static final String LIST_DOMAINS_OF_SERVICE_OFFERINGS_USED_BY_DOMAIN_PATH = "SELECT sov.domain_id, \n" +
" GROUP_CONCAT('Offering:', vm.uuid) \n" +
" FROM cloud.service_offering_view AS sov\n" +
" INNER JOIN cloud.vm_instance AS vm ON (vm.service_offering_id = sov.id) \n" +
" INNER JOIN cloud.domain AS domain ON (domain.id = vm.domain_id) \n" +
" INNER JOIN cloud.domain AS domain_so ON (domain_so.id = sov.domain_id) \n" +
" WHERE domain.path LIKE ? \n" +
" AND domain_so.path NOT LIKE ? \n " +
" AND vm.removed IS NULL \n" +
" GROUP BY sov.id";
protected ServiceOfferingJoinDaoImpl() {
sofIdSearch = createSearchBuilder();
@ -165,4 +183,38 @@ public class ServiceOfferingJoinDaoImpl extends GenericDaoBase<ServiceOfferingJo
assert offerings != null && offerings.size() == 1 : "No service offering found for offering id " + offering.getId();
return offerings.get(0);
}
@Override
public Map<Long, List<String>> listDomainsOfServiceOfferingsUsedByDomainPath(String domainPath) {
s_logger.debug(String.format("Retrieving the domains of the service offerings used by domain with path [%s].", domainPath));
TransactionLegacy txn = TransactionLegacy.currentTxn();
try (PreparedStatement pstmt = txn.prepareStatement(LIST_DOMAINS_OF_SERVICE_OFFERINGS_USED_BY_DOMAIN_PATH)) {
Map<Long, List<String>> domainsOfServiceOfferingsUsedByDomainPath = new HashMap<>();
String domainSearch = domainPath.concat("%");
pstmt.setString(1, domainSearch);
pstmt.setString(2, domainSearch);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
Long domainId = rs.getLong(1);
List<String> vmUuids = Arrays.asList(rs.getString(2).split(","));
domainsOfServiceOfferingsUsedByDomainPath.put(domainId, vmUuids);
}
}
return domainsOfServiceOfferingsUsedByDomainPath;
} catch (SQLException e) {
s_logger.error(String.format("Failed to retrieve the domains of the service offerings used by domain with path [%s] due to [%s]. Returning an empty "
+ "list of domains.", domainPath, e.getMessage()));
s_logger.debug(String.format("Failed to retrieve the domains of the service offerings used by domain with path [%s]. Returning an empty "
+ "list of domains.", domainPath), e);
return new HashMap<>();
}
}
}

View File

@ -17,6 +17,7 @@
package com.cloud.dc.dao;
import java.util.List;
import java.util.Map;
import com.cloud.dc.DedicatedResourceVO;
import com.cloud.utils.Pair;
@ -58,4 +59,6 @@ public interface DedicatedResourceDao extends GenericDao<DedicatedResourceVO, Lo
List<Long> findHostsByCluster(Long clusterId);
List<Long> findHostsByZone(Long zoneId);
Map<Long, List<String>> listDomainsOfDedicatedResourcesUsedByDomainPath(String domainPath);
}

View File

@ -16,7 +16,12 @@
// under the License.
package com.cloud.dc.dao;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -26,6 +31,7 @@ import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.utils.db.Filter;
import org.springframework.stereotype.Component;
import org.apache.log4j.Logger;
import com.cloud.dc.DedicatedResourceVO;
import com.cloud.utils.Pair;
@ -45,6 +51,8 @@ import javax.inject.Inject;
@DB
public class DedicatedResourceDaoImpl extends GenericDaoBase<DedicatedResourceVO, Long> implements DedicatedResourceDao {
public static Logger LOGGER = Logger.getLogger(DedicatedResourceDaoImpl.class.getName());
@Inject
protected HostDao hostDao;
@ -79,6 +87,17 @@ public class DedicatedResourceDaoImpl extends GenericDaoBase<DedicatedResourceVO
protected SearchBuilder<DedicatedResourceVO> ListHostsByCluster;
protected SearchBuilder<DedicatedResourceVO> ListHostsByZone;
private static final String LIST_DOMAINS_OF_DEDICATED_RESOURCES_USED_BY_DOMAIN_PATH = "SELECT dr.domain_id, \n" +
" GROUP_CONCAT('VM:', vm.uuid) \n" +
" FROM cloud.dedicated_resources AS dr\n" +
" INNER JOIN cloud.vm_instance AS vm ON (vm.domain_id = dr.domain_id)\n" +
" INNER JOIN cloud.domain AS domain ON (domain.id = vm.domain_id) \n" +
" INNER JOIN cloud.domain AS domain_dr ON (domain.id = dr.domain_id) \n" +
" WHERE domain.path LIKE ? \n" +
" AND domain_dr.path NOT LIKE ? \n " +
" AND vm.removed IS NULL \n" +
" GROUP BY dr.id";
protected DedicatedResourceDaoImpl() {
PodSearch = createSearchBuilder();
PodSearch.and("podId", PodSearch.entity().getPodId(), SearchCriteria.Op.EQ);
@ -428,4 +447,38 @@ public class DedicatedResourceDaoImpl extends GenericDaoBase<DedicatedResourceVO
}
return hosts;
}
@Override
public Map<Long, List<String>> listDomainsOfDedicatedResourcesUsedByDomainPath(String domainPath) {
LOGGER.debug(String.format("Retrieving the domains of the dedicated resources used by domain with path [%s].", domainPath));
TransactionLegacy txn = TransactionLegacy.currentTxn();
try (PreparedStatement pstmt = txn.prepareStatement(LIST_DOMAINS_OF_DEDICATED_RESOURCES_USED_BY_DOMAIN_PATH)) {
Map<Long, List<String>> domainsOfDedicatedResourcesUsedByDomainPath = new HashMap<>();
String domainSearch = domainPath.concat("%");
pstmt.setString(1, domainSearch);
pstmt.setString(2, domainSearch);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
Long domainId = rs.getLong(1);
List<String> vmUuids = Arrays.asList(rs.getString(2).split(","));
domainsOfDedicatedResourcesUsedByDomainPath.put(domainId, vmUuids);
}
}
return domainsOfDedicatedResourcesUsedByDomainPath;
} catch (SQLException e) {
LOGGER.error(String.format("Failed to retrieve the domains of the dedicated resources used by domain with path [%s] due to [%s]. Returning an empty "
+ "list of domains.", domainPath, e.getMessage()));
LOGGER.debug(String.format("Failed to retrieve the domains of the dedicated resources used by domain with path [%s]. Returning an empty "
+ "list of domains.", domainPath), e);
return new HashMap<>();
}
}
}

View File

@ -88,6 +88,7 @@ import org.apache.cloudstack.api.command.admin.domain.DeleteDomainCmd;
import org.apache.cloudstack.api.command.admin.domain.ListDomainChildrenCmd;
import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmd;
import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmdByAdmin;
import org.apache.cloudstack.api.command.admin.domain.MoveDomainCmd;
import org.apache.cloudstack.api.command.admin.domain.UpdateDomainCmd;
import org.apache.cloudstack.api.command.admin.guest.AddGuestOsCmd;
import org.apache.cloudstack.api.command.admin.guest.AddGuestOsMappingCmd;
@ -3406,6 +3407,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(ListDomainsCmd.class);
cmdList.add(ListDomainsCmdByAdmin.class);
cmdList.add(UpdateDomainCmd.class);
cmdList.add(MoveDomainCmd.class);
cmdList.add(AddHostCmd.class);
cmdList.add(AddSecondaryStorageCmd.class);
cmdList.add(CancelMaintenanceCmd.class);

View File

@ -17,10 +17,14 @@
package com.cloud.user;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.inject.Inject;
@ -28,16 +32,20 @@ import com.cloud.api.query.dao.NetworkOfferingJoinDao;
import com.cloud.api.query.dao.VpcOfferingJoinDao;
import com.cloud.api.query.vo.NetworkOfferingJoinVO;
import com.cloud.api.query.vo.VpcOfferingJoinVO;
import com.cloud.configuration.Resource;
import com.cloud.domain.dao.DomainDetailsDao;
import com.cloud.network.vpc.dao.VpcOfferingDao;
import com.cloud.network.vpc.dao.VpcOfferingDetailsDao;
import com.cloud.offerings.dao.NetworkOfferingDao;
import com.cloud.offerings.dao.NetworkOfferingDetailsDao;
import com.cloud.exception.ResourceAllocationException;
import org.apache.cloudstack.affinity.dao.AffinityGroupDomainMapDao;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.command.admin.domain.ListDomainChildrenCmd;
import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmd;
import org.apache.cloudstack.api.command.admin.domain.MoveDomainCmd;
import org.apache.cloudstack.api.command.admin.domain.UpdateDomainCmd;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
@ -151,6 +159,10 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom
private DomainDetailsDao _domainDetailsDao;
@Inject
private AnnotationDao annotationDao;
@Inject
private ResourceLimitService resourceLimitService;
@Inject
private AffinityGroupDomainMapDao affinityGroupDomainMapDao;
@Inject
MessageBus _messageBus;
@ -272,7 +284,7 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom
List<DomainVO> domains = _domainDao.search(sc, null);
if (!domains.isEmpty()) {
throw new InvalidParameterValueException("Domain with name " + name + " already exists for the parent id=" + parentId);
throw new InvalidParameterValueException(String.format("Domain with name [%s] already exists for the parent with ID [%s].", name, parentId));
}
}
@ -910,4 +922,192 @@ public class DomainManagerImpl extends ManagerBase implements DomainManager, Dom
}
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_DOMAIN_MOVE, eventDescription = "moving Domain")
@DB
public Domain moveDomainAndChildrenToNewParentDomain(MoveDomainCmd cmd) throws ResourceAllocationException {
Long idOfDomainToBeMoved = cmd.getDomainId();
Long idOfNewParentDomain = cmd.getParentDomainId();
if (idOfDomainToBeMoved == Domain.ROOT_DOMAIN) {
throw new InvalidParameterValueException("The domain to be moved cannot be the ROOT domain.");
}
if (idOfDomainToBeMoved.equals(idOfNewParentDomain)) {
throw new InvalidParameterValueException("The domain to be moved and the new parent domain cannot be the same.");
}
DomainVO domainToBeMoved = returnDomainIfExistsAndIsActive(idOfDomainToBeMoved);
s_logger.debug(String.format("Found the domain [%s] as the domain to be moved.", domainToBeMoved));
DomainVO newParentDomain = returnDomainIfExistsAndIsActive(idOfNewParentDomain);
s_logger.debug(String.format("Found the domain [%s] as the new parent domain of the domain to be moved [%s].", newParentDomain, domainToBeMoved));
Account caller = getCaller();
_accountMgr.checkAccess(caller, domainToBeMoved);
_accountMgr.checkAccess(caller, newParentDomain);
Long idOfCurrentParentOfDomainToBeMoved = domainToBeMoved.getParent();
if (idOfCurrentParentOfDomainToBeMoved.equals(idOfNewParentDomain)) {
throw new InvalidParameterValueException(String.format("The current parent domain of the domain to be moved is equal to the new parent domain [%s].", newParentDomain));
}
if (newParentDomain.getPath().startsWith(domainToBeMoved.getPath())) {
throw new InvalidParameterValueException("The new parent domain of the domain cannot be one of its children.");
}
validateUniqueDomainName(domainToBeMoved.getName(), idOfNewParentDomain);
validateNewParentDomainResourceLimits(domainToBeMoved, newParentDomain);
String currentPathOfDomainToBeMoved = domainToBeMoved.getPath();
String domainToBeMovedName = domainToBeMoved.getName().concat("/");
String newPathOfDomainToBeMoved = newParentDomain.getPath().concat(domainToBeMovedName);
validateNewParentDomainCanAccessAllDomainToBeMovedResources(domainToBeMoved, newParentDomain, currentPathOfDomainToBeMoved, newPathOfDomainToBeMoved);
DomainVO parentOfDomainToBeMoved = _domainDao.findById(idOfCurrentParentOfDomainToBeMoved);
Transaction.execute(new TransactionCallbackNoReturn() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
s_logger.debug(String.format("Setting the new parent of the domain to be moved [%s] as [%s].", domainToBeMoved, newParentDomain));
domainToBeMoved.setParent(idOfNewParentDomain);
updateDomainAndChildrenPathAndLevel(domainToBeMoved, newParentDomain, currentPathOfDomainToBeMoved, newPathOfDomainToBeMoved);
updateResourceCounts(idOfCurrentParentOfDomainToBeMoved, idOfNewParentDomain);
updateChildCounts(parentOfDomainToBeMoved, newParentDomain);
}
});
return domainToBeMoved;
}
protected void validateNewParentDomainResourceLimits(DomainVO domainToBeMoved, DomainVO newParentDomain) throws ResourceAllocationException {
long domainToBeMovedId = domainToBeMoved.getId();
long newParentDomainId = newParentDomain.getId();
for (Resource.ResourceType resourceType : Resource.ResourceType.values()) {
long currentDomainResourceCount = _resourceCountDao.getResourceCount(domainToBeMovedId, ResourceOwnerType.Domain, resourceType);
long newParentDomainResourceCount = _resourceCountDao.getResourceCount(newParentDomainId, ResourceOwnerType.Domain, resourceType);
long newParentDomainResourceLimit = resourceLimitService.findCorrectResourceLimitForDomain(newParentDomain, resourceType);
if (newParentDomainResourceLimit == Resource.RESOURCE_UNLIMITED) {
return;
}
if (currentDomainResourceCount + newParentDomainResourceCount > newParentDomainResourceLimit) {
String message = String.format("Cannot move domain [%s] to parent domain [%s] as maximum domain resource limit of type [%s] would be exceeded. The current resource "
+ "count for domain [%s] is [%s], the resource count for the new parent domain [%s] is [%s], and the limit is [%s].", domainToBeMoved.getUuid(),
newParentDomain.getUuid(), resourceType, domainToBeMoved.getUuid(), currentDomainResourceCount, newParentDomain.getUuid(), newParentDomainResourceCount,
newParentDomainResourceLimit);
s_logger.error(message);
throw new ResourceAllocationException(message, resourceType);
}
}
}
protected void validateNewParentDomainCanAccessAllDomainToBeMovedResources(DomainVO domainToBeMoved, DomainVO newParentDomain, String currentPathOfDomainToBeMoved,
String newPathOfDomainToBeMoved) {
Map<Long, List<String>> idsOfDomainsWithNetworksUsedByDomainToBeMoved = _networkDomainDao.listDomainsOfSharedNetworksUsedByDomainPath(currentPathOfDomainToBeMoved);
validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(newPathOfDomainToBeMoved, domainToBeMoved, newParentDomain, idsOfDomainsWithNetworksUsedByDomainToBeMoved, "Networks");
Map<Long, List<String>> idsOfDomainsOfAffinityGroupsUsedByDomainToBeMoved =
affinityGroupDomainMapDao.listDomainsOfAffinityGroupsUsedByDomainPath(currentPathOfDomainToBeMoved);
validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(newPathOfDomainToBeMoved, domainToBeMoved, newParentDomain, idsOfDomainsOfAffinityGroupsUsedByDomainToBeMoved, "Affinity groups");
Map<Long, List<String>> idsOfDomainsOfServiceOfferingsUsedByDomainToBeMoved =
serviceOfferingJoinDao.listDomainsOfServiceOfferingsUsedByDomainPath(currentPathOfDomainToBeMoved);
validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(newPathOfDomainToBeMoved, domainToBeMoved, newParentDomain, idsOfDomainsOfServiceOfferingsUsedByDomainToBeMoved, "Service offerings");
Map<Long, List<String>> idsOfDomainsOfNetworkOfferingsUsedByDomainToBeMoved =
networkOfferingJoinDao.listDomainsOfNetworkOfferingsUsedByDomainPath(currentPathOfDomainToBeMoved);
validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(newPathOfDomainToBeMoved, domainToBeMoved, newParentDomain, idsOfDomainsOfNetworkOfferingsUsedByDomainToBeMoved, "Network offerings");
Map<Long, List<String>> idsOfDomainsOfDedicatedResourcesUsedByDomainToBeMoved =
_dedicatedDao.listDomainsOfDedicatedResourcesUsedByDomainPath(currentPathOfDomainToBeMoved);
validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(newPathOfDomainToBeMoved, domainToBeMoved, newParentDomain, idsOfDomainsOfDedicatedResourcesUsedByDomainToBeMoved, "Dedicated resources");
}
protected void validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(String newPathOfDomainToBeMoved, DomainVO domainToBeMoved, DomainVO newParentDomain,
Map<Long, List<String>> idsOfDomainsWithResourcesUsedByDomainToBeMoved, String resourceToLog) {
Map<DomainVO, List<String>> domainsOfResourcesInaccessibleToNewParentDomain = new HashMap<>();
for (Map.Entry<Long, List<String>> entry : idsOfDomainsWithResourcesUsedByDomainToBeMoved.entrySet()) {
DomainVO domainWithResourceUsedByDomainToBeMoved = _domainDao.findById(entry.getKey());
Pattern pattern = Pattern.compile(domainWithResourceUsedByDomainToBeMoved.getPath().replace("/", "\\/").concat(".*"));
Matcher matcher = pattern.matcher(newPathOfDomainToBeMoved);
if (!matcher.matches()) {
domainsOfResourcesInaccessibleToNewParentDomain.put(domainWithResourceUsedByDomainToBeMoved, entry.getValue());
}
}
if (!domainsOfResourcesInaccessibleToNewParentDomain.isEmpty()) {
s_logger.error(String.format("The new parent domain [%s] does not have access to domains [%s] used by [%s] in the domain to be moved [%s].",
newParentDomain, domainsOfResourcesInaccessibleToNewParentDomain.keySet(), domainsOfResourcesInaccessibleToNewParentDomain.values(), domainToBeMoved));
throw new InvalidParameterValueException(String.format("New parent domain [%s] does not have access to [%s] used by domain [%s], therefore, domain [%s] cannot be moved.",
newParentDomain, resourceToLog, domainToBeMoved, domainToBeMoved));
}
}
protected DomainVO returnDomainIfExistsAndIsActive(Long idOfDomain) {
s_logger.debug(String.format("Checking if domain with ID [%s] exists and is active.", idOfDomain));
DomainVO domain = _domainDao.findById(idOfDomain);
if (domain == null) {
throw new InvalidParameterValueException(String.format("Unable to find a domain with the specified ID [%s].", idOfDomain));
} else if (domain.getState().equals(Domain.State.Inactive)) {
throw new InvalidParameterValueException(String.format("Unable to use the domain [%s] as it is in state [%s].", domain, Domain.State.Inactive));
}
return domain;
}
protected void updateDomainAndChildrenPathAndLevel(DomainVO domainToBeMoved, DomainVO newParentDomain, String oldPath, String newPath) {
Integer oldRootLevel = domainToBeMoved.getLevel();
Integer newLevel = newParentDomain.getLevel() + 1;
updateDomainPathAndLevel(domainToBeMoved, oldPath, newPath, oldRootLevel, newLevel);
List<DomainVO> childrenDomain = _domainDao.findAllChildren(oldPath, domainToBeMoved.getId());
for (DomainVO childDomain : childrenDomain) {
updateDomainPathAndLevel(childDomain, oldPath, newPath, oldRootLevel, newLevel);
}
}
protected void updateDomainPathAndLevel(DomainVO domain, String oldPath, String newPath, Integer oldRootLevel, Integer newLevel) {
String finalPath = StringUtils.replaceOnce(domain.getPath(), oldPath, newPath);
domain.setPath(finalPath);
Integer currentLevel = domain.getLevel();
int finalLevel = newLevel + currentLevel - oldRootLevel;
domain.setLevel(finalLevel);
s_logger.debug(String.format("Updating the path to [%s] and the level to [%s] of the domain [%s].", finalPath, finalLevel, domain));
_domainDao.update(domain.getId(), domain);
}
protected void updateResourceCounts(Long idOfOldParentDomain, Long idOfNewParentDomain) {
s_logger.debug(String.format("Updating the resource counts of the old parent domain [%s] and of the new parent domain [%s].", idOfOldParentDomain, idOfNewParentDomain));
resourceLimitService.recalculateResourceCount(null, idOfOldParentDomain, null);
resourceLimitService.recalculateResourceCount(null, idOfNewParentDomain, null);
}
protected void updateChildCounts(DomainVO oldParentDomain, DomainVO newParentDomain) {
int finalOldParentChildCount = oldParentDomain.getChildCount() - 1;
oldParentDomain.setChildCount(finalOldParentChildCount);
oldParentDomain.setNextChildSeq(finalOldParentChildCount + 1);
s_logger.debug(String.format("Updating the child count of the old parent domain [%s] to [%s].", oldParentDomain, finalOldParentChildCount));
_domainDao.update(oldParentDomain.getId(), oldParentDomain);
int finalNewParentChildCount = newParentDomain.getChildCount() + 1;
newParentDomain.setChildCount(finalNewParentChildCount);
newParentDomain.setNextChildSeq(finalNewParentChildCount + 1);
s_logger.debug(String.format("Updating the child count of the new parent domain [%s] to [%s].", newParentDomain, finalNewParentChildCount));
_domainDao.update(newParentDomain.getId(), newParentDomain);
}
}