Skip to content

Commit 3bd5410

Browse files
authored
Add support to clone existing offerings and update them (#12357)
* Add support to clone existing offerings and update them * add support for vpc & backup offerings to be cloned * fix capability list and mapping of params * Add support to clone network and vpc offering with the right parameters * make fields non mandatory for clone offerings APIs * Add UI support for cloning Compute and System Service offerings * remove unnecessary changes * fix license and pre-ccommit issues * Add UI support to clone disk and network offering * vpc & backup offering clone api * add unit tests * fix pre-commit checks * increase test coverage * combine add/clone disk/compute offering forms * update license * fix unit tests * fix test failures * fix test failure - unnecessary stubbings * pre-commit check failure * add recently added domain id for bkp offering to be inherited in clone operation * extract common code wrt service capability in network & vpc offering in add/clone operations * add some checks to prevent networkmode change when provider is nsx/netris from the source networkmode * address copilot comments * address comments * combine check * use appropriate zoneId during clone bkp offering * add check * fix issue with test * remove unused imports * prevent creating a bkp offering of a bkp repo that already exists * extend clone disk and service offerings to domain admins
1 parent 93239e0 commit 3bd5410

File tree

41 files changed

+10702
-1834
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+10702
-1834
lines changed

api/src/main/java/com/cloud/configuration/ConfigurationService.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,18 @@
2424
import org.apache.cloudstack.api.ApiConstants;
2525
import org.apache.cloudstack.api.command.admin.config.ResetCfgCmd;
2626
import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd;
27+
import org.apache.cloudstack.api.command.admin.network.CloneNetworkOfferingCmd;
2728
import org.apache.cloudstack.api.command.admin.network.CreateGuestNetworkIpv6PrefixCmd;
2829
import org.apache.cloudstack.api.command.admin.network.CreateManagementNetworkIpRangeCmd;
29-
import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd;
3030
import org.apache.cloudstack.api.command.admin.network.DeleteGuestNetworkIpv6PrefixCmd;
3131
import org.apache.cloudstack.api.command.admin.network.DeleteManagementNetworkIpRangeCmd;
3232
import org.apache.cloudstack.api.command.admin.network.DeleteNetworkOfferingCmd;
3333
import org.apache.cloudstack.api.command.admin.network.ListGuestNetworkIpv6PrefixesCmd;
34+
import org.apache.cloudstack.api.command.admin.network.NetworkOfferingBaseCmd;
3435
import org.apache.cloudstack.api.command.admin.network.UpdateNetworkOfferingCmd;
3536
import org.apache.cloudstack.api.command.admin.network.UpdatePodManagementNetworkIpRangeCmd;
37+
import org.apache.cloudstack.api.command.admin.offering.CloneDiskOfferingCmd;
38+
import org.apache.cloudstack.api.command.admin.offering.CloneServiceOfferingCmd;
3639
import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd;
3740
import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCmd;
3841
import org.apache.cloudstack.api.command.admin.offering.DeleteDiskOfferingCmd;
@@ -105,6 +108,33 @@ public interface ConfigurationService {
105108
*/
106109
ServiceOffering createServiceOffering(CreateServiceOfferingCmd cmd);
107110

111+
/**
112+
* Clones a service offering with optional parameter overrides
113+
*
114+
* @param cmd
115+
* the command object that specifies the source offering ID and optional parameter overrides
116+
* @return the newly created service offering cloned from source, null otherwise
117+
*/
118+
ServiceOffering cloneServiceOffering(CloneServiceOfferingCmd cmd);
119+
120+
/**
121+
* Clones a disk offering with optional parameter overrides
122+
*
123+
* @param cmd
124+
* the command object that specifies the source offering ID and optional parameter overrides
125+
* @return the newly created disk offering cloned from source, null otherwise
126+
*/
127+
DiskOffering cloneDiskOffering(CloneDiskOfferingCmd cmd);
128+
129+
/**
130+
* Clones a network offering with optional parameter overrides
131+
*
132+
* @param cmd
133+
* the command object that specifies the source offering ID and optional parameter overrides
134+
* @return the newly created network offering cloned from source, null otherwise
135+
*/
136+
NetworkOffering cloneNetworkOffering(CloneNetworkOfferingCmd cmd);
137+
108138
/**
109139
* Updates a service offering
110140
*
@@ -282,7 +312,7 @@ Vlan updateVlanAndPublicIpRange(UpdateVlanIpRangeCmd cmd) throws ConcurrentOpera
282312

283313
boolean releasePublicIpRange(ReleasePublicIpRangeCmd cmd);
284314

285-
NetworkOffering createNetworkOffering(CreateNetworkOfferingCmd cmd);
315+
NetworkOffering createNetworkOffering(NetworkOfferingBaseCmd cmd);
286316

287317
NetworkOffering updateNetworkOffering(UpdateNetworkOfferingCmd cmd);
288318

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,11 +375,13 @@ public class EventTypes {
375375

376376
// Service Offerings
377377
public static final String EVENT_SERVICE_OFFERING_CREATE = "SERVICE.OFFERING.CREATE";
378+
public static final String EVENT_SERVICE_OFFERING_CLONE = "SERVICE.OFFERING.CLONE";
378379
public static final String EVENT_SERVICE_OFFERING_EDIT = "SERVICE.OFFERING.EDIT";
379380
public static final String EVENT_SERVICE_OFFERING_DELETE = "SERVICE.OFFERING.DELETE";
380381

381382
// Disk Offerings
382383
public static final String EVENT_DISK_OFFERING_CREATE = "DISK.OFFERING.CREATE";
384+
public static final String EVENT_DISK_OFFERING_CLONE = "DISK.OFFERING.CLONE";
383385
public static final String EVENT_DISK_OFFERING_EDIT = "DISK.OFFERING.EDIT";
384386
public static final String EVENT_DISK_OFFERING_DELETE = "DISK.OFFERING.DELETE";
385387

@@ -400,6 +402,7 @@ public class EventTypes {
400402

401403
// Network offerings
402404
public static final String EVENT_NETWORK_OFFERING_CREATE = "NETWORK.OFFERING.CREATE";
405+
public static final String EVENT_NETWORK_OFFERING_CLONE = "NETWORK.OFFERING.CLONE";
403406
public static final String EVENT_NETWORK_OFFERING_ASSIGN = "NETWORK.OFFERING.ASSIGN";
404407
public static final String EVENT_NETWORK_OFFERING_EDIT = "NETWORK.OFFERING.EDIT";
405408
public static final String EVENT_NETWORK_OFFERING_REMOVE = "NETWORK.OFFERING.REMOVE";
@@ -599,6 +602,7 @@ public class EventTypes {
599602

600603
// VPC offerings
601604
public static final String EVENT_VPC_OFFERING_CREATE = "VPC.OFFERING.CREATE";
605+
public static final String EVENT_VPC_OFFERING_CLONE = "VPC.OFFERING.CLONE";
602606
public static final String EVENT_VPC_OFFERING_UPDATE = "VPC.OFFERING.UPDATE";
603607
public static final String EVENT_VPC_OFFERING_DELETE = "VPC.OFFERING.DELETE";
604608

@@ -631,6 +635,7 @@ public class EventTypes {
631635

632636
// Backup and Recovery events
633637
public static final String EVENT_VM_BACKUP_IMPORT_OFFERING = "BACKUP.IMPORT.OFFERING";
638+
public static final String EVENT_VM_BACKUP_OFFERING_CLONE = "BACKUP.OFFERING.CLONE";
634639
public static final String EVENT_VM_BACKUP_OFFERING_ASSIGN = "BACKUP.OFFERING.ASSIGN";
635640
public static final String EVENT_VM_BACKUP_OFFERING_REMOVE = "BACKUP.OFFERING.REMOVE";
636641
public static final String EVENT_VM_BACKUP_CREATE = "BACKUP.CREATE";
@@ -1046,11 +1051,13 @@ public class EventTypes {
10461051

10471052
// Service Offerings
10481053
entityEventDetails.put(EVENT_SERVICE_OFFERING_CREATE, ServiceOffering.class);
1054+
entityEventDetails.put(EVENT_SERVICE_OFFERING_CLONE, ServiceOffering.class);
10491055
entityEventDetails.put(EVENT_SERVICE_OFFERING_EDIT, ServiceOffering.class);
10501056
entityEventDetails.put(EVENT_SERVICE_OFFERING_DELETE, ServiceOffering.class);
10511057

10521058
// Disk Offerings
10531059
entityEventDetails.put(EVENT_DISK_OFFERING_CREATE, DiskOffering.class);
1060+
entityEventDetails.put(EVENT_DISK_OFFERING_CLONE, DiskOffering.class);
10541061
entityEventDetails.put(EVENT_DISK_OFFERING_EDIT, DiskOffering.class);
10551062
entityEventDetails.put(EVENT_DISK_OFFERING_DELETE, DiskOffering.class);
10561063

@@ -1071,6 +1078,7 @@ public class EventTypes {
10711078

10721079
// Network offerings
10731080
entityEventDetails.put(EVENT_NETWORK_OFFERING_CREATE, NetworkOffering.class);
1081+
entityEventDetails.put(EVENT_NETWORK_OFFERING_CLONE, NetworkOffering.class);
10741082
entityEventDetails.put(EVENT_NETWORK_OFFERING_ASSIGN, NetworkOffering.class);
10751083
entityEventDetails.put(EVENT_NETWORK_OFFERING_EDIT, NetworkOffering.class);
10761084
entityEventDetails.put(EVENT_NETWORK_OFFERING_REMOVE, NetworkOffering.class);

api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.List;
2121
import java.util.Map;
2222

23+
import org.apache.cloudstack.api.command.admin.vpc.CloneVPCOfferingCmd;
2324
import org.apache.cloudstack.api.command.admin.vpc.CreateVPCOfferingCmd;
2425
import org.apache.cloudstack.api.command.admin.vpc.UpdateVPCOfferingCmd;
2526
import org.apache.cloudstack.api.command.user.vpc.ListVPCOfferingsCmd;
@@ -34,6 +35,8 @@ public interface VpcProvisioningService {
3435

3536
VpcOffering createVpcOffering(CreateVPCOfferingCmd cmd);
3637

38+
VpcOffering cloneVPCOffering(CloneVPCOfferingCmd cmd);
39+
3740
VpcOffering createVpcOffering(String name, String displayText, List<String> supportedServices,
3841
Map<String, List<String>> serviceProviders,
3942
Map serviceCapabilitystList, NetUtils.InternetProtocol internetProtocol,

api/src/main/java/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,7 @@ public class ApiConstants {
559559
public static final String USE_STORAGE_REPLICATION = "usestoragereplication";
560560

561561
public static final String SOURCE_CIDR_LIST = "sourcecidrlist";
562+
public static final String SOURCE_OFFERING_ID = "sourceofferingid";
562563
public static final String SOURCE_ZONE_ID = "sourcezoneid";
563564
public static final String SSL_VERIFICATION = "sslverification";
564565
public static final String START_ASN = "startasn";
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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.backup;
18+
19+
import javax.inject.Inject;
20+
21+
import org.apache.cloudstack.acl.RoleType;
22+
import org.apache.cloudstack.api.APICommand;
23+
import org.apache.cloudstack.api.ApiConstants;
24+
import org.apache.cloudstack.api.ApiErrorCode;
25+
import org.apache.cloudstack.api.BaseAsyncCmd;
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.command.offering.DomainAndZoneIdResolver;
30+
import org.apache.cloudstack.api.response.BackupOfferingResponse;
31+
import org.apache.cloudstack.api.response.ZoneResponse;
32+
import org.apache.cloudstack.backup.BackupManager;
33+
import org.apache.cloudstack.backup.BackupOffering;
34+
import org.apache.cloudstack.context.CallContext;
35+
36+
import com.cloud.event.EventTypes;
37+
import com.cloud.exception.ConcurrentOperationException;
38+
import com.cloud.exception.InsufficientCapacityException;
39+
import com.cloud.exception.InvalidParameterValueException;
40+
import com.cloud.exception.NetworkRuleConflictException;
41+
import com.cloud.exception.ResourceAllocationException;
42+
import com.cloud.exception.ResourceUnavailableException;
43+
import com.cloud.utils.exception.CloudRuntimeException;
44+
45+
import java.util.Arrays;
46+
import java.util.List;
47+
import java.util.function.LongFunction;
48+
49+
@APICommand(name = "cloneBackupOffering",
50+
description = "Clones a backup offering from an existing offering",
51+
responseObject = BackupOfferingResponse.class, since = "4.23.0",
52+
authorized = {RoleType.Admin})
53+
public class CloneBackupOfferingCmd extends BaseAsyncCmd implements DomainAndZoneIdResolver {
54+
55+
@Inject
56+
protected BackupManager backupManager;
57+
58+
/////////////////////////////////////////////////////
59+
//////////////// API parameters /////////////////////
60+
////////////////////////////////////////////////////
61+
62+
@Parameter(name = ApiConstants.SOURCE_OFFERING_ID, type = BaseCmd.CommandType.UUID, entityType = BackupOfferingResponse.class,
63+
required = true, description = "The ID of the source backup offering to clone from")
64+
private Long sourceOfferingId;
65+
66+
@Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, required = true,
67+
description = "The name of the cloned offering")
68+
private String name;
69+
70+
@Parameter(name = ApiConstants.DESCRIPTION, type = BaseCmd.CommandType.STRING, required = false,
71+
description = "The description of the cloned offering")
72+
private String description;
73+
74+
@Parameter(name = ApiConstants.EXTERNAL_ID, type = BaseCmd.CommandType.STRING, required = false,
75+
description = "The backup offering ID (from backup provider side)")
76+
private String externalId;
77+
78+
@Parameter(name = ApiConstants.ZONE_ID, type = BaseCmd.CommandType.UUID, entityType = ZoneResponse.class,
79+
description = "The zone ID", required = false)
80+
private Long zoneId;
81+
82+
@Parameter(name = ApiConstants.DOMAIN_ID,
83+
type = CommandType.STRING,
84+
description = "the ID of the containing domain(s) as comma separated string, public for public offerings",
85+
length = 4096)
86+
private String domainIds;
87+
88+
@Parameter(name = ApiConstants.ALLOW_USER_DRIVEN_BACKUPS, type = BaseCmd.CommandType.BOOLEAN,
89+
description = "Whether users are allowed to create adhoc backups and backup schedules", required = false)
90+
private Boolean userDrivenBackups;
91+
92+
/////////////////////////////////////////////////////
93+
/////////////////// Accessors ///////////////////////
94+
/////////////////////////////////////////////////////
95+
96+
public Long getSourceOfferingId() {
97+
return sourceOfferingId;
98+
}
99+
100+
public String getName() {
101+
return name;
102+
}
103+
104+
public String getExternalId() {
105+
return externalId;
106+
}
107+
108+
public Long getZoneId() {
109+
return zoneId;
110+
}
111+
112+
public String getDescription() {
113+
return description;
114+
}
115+
116+
public Boolean getUserDrivenBackups() {
117+
return userDrivenBackups;
118+
}
119+
120+
public List<Long> getDomainIds() {
121+
if (domainIds != null && !domainIds.isEmpty()) {
122+
return Arrays.asList(Arrays.stream(domainIds.split(",")).map(domainId -> Long.parseLong(domainId.trim())).toArray(Long[]::new));
123+
}
124+
LongFunction<List<Long>> defaultDomainsProvider = null;
125+
if (backupManager != null) {
126+
defaultDomainsProvider = backupManager::getBackupOfferingDomains;
127+
}
128+
return resolveDomainIds(domainIds, sourceOfferingId, defaultDomainsProvider, "backup offering");
129+
}
130+
131+
/////////////////////////////////////////////////////
132+
/////////////// API Implementation///////////////////
133+
/////////////////////////////////////////////////////
134+
135+
@Override
136+
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
137+
try {
138+
BackupOffering policy = backupManager.cloneBackupOffering(this);
139+
if (policy == null) {
140+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to clone backup offering");
141+
}
142+
BackupOfferingResponse response = _responseGenerator.createBackupOfferingResponse(policy);
143+
response.setResponseName(getCommandName());
144+
setResponseObject(response);
145+
} catch (InvalidParameterValueException e) {
146+
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, e.getMessage());
147+
} catch (CloudRuntimeException e) {
148+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
149+
}
150+
}
151+
152+
@Override
153+
public long getEntityOwnerId() {
154+
return CallContext.current().getCallingAccount().getId();
155+
}
156+
157+
@Override
158+
public String getEventType() {
159+
return EventTypes.EVENT_VM_BACKUP_OFFERING_CLONE;
160+
}
161+
162+
@Override
163+
public String getEventDescription() {
164+
return "Cloning backup offering: " + name + " from source offering: " + (sourceOfferingId == null ? "" : sourceOfferingId.toString());
165+
}
166+
}

api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
public class ImportBackupOfferingCmd extends BaseAsyncCmd {
5555

5656
@Inject
57-
private BackupManager backupManager;
57+
protected BackupManager backupManager;
5858

5959
/////////////////////////////////////////////////////
6060
//////////////// API parameters /////////////////////
@@ -86,7 +86,8 @@ public class ImportBackupOfferingCmd extends BaseAsyncCmd {
8686
type = CommandType.LIST,
8787
collectionType = CommandType.UUID,
8888
entityType = DomainResponse.class,
89-
description = "the ID of the containing domain(s), null for public offerings")
89+
description = "the ID of the containing domain(s), null for public offerings",
90+
since = "4.23.0")
9091
private List<Long> domainIds;
9192

9293
/////////////////////////////////////////////////////

0 commit comments

Comments
 (0)