diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 5fce169ffed..67fed5500ee 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -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); diff --git a/api/src/main/java/com/cloud/user/DomainService.java b/api/src/main/java/com/cloud/user/DomainService.java index 3ccfcbcea4c..06109cf5ff7 100644 --- a/api/src/main/java/com/cloud/user/DomainService.java +++ b/api/src/main/java/com/cloud/user/DomainService.java @@ -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; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/MoveDomainCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/MoveDomainCmd.java new file mode 100644 index 00000000000..586345b2de7 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/MoveDomainCmd.java @@ -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."); + } + } +} diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDao.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDao.java index f4d0ad71b8c..43d4d25305e 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDao.java @@ -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 getDomainNetworkMapByNetworkId(long networkId); List listNetworkIdsByDomain(long domainId); + + Map> listDomainsOfSharedNetworksUsedByDomainPath(String domainPath); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDaoImpl.java index 188f306cecc..ce86a8636a1 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDaoImpl.java @@ -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 implements NetworkDomainDao { + public static Logger logger = Logger.getLogger(NetworkDomainDaoImpl.class.getName()); final SearchBuilder AllFieldsSearch; final SearchBuilder 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 } return networkIdsToReturn; } + + @Override + public Map> 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> 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 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<>(); + } + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDao.java b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDao.java index 07be976f202..27040c2f54e 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDao.java @@ -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 listByDomain(Object... domainId); + Map> listDomainsOfAffinityGroupsUsedByDomainPath(String domainPath); + } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDaoImpl.java index 5ecf63d6a6c..1dd22df46d7 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDaoImpl.java @@ -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 implements AffinityGroupDomainMapDao { + public static Logger logger = Logger.getLogger(NetworkDomainDaoImpl.class.getName()); private SearchBuilder ListByAffinityGroup; private SearchBuilder 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> 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> 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 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<>(); + } + } + } diff --git a/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDao.java index 767b9acf5d4..f5c58a2ec24 100644 --- a/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDao.java +++ b/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDao.java @@ -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> listDomainsOfNetworkOfferingsUsedByDomainPath(String domainPath); } diff --git a/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDaoImpl.java index 474409a976c..d50f1610e69 100644 --- a/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDaoImpl.java @@ -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 findByDomainId(long domainId, Boolean includeAllDomainOffering) { SearchBuilder sb = createSearchBuilder(); @@ -120,4 +138,39 @@ public class NetworkOfferingJoinDaoImpl extends GenericDaoBase> 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> 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 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<>(); + } + } } diff --git a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDao.java b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDao.java index 94cc943a812..973e4017529 100644 --- a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDao.java +++ b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDao.java @@ -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> listDomainsOfServiceOfferingsUsedByDomainPath(String domainPath); } diff --git a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java index 1cad19e40a2..4a81cc8bfee 100644 --- a/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/ServiceOfferingJoinDaoImpl.java @@ -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> 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> 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 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<>(); + } + } } diff --git a/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDao.java b/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDao.java index 1a67ff4feed..c6ed450e2e1 100644 --- a/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDao.java +++ b/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDao.java @@ -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 findHostsByCluster(Long clusterId); List findHostsByZone(Long zoneId); + + Map> listDomainsOfDedicatedResourcesUsedByDomainPath(String domainPath); } diff --git a/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDaoImpl.java b/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDaoImpl.java index c10ef2dc25f..31f2e361cd5 100644 --- a/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDaoImpl.java +++ b/server/src/main/java/com/cloud/dc/dao/DedicatedResourceDaoImpl.java @@ -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 implements DedicatedResourceDao { + public static Logger LOGGER = Logger.getLogger(DedicatedResourceDaoImpl.class.getName()); + @Inject protected HostDao hostDao; @@ -79,6 +87,17 @@ public class DedicatedResourceDaoImpl extends GenericDaoBase ListHostsByCluster; protected SearchBuilder 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> 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> 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 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<>(); + } + } } diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 9e88b6f1a1a..f794736a4d5 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -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); diff --git a/server/src/main/java/com/cloud/user/DomainManagerImpl.java b/server/src/main/java/com/cloud/user/DomainManagerImpl.java index 2dd356aca8e..1551309890c 100644 --- a/server/src/main/java/com/cloud/user/DomainManagerImpl.java +++ b/server/src/main/java/com/cloud/user/DomainManagerImpl.java @@ -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 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> idsOfDomainsWithNetworksUsedByDomainToBeMoved = _networkDomainDao.listDomainsOfSharedNetworksUsedByDomainPath(currentPathOfDomainToBeMoved); + validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(newPathOfDomainToBeMoved, domainToBeMoved, newParentDomain, idsOfDomainsWithNetworksUsedByDomainToBeMoved, "Networks"); + + Map> idsOfDomainsOfAffinityGroupsUsedByDomainToBeMoved = + affinityGroupDomainMapDao.listDomainsOfAffinityGroupsUsedByDomainPath(currentPathOfDomainToBeMoved); + validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(newPathOfDomainToBeMoved, domainToBeMoved, newParentDomain, idsOfDomainsOfAffinityGroupsUsedByDomainToBeMoved, "Affinity groups"); + + Map> idsOfDomainsOfServiceOfferingsUsedByDomainToBeMoved = + serviceOfferingJoinDao.listDomainsOfServiceOfferingsUsedByDomainPath(currentPathOfDomainToBeMoved); + validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(newPathOfDomainToBeMoved, domainToBeMoved, newParentDomain, idsOfDomainsOfServiceOfferingsUsedByDomainToBeMoved, "Service offerings"); + + Map> idsOfDomainsOfNetworkOfferingsUsedByDomainToBeMoved = + networkOfferingJoinDao.listDomainsOfNetworkOfferingsUsedByDomainPath(currentPathOfDomainToBeMoved); + validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(newPathOfDomainToBeMoved, domainToBeMoved, newParentDomain, idsOfDomainsOfNetworkOfferingsUsedByDomainToBeMoved, "Network offerings"); + + Map> idsOfDomainsOfDedicatedResourcesUsedByDomainToBeMoved = + _dedicatedDao.listDomainsOfDedicatedResourcesUsedByDomainPath(currentPathOfDomainToBeMoved); + validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(newPathOfDomainToBeMoved, domainToBeMoved, newParentDomain, idsOfDomainsOfDedicatedResourcesUsedByDomainToBeMoved, "Dedicated resources"); + } + + protected void validateNewParentDomainCanAccessResourcesOfDomainToBeMoved(String newPathOfDomainToBeMoved, DomainVO domainToBeMoved, DomainVO newParentDomain, + Map> idsOfDomainsWithResourcesUsedByDomainToBeMoved, String resourceToLog) { + Map> domainsOfResourcesInaccessibleToNewParentDomain = new HashMap<>(); + for (Map.Entry> 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 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); + } }