Skip to content

Commit ee3f111

Browse files
committed
VM ingestion feature allows CloudStack to discover, on-board, import existing VMs in an infra. The feature currently works only for VMware, with a hypervisor agnostic framework which may be extended for KVM and XenServer in future.
Two new APIs have been added, listUnmanagedInstances and importUnmanagedInstance. listUnmanagedInstances API will list all unmanaged virtual machine for a give cluster. Optionally, name for an existing unmanaged virtual machine can be given to retrieve VM details. API request params - clusterid(UUID of cluster) name(instance name) Response - clusterid hostid name osdisplayname memory powerstate cpuCoresPerSocket cpunumber cpuspeed disk - id - capacity - controller - controllerunit - imagepath - position nic - id - macaddress - networkname - vlanid - pcislot importUnmanagedInstance API will import an exisitng unmanaged virtual machine into CloudStack for a given cluster and virtual machine name. Service offering for the VM, disk offerings for volumes and networks for NICs of the VM can be mapped. Some optional parameters like projectid, domainid, hostname, details, etc can also be given. Appropritate networks, service offering and disk offerings need to be present before import and cannot be created on the fly during the API call. API request params - clusterid(UUID of cluster) name(instance name) displayname hostname domainid projectid templateid serviceofferingid diskofferingid(UUID of disk offering for root disk) nicnetworklist(Map for NIC ID and corresponding Network UUID) datadiskofferinglist(Map for disk ID and corresponding disk offering UUID) details(Map for VM details) Response - Same response as that of deployVirtualMachine API Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
1 parent 7c7efe7 commit ee3f111

File tree

27 files changed

+2376
-14
lines changed

27 files changed

+2376
-14
lines changed

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
import java.util.HashMap;
2020
import java.util.Map;
2121

22+
import org.apache.cloudstack.acl.Role;
23+
import org.apache.cloudstack.acl.RolePermission;
24+
import org.apache.cloudstack.annotation.Annotation;
25+
import org.apache.cloudstack.config.Configuration;
26+
import org.apache.cloudstack.ha.HAConfig;
27+
import org.apache.cloudstack.usage.Usage;
28+
2229
import com.cloud.dc.DataCenter;
2330
import com.cloud.dc.Pod;
2431
import com.cloud.dc.StorageNetworkIpRange;
@@ -69,12 +76,6 @@
6976
import com.cloud.vm.Nic;
7077
import com.cloud.vm.NicSecondaryIp;
7178
import com.cloud.vm.VirtualMachine;
72-
import org.apache.cloudstack.acl.Role;
73-
import org.apache.cloudstack.acl.RolePermission;
74-
import org.apache.cloudstack.annotation.Annotation;
75-
import org.apache.cloudstack.config.Configuration;
76-
import org.apache.cloudstack.ha.HAConfig;
77-
import org.apache.cloudstack.usage.Usage;
7879

7980
public class EventTypes {
8081

@@ -96,6 +97,7 @@ public class EventTypes {
9697
public static final String EVENT_VM_MOVE = "VM.MOVE";
9798
public static final String EVENT_VM_RESTORE = "VM.RESTORE";
9899
public static final String EVENT_VM_EXPUNGE = "VM.EXPUNGE";
100+
public static final String EVENT_VM_INGEST = "VM.INGEST";
99101

100102
// Domain Router
101103
public static final String EVENT_ROUTER_CREATE = "ROUTER.CREATE";
@@ -594,6 +596,7 @@ public class EventTypes {
594596
entityEventDetails.put(EVENT_VM_MOVE, VirtualMachine.class);
595597
entityEventDetails.put(EVENT_VM_RESTORE, VirtualMachine.class);
596598
entityEventDetails.put(EVENT_VM_EXPUNGE, VirtualMachine.class);
599+
entityEventDetails.put(EVENT_VM_INGEST, VirtualMachine.class);
597600

598601
entityEventDetails.put(EVENT_ROUTER_CREATE, VirtualRouter.class);
599602
entityEventDetails.put(EVENT_ROUTER_DESTROY, VirtualRouter.class);

api/src/main/java/com/cloud/vm/UserVmService.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// under the License.
1717
package com.cloud.vm;
1818

19+
import java.util.LinkedHashMap;
1920
import java.util.List;
2021
import java.util.Map;
2122

@@ -513,4 +514,8 @@ UserVm upgradeVirtualMachine(ScaleVMCmd cmd) throws ResourceUnavailableException
513514

514515
void collectVmNetworkStatistics (UserVm userVm);
515516

517+
UserVm ingestVm(final DataCenter zone, final Host host, final VirtualMachineTemplate template, final String hostName, final String displayName, final Account owner, final String userData, final Account caller, final Boolean isDisplayVm, final String keyboard,
518+
final long accountId, final long userId, final ServiceOffering serviceOffering, final DiskOffering rootDiskOffering, final String sshPublicKey, final LinkedHashMap<String, NicProfile> networkNicMap,
519+
final String instanceName, final HypervisorType hypervisorType, final Map<String, String> customParameters, final VirtualMachine.PowerState powerState) throws InsufficientCapacityException;
520+
516521
}
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
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+
18+
package org.apache.cloudstack.api.command.admin.ingestion;
19+
20+
import java.util.Collection;
21+
import java.util.HashMap;
22+
import java.util.Iterator;
23+
import java.util.Map;
24+
25+
import javax.inject.Inject;
26+
27+
import org.apache.cloudstack.api.ACL;
28+
import org.apache.cloudstack.api.APICommand;
29+
import org.apache.cloudstack.api.ApiConstants;
30+
import org.apache.cloudstack.api.BaseAsyncCmd;
31+
import org.apache.cloudstack.api.Parameter;
32+
import org.apache.cloudstack.api.ResponseObject;
33+
import org.apache.cloudstack.api.ServerApiException;
34+
import org.apache.cloudstack.api.response.ClusterResponse;
35+
import org.apache.cloudstack.api.response.DiskOfferingResponse;
36+
import org.apache.cloudstack.api.response.DomainResponse;
37+
import org.apache.cloudstack.api.response.ProjectResponse;
38+
import org.apache.cloudstack.api.response.ServiceOfferingResponse;
39+
import org.apache.cloudstack.api.response.TemplateResponse;
40+
import org.apache.cloudstack.api.response.UserVmResponse;
41+
import org.apache.cloudstack.context.CallContext;
42+
import org.apache.cloudstack.ingestion.VmIngestionService;
43+
import org.apache.log4j.Logger;
44+
45+
import com.cloud.event.EventTypes;
46+
import com.cloud.exception.ConcurrentOperationException;
47+
import com.cloud.exception.InsufficientCapacityException;
48+
import com.cloud.exception.InvalidParameterValueException;
49+
import com.cloud.exception.NetworkRuleConflictException;
50+
import com.cloud.exception.ResourceAllocationException;
51+
import com.cloud.exception.ResourceUnavailableException;
52+
import com.cloud.network.Network;
53+
import com.cloud.offering.DiskOffering;
54+
import com.cloud.offering.ServiceOffering;
55+
import com.cloud.org.Cluster;
56+
import com.cloud.user.Account;
57+
58+
@APICommand(name = ImportUnmanageInstanceCmd.API_NAME,
59+
description = "Import unmanaged virtual machine from a given cluster/host.",
60+
responseObject = UserVmResponse.class,
61+
responseView = ResponseObject.ResponseView.Full,
62+
requestHasSensitiveInfo = false,
63+
responseHasSensitiveInfo = true)
64+
public class ImportUnmanageInstanceCmd extends BaseAsyncCmd {
65+
public static final Logger s_logger = Logger.getLogger(ImportUnmanageInstanceCmd.class.getName());
66+
public static final String API_NAME = "importUnmanagedInstance";
67+
68+
@Inject
69+
public VmIngestionService vmIngestionService;
70+
71+
@Parameter(name = ApiConstants.CLUSTER_ID,
72+
type = CommandType.UUID,
73+
entityType = ClusterResponse.class,
74+
required = true,
75+
description = "the cluster ID")
76+
private Long clusterId;
77+
78+
@Parameter(name = ApiConstants.NAME,
79+
type = CommandType.STRING,
80+
required = true,
81+
description = "the hypervisor name of the instance")
82+
private String name;
83+
84+
@Parameter(name = ApiConstants.DISPLAY_NAME,
85+
type = CommandType.STRING,
86+
description = "the display name of the instance")
87+
private String displayName;
88+
89+
@Parameter(name = ApiConstants.HOST_NAME,
90+
type = CommandType.STRING,
91+
description = "the host name of the instance")
92+
private String hostName;
93+
94+
@Parameter(name = ApiConstants.ACCOUNT,
95+
type = CommandType.STRING,
96+
description = "an optional account for the virtual machine. Must be used with domainId.")
97+
private String accountName;
98+
99+
@Parameter(name = ApiConstants.DOMAIN_ID,
100+
type = CommandType.UUID,
101+
entityType = DomainResponse.class,
102+
description = "import instance to the domain specified")
103+
private Long domainId;
104+
105+
@Parameter(name = ApiConstants.PROJECT_ID,
106+
type = CommandType.UUID,
107+
entityType = ProjectResponse.class,
108+
description = "import instance for the project")
109+
private Long projectId;
110+
111+
@ACL
112+
@Parameter(name = ApiConstants.TEMPLATE_ID,
113+
type = CommandType.UUID,
114+
entityType = TemplateResponse.class,
115+
required = true,
116+
description = "the ID of the template for the virtual machine")
117+
private Long templateId;
118+
119+
@ACL
120+
@Parameter(name = ApiConstants.SERVICE_OFFERING_ID,
121+
type = CommandType.UUID,
122+
entityType = ServiceOfferingResponse.class,
123+
required = true,
124+
description = "the ID of the service offering for the virtual machine")
125+
private Long serviceOfferingId;
126+
127+
@ACL
128+
@Parameter(name = ApiConstants.DISK_OFFERING_ID,
129+
type = CommandType.UUID,
130+
entityType = DiskOfferingResponse.class,
131+
required = true,
132+
description = "the ID of the root disk offering for the virtual machine")
133+
private Long diskOfferingId;
134+
135+
@Parameter(name = "nicnetworklist",
136+
type = CommandType.MAP,
137+
description = "VM nic to network id mapping")
138+
private Map nicNetworkList;
139+
140+
@Parameter(name = ApiConstants.DATADISK_OFFERING_LIST,
141+
type = CommandType.MAP,
142+
description = "datadisk template to disk-offering mapping")
143+
private Map dataDiskToDiskOfferingList;
144+
145+
@Parameter(name = ApiConstants.DETAILS,
146+
type = CommandType.MAP,
147+
description = "used to specify the custom parameters.")
148+
private Map details;
149+
150+
public Long getClusterId() {
151+
return clusterId;
152+
}
153+
154+
public String getName() {
155+
return name;
156+
}
157+
158+
public String getDisplayName() {
159+
return displayName;
160+
}
161+
162+
public String getHostName() {
163+
return hostName;
164+
}
165+
166+
public String getAccountName() {
167+
return accountName;
168+
}
169+
170+
public Long getDomainId() {
171+
return domainId;
172+
}
173+
174+
public Long getTemplateId() {
175+
return templateId;
176+
}
177+
178+
public Long getProjectId() {
179+
return projectId;
180+
}
181+
182+
public Long getServiceOfferingId() {
183+
return serviceOfferingId;
184+
}
185+
186+
public Long getDiskOfferingId() {
187+
return diskOfferingId;
188+
}
189+
190+
public Map<String, Long> getNicNetworkList() {
191+
Map<String, Long> nicNetworkMap = new HashMap<>();
192+
if (nicNetworkList != null && !nicNetworkList.isEmpty()) {
193+
Collection parameterCollection = nicNetworkList.values();
194+
Iterator iter = parameterCollection.iterator();
195+
while (iter.hasNext()) {
196+
HashMap<String, String> value = (HashMap<String, String>)iter.next();
197+
String nic = value.get("nic");
198+
String networkUuid = value.get("network");
199+
if (_entityMgr.findByUuid(Network.class, networkUuid) != null) {
200+
nicNetworkMap.put(nic, _entityMgr.findByUuid(Network.class, networkUuid).getId());
201+
} else {
202+
throw new InvalidParameterValueException(String.format("Unable to find network ID: %s for NIC ID: %s", networkUuid, nic));
203+
}
204+
}
205+
}
206+
return nicNetworkMap;
207+
}
208+
209+
public Map<String, Long> getDataDiskToDiskOfferingList() {
210+
Map<String, Long> dataDiskToDiskOfferingMap = new HashMap<>();
211+
if (dataDiskToDiskOfferingList != null && !dataDiskToDiskOfferingList.isEmpty()) {
212+
Collection parameterCollection = dataDiskToDiskOfferingList.values();
213+
Iterator iter = parameterCollection.iterator();
214+
while (iter.hasNext()) {
215+
HashMap<String, String> value = (HashMap<String, String>)iter.next();
216+
String disk = value.get("disk");
217+
String offeringUuid = value.get("diskOffering");
218+
if (_entityMgr.findByUuid(DiskOffering.class, offeringUuid) != null) {
219+
dataDiskToDiskOfferingMap.put(disk, _entityMgr.findByUuid(DiskOffering.class, offeringUuid).getId());
220+
} else {
221+
throw new InvalidParameterValueException(String.format("Unable to find disk offering ID: %s for data disk ID: %s", offeringUuid, disk));
222+
}
223+
}
224+
}
225+
return dataDiskToDiskOfferingMap;
226+
}
227+
228+
public Map getDetails() {
229+
Map<String, String> customParameterMap = new HashMap<String, String>();
230+
if (details != null && details.size() != 0) {
231+
Collection parameterCollection = details.values();
232+
Iterator iter = parameterCollection.iterator();
233+
while (iter.hasNext()) {
234+
HashMap<String, String> value = (HashMap<String, String>)iter.next();
235+
for (Map.Entry<String,String> entry: value.entrySet()) {
236+
customParameterMap.put(entry.getKey(),entry.getValue());
237+
}
238+
}
239+
}
240+
return customParameterMap;
241+
}
242+
243+
@Override
244+
public String getEventType() {
245+
return EventTypes.EVENT_VM_INGEST;
246+
}
247+
248+
@Override
249+
public String getEventDescription() {
250+
return "Importing unmanaged VM";
251+
}
252+
253+
@Override
254+
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
255+
validateInput();
256+
UserVmResponse response = vmIngestionService.importUnmanagedInstance(this);
257+
response.setResponseName(getCommandName());
258+
setResponseObject(response);
259+
}
260+
261+
@Override
262+
public String getCommandName() {
263+
return API_NAME.toLowerCase() + BaseAsyncCmd.RESPONSE_SUFFIX;
264+
}
265+
266+
@Override
267+
public long getEntityOwnerId() {
268+
Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true);
269+
if (accountId == null) {
270+
Account account = CallContext.current().getCallingAccount();
271+
if (account != null) {
272+
accountId = account.getId();
273+
} else {
274+
accountId = Account.ACCOUNT_ID_SYSTEM;
275+
}
276+
}
277+
return accountId;
278+
}
279+
280+
private void validateInput() {
281+
if (_entityMgr.findById(Cluster.class, clusterId) == null) {
282+
throw new InvalidParameterValueException(String.format("Unable to find cluster with ID: %d", clusterId));
283+
}
284+
if (_entityMgr.findById(ServiceOffering.class, serviceOfferingId) == null) {
285+
throw new InvalidParameterValueException(String.format("Unable to find service offering with ID: %d", serviceOfferingId));
286+
}
287+
}
288+
}

0 commit comments

Comments
 (0)