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

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


The following commit(s) were added to refs/heads/main by this push:
     new 12dcf5cc6f4 Move subdomains between domains (#7446)
12dcf5cc6f4 is described below

commit 12dcf5cc6f4727f6193d289b2d656001b26da643
Author: João Jandre <[email protected]>
AuthorDate: Sun Dec 10 03:48:15 2023 -0300

    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.
---
 api/src/main/java/com/cloud/event/EventTypes.java  |   2 +
 .../main/java/com/cloud/user/DomainService.java    |   3 +
 .../api/command/admin/domain/MoveDomainCmd.java    |  73 ++++++++
 .../com/cloud/network/dao/NetworkDomainDao.java    |   3 +
 .../cloud/network/dao/NetworkDomainDaoImpl.java    |  56 +++++-
 .../affinity/dao/AffinityGroupDomainMapDao.java    |   3 +
 .../dao/AffinityGroupDomainMapDaoImpl.java         |  57 ++++++
 .../api/query/dao/NetworkOfferingJoinDao.java      |   3 +
 .../api/query/dao/NetworkOfferingJoinDaoImpl.java  |  53 ++++++
 .../api/query/dao/ServiceOfferingJoinDao.java      |   3 +
 .../api/query/dao/ServiceOfferingJoinDaoImpl.java  |  52 ++++++
 .../com/cloud/dc/dao/DedicatedResourceDao.java     |   3 +
 .../com/cloud/dc/dao/DedicatedResourceDaoImpl.java |  53 ++++++
 .../com/cloud/server/ManagementServerImpl.java     |   2 +
 .../java/com/cloud/user/DomainManagerImpl.java     | 202 ++++++++++++++++++++-
 15 files changed, 566 insertions(+), 2 deletions(-)

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, Long> {
     NetworkDomainVO getDomainNetworkMapByNetworkId(long networkId);
 
     List<Long> listNetworkIdsByDomain(long domainId);
+
+    Map<Long, List<String>> 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<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<>();
+        }
+    }
 }
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<AffinityGroupDomai
 
     List<AffinityGroupDomainMapVO> listByDomain(Object... domainId);
 
+    Map<Long, List<String>> 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<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<>();
+        }
+    }
+
 }
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<NetworkOfferingJoinVO
     NetworkOfferingResponse newNetworkOfferingResponse(NetworkOffering nof);
 
     NetworkOfferingJoinVO newNetworkOfferingView(NetworkOffering nof);
+
+    Map<Long, List<String>> 
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<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<>();
+        }
+    }
 }
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<ServiceOfferingJoinVO
     ServiceOfferingResponse newServiceOfferingResponse(ServiceOfferingJoinVO 
offering);
 
     ServiceOfferingJoinVO newServiceOfferingView(ServiceOffering offering);
+
+    Map<Long, List<String>> 
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<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<>();
+        }
+    }
 }
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<DedicatedResourceVO, Lo
     List<Long> findHostsByCluster(Long clusterId);
 
     List<Long> findHostsByZone(Long zoneId);
+
+    Map<Long, List<String>> 
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<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<>();
+        }
+    }
 }
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<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);
+    }
 }

Reply via email to