Skip to content

Commit 12dcf5c

Browse files
authored
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.
1 parent 4bdf35b commit 12dcf5c

File tree

15 files changed

+566
-2
lines changed

15 files changed

+566
-2
lines changed

api/src/main/java/com/cloud/event/EventTypes.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ public class EventTypes {
320320
public static final String EVENT_DOMAIN_CREATE = "DOMAIN.CREATE";
321321
public static final String EVENT_DOMAIN_DELETE = "DOMAIN.DELETE";
322322
public static final String EVENT_DOMAIN_UPDATE = "DOMAIN.UPDATE";
323+
public static final String EVENT_DOMAIN_MOVE = "DOMAIN.MOVE";
323324

324325
// Snapshots
325326
public static final String EVENT_SNAPSHOT_COPY = "SNAPSHOT.COPY";
@@ -878,6 +879,7 @@ public class EventTypes {
878879
entityEventDetails.put(EVENT_DOMAIN_CREATE, Domain.class);
879880
entityEventDetails.put(EVENT_DOMAIN_DELETE, Domain.class);
880881
entityEventDetails.put(EVENT_DOMAIN_UPDATE, Domain.class);
882+
entityEventDetails.put(EVENT_DOMAIN_MOVE, Domain.class);
881883

882884
// Snapshots
883885
entityEventDetails.put(EVENT_SNAPSHOT_CREATE, Snapshot.class);

api/src/main/java/com/cloud/user/DomainService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020

2121
import org.apache.cloudstack.api.command.admin.domain.ListDomainChildrenCmd;
2222
import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmd;
23+
import org.apache.cloudstack.api.command.admin.domain.MoveDomainCmd;
2324

2425
import com.cloud.domain.Domain;
2526
import com.cloud.exception.PermissionDeniedException;
27+
import com.cloud.exception.ResourceAllocationException;
2628
import com.cloud.utils.Pair;
2729

2830
public interface DomainService {
@@ -66,4 +68,5 @@ public interface DomainService {
6668
*/
6769
Domain findDomainByIdOrPath(Long id, String domainPath);
6870

71+
Domain moveDomainAndChildrenToNewParentDomain(MoveDomainCmd cmd) throws ResourceAllocationException;
6972
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.api.command.admin.domain;
18+
19+
import com.cloud.domain.Domain;
20+
import com.cloud.exception.ResourceAllocationException;
21+
import com.cloud.user.Account;
22+
import org.apache.cloudstack.acl.RoleType;
23+
import org.apache.cloudstack.api.APICommand;
24+
import org.apache.cloudstack.api.ApiConstants;
25+
import org.apache.cloudstack.api.ApiErrorCode;
26+
import org.apache.cloudstack.api.BaseCmd;
27+
import org.apache.cloudstack.api.Parameter;
28+
import org.apache.cloudstack.api.ServerApiException;
29+
import org.apache.cloudstack.api.response.DomainResponse;
30+
31+
@APICommand(name = "moveDomain", description = "Moves a domain and its children to a new parent domain.", since = "4.19.0.0", responseObject = DomainResponse.class,
32+
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin})
33+
public class MoveDomainCmd extends BaseCmd {
34+
35+
private static final String APINAME = "moveDomain";
36+
@Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = true, entityType = DomainResponse.class, description = "The ID of the domain to be moved.")
37+
private Long domainId;
38+
39+
@Parameter(name = ApiConstants.PARENT_DOMAIN_ID, type = CommandType.UUID, required = true, entityType = DomainResponse.class,
40+
description = "The ID of the new parent domain of the domain to be moved.")
41+
private Long parentDomainId;
42+
43+
public Long getDomainId() {
44+
return domainId;
45+
}
46+
47+
public Long getParentDomainId() {
48+
return parentDomainId;
49+
}
50+
51+
@Override
52+
public String getCommandName() {
53+
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
54+
}
55+
56+
@Override
57+
public long getEntityOwnerId() {
58+
return Account.ACCOUNT_ID_SYSTEM;
59+
}
60+
61+
@Override
62+
public void execute() throws ResourceAllocationException {
63+
Domain domain = _domainService.moveDomainAndChildrenToNewParentDomain(this);
64+
65+
if (domain != null) {
66+
DomainResponse response = _responseGenerator.createDomainResponse(domain);
67+
response.setResponseName(getCommandName());
68+
this.setResponseObject(response);
69+
} else {
70+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to move the domain.");
71+
}
72+
}
73+
}

engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDao.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.cloud.network.dao;
1818

1919
import java.util.List;
20+
import java.util.Map;
2021

2122
import com.cloud.utils.db.GenericDao;
2223

@@ -26,4 +27,6 @@ public interface NetworkDomainDao extends GenericDao<NetworkDomainVO, Long> {
2627
NetworkDomainVO getDomainNetworkMapByNetworkId(long networkId);
2728

2829
List<Long> listNetworkIdsByDomain(long domainId);
30+
31+
Map<Long, List<String>> listDomainsOfSharedNetworksUsedByDomainPath(String domainPath);
2932
}

engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainDaoImpl.java

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,17 @@
1616
// under the License.
1717
package com.cloud.network.dao;
1818

19+
import java.sql.PreparedStatement;
20+
import java.sql.ResultSet;
21+
import java.sql.SQLException;
1922
import java.util.ArrayList;
23+
import java.util.Arrays;
24+
import java.util.HashMap;
2025
import java.util.List;
26+
import java.util.Map;
2127

22-
28+
import com.cloud.utils.db.TransactionLegacy;
29+
import org.apache.log4j.Logger;
2330
import org.springframework.stereotype.Component;
2431

2532
import com.cloud.utils.db.DB;
@@ -31,9 +38,23 @@
3138
@Component
3239
@DB()
3340
public class NetworkDomainDaoImpl extends GenericDaoBase<NetworkDomainVO, Long> implements NetworkDomainDao {
41+
public static Logger logger = Logger.getLogger(NetworkDomainDaoImpl.class.getName());
3442
final SearchBuilder<NetworkDomainVO> AllFieldsSearch;
3543
final SearchBuilder<NetworkDomainVO> DomainsSearch;
3644

45+
private static final String LIST_DOMAINS_OF_SHARED_NETWORKS_USED_BY_DOMAIN_PATH = "SELECT shared_nw.domain_id, \n" +
46+
"GROUP_CONCAT('VM:', vm.uuid, ' | NW:' , network.uuid) \n" +
47+
"FROM cloud.domain_network_ref AS shared_nw\n" +
48+
"INNER JOIN cloud.nics AS nic ON (nic.network_id = shared_nw.network_id AND nic.removed IS NULL)\n" +
49+
"INNER JOIN cloud.vm_instance AS vm ON (vm.id = nic.instance_id)\n" +
50+
"INNER JOIN cloud.domain AS domain ON (domain.id = vm.domain_id)\n" +
51+
"INNER JOIN cloud.domain AS domain_sn ON (domain_sn.id = shared_nw.domain_id)\n" +
52+
"INNER JOIN cloud.networks AS network ON (shared_nw.network_id = network.id)\n" +
53+
"WHERE shared_nw.subdomain_access = 1\n" +
54+
"AND domain.path LIKE ?\n" +
55+
"AND domain_sn.path NOT LIKE ?\n" +
56+
"GROUP BY shared_nw.network_id";
57+
3758
protected NetworkDomainDaoImpl() {
3859
super();
3960

@@ -71,4 +92,37 @@ public List<Long> listNetworkIdsByDomain(long domainId) {
7192
}
7293
return networkIdsToReturn;
7394
}
95+
96+
@Override
97+
public Map<Long, List<String>> listDomainsOfSharedNetworksUsedByDomainPath(String domainPath) {
98+
logger.debug(String.format("Retrieving the domains of the shared networks with subdomain access used by domain with path [%s].", domainPath));
99+
100+
TransactionLegacy txn = TransactionLegacy.currentTxn();
101+
try (PreparedStatement pstmt = txn.prepareStatement(LIST_DOMAINS_OF_SHARED_NETWORKS_USED_BY_DOMAIN_PATH)) {
102+
Map<Long, List<String>> domainsOfSharedNetworksUsedByDomainPath = new HashMap<>();
103+
104+
String domainSearch = domainPath.concat("%");
105+
pstmt.setString(1, domainSearch);
106+
pstmt.setString(2, domainSearch);
107+
108+
try (ResultSet rs = pstmt.executeQuery()) {
109+
while (rs.next()) {
110+
Long domainId = rs.getLong(1);
111+
List<String> vmUuidsAndNetworkUuids = Arrays.asList(rs.getString(2).split(","));
112+
113+
domainsOfSharedNetworksUsedByDomainPath.put(domainId, vmUuidsAndNetworkUuids);
114+
}
115+
}
116+
117+
return domainsOfSharedNetworksUsedByDomainPath;
118+
} catch (SQLException e) {
119+
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 "
120+
+ "list of domains.", domainPath, e.getMessage()));
121+
122+
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 "
123+
+ "list of domains.", domainPath), e);
124+
125+
return new HashMap<>();
126+
}
127+
}
74128
}

engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDao.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.apache.cloudstack.affinity.dao;
1818

1919
import java.util.List;
20+
import java.util.Map;
2021

2122
import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
2223

@@ -28,4 +29,6 @@ public interface AffinityGroupDomainMapDao extends GenericDao<AffinityGroupDomai
2829

2930
List<AffinityGroupDomainMapVO> listByDomain(Object... domainId);
3031

32+
Map<Long, List<String>> listDomainsOfAffinityGroupsUsedByDomainPath(String domainPath);
33+
3134
}

engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDaoImpl.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,46 @@
1616
// under the License.
1717
package org.apache.cloudstack.affinity.dao;
1818

19+
import java.sql.PreparedStatement;
20+
import java.sql.ResultSet;
21+
import java.sql.SQLException;
22+
import java.util.Arrays;
23+
import java.util.HashMap;
1924
import java.util.List;
25+
import java.util.Map;
2026

2127
import javax.annotation.PostConstruct;
2228

2329
import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
30+
import org.apache.log4j.Logger;
2431

32+
import com.cloud.network.dao.NetworkDomainDaoImpl;
2533
import com.cloud.utils.db.GenericDaoBase;
2634
import com.cloud.utils.db.SearchBuilder;
2735
import com.cloud.utils.db.SearchCriteria;
2836
import com.cloud.utils.db.SearchCriteria.Op;
37+
import com.cloud.utils.db.TransactionLegacy;
2938

3039
public class AffinityGroupDomainMapDaoImpl extends GenericDaoBase<AffinityGroupDomainMapVO, Long> implements AffinityGroupDomainMapDao {
40+
public static Logger logger = Logger.getLogger(NetworkDomainDaoImpl.class.getName());
3141

3242
private SearchBuilder<AffinityGroupDomainMapVO> ListByAffinityGroup;
3343

3444
private SearchBuilder<AffinityGroupDomainMapVO> DomainsSearch;
3545

46+
private static final String LIST_DOMAINS_WITH_AFFINITY_GROUPS_WITH_SUBDOMAIN_ACCESS_USED_BY_DOMAIN_PATH = "SELECT affinity_group_domain_map.domain_id, \n" +
47+
"GROUP_CONCAT('VM:', vm.uuid, ' | AG:' , affinity_group.uuid) \n" +
48+
"FROM cloud.affinity_group_domain_map AS affinity_group_domain_map\n" +
49+
"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" +
50+
"INNER JOIN cloud.vm_instance AS vm ON (vm.id = affinity_group_vm_map.instance_id)\n" +
51+
"INNER JOIN cloud.domain AS domain ON (domain.id = vm.domain_id)\n" +
52+
"INNER JOIN cloud.domain AS domain_sn ON (domain_sn.id = affinity_group_domain_map.domain_id)\n" +
53+
"INNER JOIN cloud.affinity_group AS affinity_group ON (affinity_group.id = affinity_group_domain_map.affinity_group_id)\n" +
54+
"WHERE affinity_group_domain_map.subdomain_access = 1\n" +
55+
"AND domain.path LIKE ?\n" +
56+
"AND domain_sn.path NOT LIKE ?\n" +
57+
"GROUP BY affinity_group.id";
58+
3659
public AffinityGroupDomainMapDaoImpl() {
3760
}
3861

@@ -62,4 +85,38 @@ public List<AffinityGroupDomainMapVO> listByDomain(Object... domainId) {
6285
return listBy(sc);
6386
}
6487

88+
@Override
89+
public Map<Long, List<String>> listDomainsOfAffinityGroupsUsedByDomainPath(String domainPath) {
90+
logger.debug(String.format("Retrieving the domains of the affinity groups with subdomain access used by domain with path [%s].", domainPath));
91+
92+
TransactionLegacy txn = TransactionLegacy.currentTxn();
93+
try (PreparedStatement pstmt = txn.prepareStatement(LIST_DOMAINS_WITH_AFFINITY_GROUPS_WITH_SUBDOMAIN_ACCESS_USED_BY_DOMAIN_PATH)) {
94+
Map<Long, List<String>> domainsOfAffinityGroupsUsedByDomainPath = new HashMap<>();
95+
96+
String domainSearch = domainPath.concat("%");
97+
pstmt.setString(1, domainSearch);
98+
pstmt.setString(2, domainSearch);
99+
100+
101+
try (ResultSet rs = pstmt.executeQuery()) {
102+
while (rs.next()) {
103+
Long domainId = rs.getLong(1);
104+
List<String> vmUuidsAndAffinityGroupUuids = Arrays.asList(rs.getString(2).split(","));
105+
106+
domainsOfAffinityGroupsUsedByDomainPath.put(domainId, vmUuidsAndAffinityGroupUuids);
107+
}
108+
}
109+
110+
return domainsOfAffinityGroupsUsedByDomainPath;
111+
} catch (SQLException e) {
112+
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 " +
113+
"empty list of domains.", domainPath, e.getMessage()));
114+
115+
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 "
116+
+ "list of domains.", domainPath), e);
117+
118+
return new HashMap<>();
119+
}
120+
}
121+
65122
}

server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDao.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package com.cloud.api.query.dao;
1919

2020
import java.util.List;
21+
import java.util.Map;
2122

2223
import org.apache.cloudstack.api.response.NetworkOfferingResponse;
2324

@@ -52,4 +53,6 @@ public interface NetworkOfferingJoinDao extends GenericDao<NetworkOfferingJoinVO
5253
NetworkOfferingResponse newNetworkOfferingResponse(NetworkOffering nof);
5354

5455
NetworkOfferingJoinVO newNetworkOfferingView(NetworkOffering nof);
56+
57+
Map<Long, List<String>> listDomainsOfNetworkOfferingsUsedByDomainPath(String domainPath);
5558
}

server/src/main/java/com/cloud/api/query/dao/NetworkOfferingJoinDaoImpl.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,15 @@
1717

1818
package com.cloud.api.query.dao;
1919

20+
import java.sql.PreparedStatement;
21+
import java.sql.ResultSet;
22+
import java.sql.SQLException;
23+
import java.util.Arrays;
24+
import java.util.HashMap;
2025
import java.util.List;
26+
import java.util.Map;
2127

28+
import com.cloud.utils.db.TransactionLegacy;
2229
import org.apache.commons.lang3.StringUtils;
2330
import org.apache.cloudstack.api.response.NetworkOfferingResponse;
2431
import org.apache.log4j.Logger;
@@ -44,6 +51,17 @@ protected NetworkOfferingJoinDaoImpl() {
4451
_count = "select count(distinct id) from network_offering_view WHERE ";
4552
}
4653

54+
private static final String LIST_DOMAINS_OF_NETWORK_OFFERINGS_USED_BY_DOMAIN_PATH = "SELECT nov.domain_id, \n" +
55+
" GROUP_CONCAT('Network:', net.uuid) \n" +
56+
" FROM cloud.network_offering_view AS nov\n" +
57+
" INNER JOIN cloud.networks AS net ON (net.network_offering_id = nov.id) \n" +
58+
" INNER JOIN cloud.domain AS domain ON (domain.id = net.domain_id) \n" +
59+
" INNER JOIN cloud.domain AS domain_so ON (domain_so.id = nov.domain_id) \n" +
60+
" WHERE domain.path LIKE ? \n" +
61+
" AND domain_so.path NOT LIKE ? \n" +
62+
" AND net.removed IS NULL \n" +
63+
" GROUP BY nov.id";
64+
4765
@Override
4866
public List<NetworkOfferingJoinVO> findByDomainId(long domainId, Boolean includeAllDomainOffering) {
4967
SearchBuilder<NetworkOfferingJoinVO> sb = createSearchBuilder();
@@ -120,4 +138,39 @@ public NetworkOfferingJoinVO newNetworkOfferingView(NetworkOffering offering) {
120138
assert offerings != null && offerings.size() == 1 : "No network offering found for offering id " + offering.getId();
121139
return offerings.get(0);
122140
}
141+
142+
143+
144+
@Override
145+
public Map<Long, List<String>> listDomainsOfNetworkOfferingsUsedByDomainPath(String domainPath) {
146+
s_logger.debug(String.format("Retrieving the domains of the network offerings used by domain with path [%s].", domainPath));
147+
148+
TransactionLegacy txn = TransactionLegacy.currentTxn();
149+
try (PreparedStatement pstmt = txn.prepareStatement(LIST_DOMAINS_OF_NETWORK_OFFERINGS_USED_BY_DOMAIN_PATH)) {
150+
Map<Long, List<String>> domainsOfNetworkOfferingsUsedByDomainPath = new HashMap<>();
151+
152+
String domainSearch = domainPath.concat("%");
153+
pstmt.setString(1, domainSearch);
154+
pstmt.setString(2, domainSearch);
155+
156+
try (ResultSet rs = pstmt.executeQuery()) {
157+
while (rs.next()) {
158+
Long domainId = rs.getLong(1);
159+
List<String> networkUuids = Arrays.asList(rs.getString(2).split(","));
160+
161+
domainsOfNetworkOfferingsUsedByDomainPath.put(domainId, networkUuids);
162+
}
163+
}
164+
165+
return domainsOfNetworkOfferingsUsedByDomainPath;
166+
} catch (SQLException e) {
167+
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 "
168+
+ "list of domains.", domainPath, e.getMessage()));
169+
170+
s_logger.debug(String.format("Failed to retrieve the domains of the network offerings used by domain with path [%s]. Returning an empty " +
171+
"list of domains.", domainPath), e);
172+
173+
return new HashMap<>();
174+
}
175+
}
123176
}

0 commit comments

Comments
 (0)