From 3d9a92a57aababf27a371d4f4edc30757ca988f2 Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Mon, 23 Sep 2024 12:48:30 +0530 Subject: [PATCH 001/183] Introducing External Framework and Orchestrate Anything --- .../java/com/cloud/hypervisor/Hypervisor.java | 1 + .../com/cloud/network/NetworkService.java | 2 + .../main/java/com/cloud/storage/Storage.java | 1 + .../java/com/cloud/vm/VmDetailConstants.java | 5 + .../apache/cloudstack/api/ApiConstants.java | 2 + .../command/admin/cluster/AddClusterCmd.java | 9 +- .../api/command/admin/host/AddHostCmd.java | 23 + .../api/command/admin/host/UpdateHostCmd.java | 16 + .../offering/CreateServiceOfferingCmd.java | 15 + .../api/command/admin/vm/MigrateVMCmd.java | 5 + .../user/template/ListTemplatesCmd.java | 7 + .../user/template/RegisterTemplateCmd.java | 26 ++ .../api/command/user/vm/DeployVMCmd.java | 16 + .../api/response/ClusterResponse.java | 12 + .../cloudstack/api/response/HostResponse.java | 12 + .../api/response/TemplateResponse.java | 8 + client/pom.xml | 5 + .../api/PostExternalProvisioningAnswer.java | 43 ++ .../api/PostExternalProvisioningCommand.java | 46 ++ .../PrepareExternalProvisioningAnswer.java | 43 ++ .../PrepareExternalProvisioningCommand.java | 46 ++ .../com/cloud/agent/api/RebootCommand.java | 11 + .../java/com/cloud/agent/api/StopCommand.java | 19 + debian/cloudstack-common.install | 1 + .../cloud/hypervisor/ExternalProvisioner.java | 60 +++ engine/orchestration/pom.xml | 5 + .../cloud/vm/VirtualMachineManagerImpl.java | 110 ++++- .../orchestration/NetworkOrchestrator.java | 102 +++++ .../java/com/cloud/dc/dao/ClusterDao.java | 2 + .../java/com/cloud/dc/dao/ClusterDaoImpl.java | 22 + .../PhysicalNetworkTrafficTypeDaoImpl.java | 2 +- .../storage/image/TemplateServiceImpl.java | 2 + plugins/hypervisors/external/pom.xml | 44 ++ .../agent/manager/ExternalAgentManager.java | 37 ++ .../manager/ExternalAgentManagerImpl.java | 144 ++++++ .../agent/manager/ExternalServerPlanner.java | 152 +++++++ .../manager/ExternalTemplateAdapter.java | 267 +++++++++++ .../discoverer/ExternalServerDiscoverer.java | 248 ++++++++++ .../SimpleExternalProvisioner.java | 422 ++++++++++++++++++ .../resource/ExternalResourceBase.java | 276 ++++++++++++ .../guru/ExternalHypervisorGuru.java | 144 ++++++ .../core/spring-external-core-context.xml | 48 ++ .../external-compute/module.properties | 18 + .../spring-external-compute-context.xml | 23 + .../external-discoverer/module.properties | 18 + .../spring-external-discoverer-context.xml | 34 ++ .../external-planner/module.properties | 18 + .../spring-external-planner-context.xml | 34 ++ .../external-storage/module.properties | 18 + .../spring-external-storage-context.xml | 32 ++ plugins/pom.xml | 1 + .../powerOperations.sh | 77 ++++ .../simpleServerProvisioner/provisioner.sh | 79 ++++ .../java/com/cloud/api/ApiResponseHelper.java | 6 + .../com/cloud/api/query/QueryManagerImpl.java | 27 +- .../cloud/api/query/dao/HostJoinDaoImpl.java | 8 +- .../api/query/dao/TemplateJoinDaoImpl.java | 22 +- .../cloud/capacity/CapacityManagerImpl.java | 16 + .../java/com/cloud/configuration/Config.java | 2 +- .../ConfigurationManagerImpl.java | 30 ++ .../deploy/DeploymentPlanningManagerImpl.java | 10 +- .../com/cloud/deploy/FirstFitPlanner.java | 2 +- .../hypervisor/HypervisorGuruManagerImpl.java | 15 +- .../com/cloud/network/NetworkServiceImpl.java | 11 + .../network/element/VirtualRouterElement.java | 2 +- .../cloud/network/guru/GuestNetworkGuru.java | 6 + .../network/router/NetworkHelperImpl.java | 11 +- .../com/cloud/network/vpc/VpcManagerImpl.java | 1 + .../com/cloud/resource/DiscovererBase.java | 3 + .../cloud/resource/ResourceManagerImpl.java | 97 +++- .../cloud/storage/VolumeApiServiceImpl.java | 24 +- .../com/cloud/template/TemplateAdapter.java | 1 + .../cloud/template/TemplateAdapterBase.java | 11 +- .../cloud/template/TemplateManagerImpl.java | 17 +- .../java/com/cloud/vm/UserVmManagerImpl.java | 6 +- .../vm/snapshot/VMSnapshotManagerImpl.java | 4 + .../ConsoleAccessManagerImpl.java | 5 + .../spring-server-core-managers-context.xml | 1 + .../com/cloud/vm/UserVmManagerImplTest.java | 2 +- .../com/cloud/vpc/MockNetworkManagerImpl.java | 5 + ui/public/locales/en.json | 7 + ui/src/components/view/DetailSettings.vue | 7 +- ui/src/config/section/image.js | 2 +- ui/src/config/section/infra/clusters.js | 2 +- ui/src/config/section/infra/hosts.js | 2 +- ui/src/config/section/offering.js | 2 +- ui/src/views/compute/DeployVM.vue | 114 ++++- .../views/image/RegisterOrUploadTemplate.vue | 179 +++++++- ui/src/views/infra/ClusterAdd.vue | 49 +- ui/src/views/infra/HostAdd.vue | 114 ++++- ui/src/views/offering/AddComputeOffering.vue | 137 +++++- 91 files changed, 3695 insertions(+), 78 deletions(-) create mode 100644 core/src/main/java/com/cloud/agent/api/PostExternalProvisioningAnswer.java create mode 100644 core/src/main/java/com/cloud/agent/api/PostExternalProvisioningCommand.java create mode 100644 core/src/main/java/com/cloud/agent/api/PrepareExternalProvisioningAnswer.java create mode 100644 core/src/main/java/com/cloud/agent/api/PrepareExternalProvisioningCommand.java create mode 100644 engine/components-api/src/main/java/com/cloud/hypervisor/ExternalProvisioner.java create mode 100644 plugins/hypervisors/external/pom.xml create mode 100644 plugins/hypervisors/external/src/main/java/com/cloud/agent/manager/ExternalAgentManager.java create mode 100644 plugins/hypervisors/external/src/main/java/com/cloud/agent/manager/ExternalAgentManagerImpl.java create mode 100644 plugins/hypervisors/external/src/main/java/com/cloud/agent/manager/ExternalServerPlanner.java create mode 100644 plugins/hypervisors/external/src/main/java/com/cloud/agent/manager/ExternalTemplateAdapter.java create mode 100644 plugins/hypervisors/external/src/main/java/com/cloud/hypervisor/external/discoverer/ExternalServerDiscoverer.java create mode 100644 plugins/hypervisors/external/src/main/java/com/cloud/hypervisor/external/provisioner/simpleprovisioner/SimpleExternalProvisioner.java create mode 100644 plugins/hypervisors/external/src/main/java/com/cloud/hypervisor/external/resource/ExternalResourceBase.java create mode 100644 plugins/hypervisors/external/src/main/java/org/apache/cloudstack/guru/ExternalHypervisorGuru.java create mode 100644 plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/core/spring-external-core-context.xml create mode 100644 plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-compute/module.properties create mode 100644 plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-compute/spring-external-compute-context.xml create mode 100644 plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-discoverer/module.properties create mode 100644 plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-discoverer/spring-external-discoverer-context.xml create mode 100644 plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-planner/module.properties create mode 100644 plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-planner/spring-external-planner-context.xml create mode 100644 plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-storage/module.properties create mode 100644 plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-storage/spring-external-storage-context.xml create mode 100644 scripts/vm/hypervisor/external/simpleServerProvisioner/powerOperations.sh create mode 100644 scripts/vm/hypervisor/external/simpleServerProvisioner/provisioner.sh diff --git a/api/src/main/java/com/cloud/hypervisor/Hypervisor.java b/api/src/main/java/com/cloud/hypervisor/Hypervisor.java index 27ffef1c3708..7cad02712c73 100644 --- a/api/src/main/java/com/cloud/hypervisor/Hypervisor.java +++ b/api/src/main/java/com/cloud/hypervisor/Hypervisor.java @@ -54,6 +54,7 @@ public enum Functionality { public static final HypervisorType Ovm3 = new HypervisorType("Ovm3", ImageFormat.RAW); public static final HypervisorType LXC = new HypervisorType("LXC"); public static final HypervisorType Custom = new HypervisorType("Custom", null, EnumSet.of(RootDiskSizeOverride)); + public static final HypervisorType External = new HypervisorType("External"); public static final HypervisorType Any = new HypervisorType("Any"); /*If you don't care about the hypervisor type*/ private final String name; private final ImageFormat imageFormat; diff --git a/api/src/main/java/com/cloud/network/NetworkService.java b/api/src/main/java/com/cloud/network/NetworkService.java index b8dd464b3655..b5915539bb2b 100644 --- a/api/src/main/java/com/cloud/network/NetworkService.java +++ b/api/src/main/java/com/cloud/network/NetworkService.java @@ -268,4 +268,6 @@ Network createPrivateNetwork(String networkName, String displayText, long physic InternalLoadBalancerElementService getInternalLoadBalancerElementByNetworkServiceProviderId(long networkProviderId); InternalLoadBalancerElementService getInternalLoadBalancerElementById(long providerId); List getInternalLoadBalancerElements(); + + String getNsxSegmentId(long domainId, long accountId, long zoneId, Long vpcId, long networkId); } diff --git a/api/src/main/java/com/cloud/storage/Storage.java b/api/src/main/java/com/cloud/storage/Storage.java index 05b8b3ab7a86..1ad3731b9eaa 100644 --- a/api/src/main/java/com/cloud/storage/Storage.java +++ b/api/src/main/java/com/cloud/storage/Storage.java @@ -30,6 +30,7 @@ public static enum ImageFormat { OVA(true, true, true, "ova"), VHDX(true, true, true, "vhdx"), BAREMETAL(false, false, false, "BAREMETAL"), + EXTERNAL(false, false, false, "EXTERNAL"), VMDK(true, true, false, "vmdk"), VDI(true, true, false, "vdi"), TAR(false, false, false, "tar"), diff --git a/api/src/main/java/com/cloud/vm/VmDetailConstants.java b/api/src/main/java/com/cloud/vm/VmDetailConstants.java index a6c9b6eba16b..a6f3ce0695e2 100644 --- a/api/src/main/java/com/cloud/vm/VmDetailConstants.java +++ b/api/src/main/java/com/cloud/vm/VmDetailConstants.java @@ -110,4 +110,9 @@ public interface VmDetailConstants { // CPU mode and model, ADMIN only String GUEST_CPU_MODE = "guest.cpu.mode"; String GUEST_CPU_MODEL = "guest.cpu.model"; + String MAC_ADDRESS = "mac_address"; + String EXPUNGE_EXTERNAL_SERVER = "expunge.external.server"; + String EXTERNAL_DETAIL_PREFIX = "External:"; + String CLOUDSTACK_VM_DETAILS = "cloudstack.vm.details"; + String CLOUDSTACK_VLAN = "cloudstack.vlan"; } diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index acce2bc77264..0c52cd243f3f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -207,6 +207,8 @@ public class ApiConstants { public static final String EXTRA_DHCP_OPTION_VALUE = "extradhcpvalue"; public static final String EXTERNAL = "external"; public static final String EXTERNAL_UUID = "externaluuid"; + public static final String EXTERNAL_PROVISIONER = "externalprovisioner"; + public static final String EXTERNAL_DETAILS = "externaldetails"; public static final String FENCE = "fence"; public static final String FETCH_LATEST = "fetchlatest"; public static final String FILESYSTEM = "filesystem"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java index 69cb43ce40ec..0f6e9b562318 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java @@ -65,9 +65,12 @@ public class AddClusterCmd extends BaseCmd { @Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, required = true, - description = "hypervisor type of the cluster: XenServer,KVM,VMware,Hyperv,BareMetal,Simulator,Ovm3") + description = "hypervisor type of the cluster: XenServer,KVM,VMware,Hyperv,BareMetal,Simulator,Ovm3,External") private String hypervisor; + @Parameter(name = ApiConstants.EXTERNAL_PROVISIONER, type = CommandType.STRING, description = "Name of the provisioner for the external host, this is mandatory input in case of hypervisor type external") + private String provisioner; + @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, description = "the CPU arch of the cluster. Valid options are: x86_64, aarch64", since = "4.20") @@ -184,6 +187,10 @@ public String getHypervisor() { return hypervisor; } + public String getExternalProvisioner() { + return provisioner; + } + public String getClusterType() { return clusterType; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/AddHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/AddHostCmd.java index ca27837aa881..b2359e1a4515 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/AddHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/AddHostCmd.java @@ -17,9 +17,12 @@ package org.apache.cloudstack.api.command.admin.host; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import com.cloud.vm.VmDetailConstants; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; @@ -75,6 +78,12 @@ public class AddHostCmd extends BaseCmd { @Parameter(name = ApiConstants.HOST_TAGS, type = CommandType.LIST, collectionType = CommandType.STRING, description = "list of tags to be added to the host") private List hostTags; + @Parameter(name = ApiConstants.EXTERNAL_PROVISIONER, type = CommandType.STRING, description = "Name of the provisioner for the external host, this is mandatory input in case of hypervisor type external") + private String provisioner; + + @Parameter(name = ApiConstants.EXTERNAL_DETAILS, type = CommandType.MAP, description = "Details in key/value pairs using format externaldetails[i].keyname=keyvalue. Example: externaldetails[0].endpoint.url=urlvalue") + protected Map externalDetails; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -111,6 +120,10 @@ public String getHypervisor() { return hypervisor; } + public String getExternalProvisioner() { + return provisioner; + } + public List getHostTags() { return hostTags; } @@ -119,6 +132,16 @@ public String getAllocationState() { return allocationState; } + public Map getExternalDetails() { + Map customparameterMap = convertDetailsToMap(externalDetails); + Map details = new HashMap<>(); + for (String key : customparameterMap.keySet()) { + String value = customparameterMap.get(key); + details.put(VmDetailConstants.EXTERNAL_DETAIL_PREFIX + key, value); + } + return details; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java index 397f9c80735e..ead94539f770 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java @@ -18,6 +18,7 @@ import com.cloud.host.Host; import com.cloud.user.Account; +import com.cloud.vm.VmDetailConstants; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -28,7 +29,9 @@ import org.apache.cloudstack.api.response.GuestOSCategoryResponse; import org.apache.cloudstack.api.response.HostResponse; +import java.util.HashMap; import java.util.List; +import java.util.Map; @APICommand(name = "updateHost", description = "Updates a host.", responseObject = HostResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -67,6 +70,9 @@ public class UpdateHostCmd extends BaseCmd { @Parameter(name = ApiConstants.ANNOTATION, type = CommandType.STRING, description = "Add an annotation to this host", since = "4.11", authorized = {RoleType.Admin}) private String annotation; + @Parameter(name = ApiConstants.EXTERNAL_DETAILS, type = CommandType.MAP, description = "Details in key/value pairs using format externaldetails[i].keyname=keyvalue. Example: externaldetails[0].endpoint.url=urlvalue") + protected Map externalDetails; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -103,6 +109,16 @@ public String getAnnotation() { return annotation; } + public Map getExternalDetails() { + Map customparameterMap = convertDetailsToMap(externalDetails); + Map details = new HashMap<>(); + for (String key : customparameterMap.keySet()) { + String value = customparameterMap.get(key); + details.put(VmDetailConstants.EXTERNAL_DETAIL_PREFIX + key, value); + } + return details; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java index 8f6d5413d72d..911e86ca5fa4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Set; +import com.cloud.vm.VmDetailConstants; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; @@ -251,6 +252,8 @@ public class CreateServiceOfferingCmd extends BaseCmd { since="4.20") private Boolean purgeResources; + @Parameter(name = ApiConstants.EXTERNAL_DETAILS, type = CommandType.MAP, description = "Details in key/value pairs using format externaldetails[i].keyname=keyvalue. Example: externaldetails[0].endpoint.url=urlvalue") + protected Map externalDetails; ///////////////////////////////////////////////////// @@ -359,9 +362,21 @@ public Map getDetails() { } } } + + detailsMap.putAll(getExternalDetails()); return detailsMap; } + public Map getExternalDetails() { + Map customparameterMap = convertDetailsToMap(externalDetails); + Map details = new HashMap<>(); + for (String key : customparameterMap.keySet()) { + String value = customparameterMap.get(key); + details.put(VmDetailConstants.EXTERNAL_DETAIL_PREFIX + key, value); + } + return details; + } + public Long getRootDiskSize() { return rootDiskSize; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVMCmd.java index 8881a2bc354e..0bc993ef1f71 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVMCmd.java @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.vm; +import com.cloud.hypervisor.Hypervisor; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.APICommand; @@ -145,6 +146,10 @@ public void execute() { throw new InvalidParameterValueException("Unable to find the VM by id=" + getVirtualMachineId()); } + if (Hypervisor.HypervisorType.External.equals(userVm.getHypervisorType())) { + throw new InvalidParameterValueException("Migrate VM instance operation is not allowed for External hypervisor type"); + } + Host destinationHost = null; // OfflineMigration performed when this parameter is specified StoragePool destStoragePool = null; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java index bff65ef70a92..8d66e8075951 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java @@ -57,6 +57,9 @@ public class ListTemplatesCmd extends BaseListTaggedResourcesCmd implements User @Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, description = "the hypervisor for which to restrict the search") private String hypervisor; + @Parameter(name = ApiConstants.EXTERNAL_PROVISIONER, type = CommandType.STRING, description = "Name of the provisioner for the external host, this is mandatory input in case of hypervisor type external") + private String provisioner; + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = TemplateResponse.class, description = "the template ID") private Long id; @@ -138,6 +141,10 @@ public String getHypervisor() { return hypervisor; } + public String getExternalProvisioner() { + return provisioner; + } + public Long getId() { return id; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java index 1f968b869b99..170a21689f44 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java @@ -21,10 +21,12 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.vm.VmDetailConstants; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; @@ -74,6 +76,9 @@ public class RegisterTemplateCmd extends BaseCmd implements UserCmd { @Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, required = true, description = "the target hypervisor for the template") protected String hypervisor; + @Parameter(name = ApiConstants.EXTERNAL_PROVISIONER, type = CommandType.STRING, description = "Name of the provisioner for the external host, this is mandatory input in case of hypervisor type external") + private String provisioner; + @Parameter(name = ApiConstants.IS_FEATURED, type = CommandType.BOOLEAN, description = "true if this template is a featured template, false otherwise") private Boolean featured; @@ -178,6 +183,9 @@ public class RegisterTemplateCmd extends BaseCmd implements UserCmd { since = "4.20") private String arch; + @Parameter(name = ApiConstants.EXTERNAL_DETAILS, type = CommandType.MAP, description = "Details in key/value pairs using format externaldetails[i].keyname=keyvalue. Example: externaldetails[0].endpoint.url=urlvalue") + protected Map externalDetails; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -198,6 +206,10 @@ public String getHypervisor() { return hypervisor; } + public String getExternalProvisioner() { + return provisioner; + } + public Boolean isFeatured() { return featured; } @@ -303,6 +315,16 @@ public CPU.CPUArch getArch() { return CPU.CPUArch.fromType(arch); } + public Map getExternalDetails() { + Map customparameterMap = convertDetailsToMap(externalDetails); + Map details = new HashMap<>(); + for (String key : customparameterMap.keySet()) { + String value = customparameterMap.get(key); + details.put(VmDetailConstants.EXTERNAL_DETAIL_PREFIX + key, value); + } + return details; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -370,6 +392,10 @@ protected void validateParameters() { "is only allowed for KVM or %s templates", customHypervisor)); } + if (getHypervisor().equalsIgnoreCase(Hypervisor.HypervisorType.External.toString()) && getExternalProvisioner() == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("External provisioner input is required in case of hypervisor type external")); + } + if (!isDeployAsIs() && osTypeId == null) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please provide a guest OS type"); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index 52d42a95d981..e971b43a8065 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -278,6 +278,9 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG description = "Enable packed virtqueues or not.") private Boolean nicPackedVirtQueues; + @Parameter(name = ApiConstants.EXTERNAL_DETAILS, type = CommandType.MAP, description = "Details in key/value pairs using format externaldetails[i].keyname=keyvalue. Example: externaldetails[0].server.type=typevalue") + protected Map externalDetails; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -351,9 +354,22 @@ public Map getDetails() { customparameterMap.put(VmDetailConstants.NIC_PACKED_VIRTQUEUES_ENABLED, BooleanUtils.toStringTrueFalse(nicPackedVirtQueues)); } + if (MapUtils.isNotEmpty(externalDetails)) { + customparameterMap.putAll(getExternalDetails()); + } + return customparameterMap; } + public Map getExternalDetails() { + Map customparameterMap = convertDetailsToMap(externalDetails); + Map details = new HashMap<>(); + for (String key : customparameterMap.keySet()) { + String value = customparameterMap.get(key); + details.put(VmDetailConstants.EXTERNAL_DETAIL_PREFIX + key, value); + } + return details; + } public ApiConstants.BootMode getBootMode() { if (StringUtils.isNotBlank(bootMode)) { diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java index 1c69849239f9..cfe00fdcb83c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java @@ -59,6 +59,10 @@ public class ClusterResponse extends BaseResponseWithAnnotations { @Param(description = "the hypervisor type of the cluster") private String hypervisorType; + @SerializedName(ApiConstants.EXTERNAL_PROVISIONER) + @Param(description = "the provisioner name for the hypervisor type external") + private String externalProvisioner; + @SerializedName("clustertype") @Param(description = "the type of the cluster") private String clusterType; @@ -220,6 +224,14 @@ public void setResourceDetails(Map details) { } } + public void setExternalProvisioner(String externalProvisioner) { + this.externalProvisioner = externalProvisioner; + } + + public String getExternalProvisioner() { + return externalProvisioner; + } + public Map getResourceDetails() { return resourceDetails; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java index 091d6391b313..6b58c6b04499 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java @@ -91,6 +91,10 @@ public class HostResponse extends BaseResponseWithAnnotations { @Param(description = "the host hypervisor") private String hypervisor; + @SerializedName(ApiConstants.EXTERNAL_PROVISIONER) + @Param(description = "the provisioner name for the hypervisor type external") + private String externalProvisioner; + @SerializedName("cpusockets") @Param(description = "the number of CPU sockets on the host") private Integer cpuSockets; @@ -367,6 +371,14 @@ public void setHypervisor(String hypervisor) { this.hypervisor = hypervisor; } + public void setExternalProvisioner(String provisioner) { + this.externalProvisioner = provisioner; + } + + public String getExternalProvisioner() { + return externalProvisioner; + } + public void setCpuSockets(Integer cpuSockets) { this.cpuSockets = cpuSockets; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java index 98e96091d8c7..c1cb5957df26 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java @@ -131,6 +131,10 @@ public class TemplateResponse extends BaseResponseWithTagInformation implements @Param(description = "the hypervisor on which the template runs") private String hypervisor; + @SerializedName(ApiConstants.EXTERNAL_PROVISIONER) + @Param(description = "the provisioner name for the hypervisor type external") + private String provisioner; + @SerializedName(ApiConstants.DOMAIN) @Param(description = "the name of the domain to which the template belongs") private String domainName; @@ -357,6 +361,10 @@ public void setHypervisor(String hypervisor) { this.hypervisor = hypervisor; } + public void setExternalProvisioner(String provisioner) { + this.provisioner = provisioner; + } + @Override public void setDomainName(String domainName) { this.domainName = domainName; diff --git a/client/pom.xml b/client/pom.xml index 2b673d7750e9..d2ff28e8872b 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -347,6 +347,11 @@ cloud-plugin-hypervisor-hyperv ${project.version} + + org.apache.cloudstack + cloud-plugin-hypervisor-external + ${project.version} + org.apache.cloudstack cloud-plugin-storage-allocator-random diff --git a/core/src/main/java/com/cloud/agent/api/PostExternalProvisioningAnswer.java b/core/src/main/java/com/cloud/agent/api/PostExternalProvisioningAnswer.java new file mode 100644 index 000000000000..8c5e2a9369c1 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/PostExternalProvisioningAnswer.java @@ -0,0 +1,43 @@ +// +// 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 com.cloud.agent.api; + +import java.util.Map; + +public class PostExternalProvisioningAnswer extends Answer { + + Map serverDetails; + + public PostExternalProvisioningAnswer() { + super(); + } + + public PostExternalProvisioningAnswer(PostExternalProvisioningCommand cmd, Map serverDetails, String details) { + super(cmd, true, details); + this.serverDetails = serverDetails; + } + + public PostExternalProvisioningAnswer(PostExternalProvisioningCommand cmd, String details, boolean success) { + super(cmd, success, details); + } + + public Map getServerDetails() { + return serverDetails; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/PostExternalProvisioningCommand.java b/core/src/main/java/com/cloud/agent/api/PostExternalProvisioningCommand.java new file mode 100644 index 000000000000..cca0d8e79bab --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/PostExternalProvisioningCommand.java @@ -0,0 +1,46 @@ +// +// 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 com.cloud.agent.api; + +import java.util.Map; + +public class PostExternalProvisioningCommand extends Command { + + String vmUUID; + + Map accessDetails; + + public PostExternalProvisioningCommand(String vmUUID, Map accessDetails) { + this.vmUUID = vmUUID; + this.accessDetails = accessDetails; + } + + public String getVmUUID() { + return vmUUID; + } + + public Map getAccessDetails() { + return accessDetails; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/PrepareExternalProvisioningAnswer.java b/core/src/main/java/com/cloud/agent/api/PrepareExternalProvisioningAnswer.java new file mode 100644 index 000000000000..ff5b3a69c789 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/PrepareExternalProvisioningAnswer.java @@ -0,0 +1,43 @@ +// +// 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 com.cloud.agent.api; + +import java.util.Map; + +public class PrepareExternalProvisioningAnswer extends Answer { + + Map serverDetails; + + public PrepareExternalProvisioningAnswer() { + super(); + } + + public PrepareExternalProvisioningAnswer(PrepareExternalProvisioningCommand cmd, Map serverDetails, String details) { + super(cmd, true, details); + this.serverDetails = serverDetails; + } + + public PrepareExternalProvisioningAnswer(PrepareExternalProvisioningCommand cmd, String details, boolean success) { + super(cmd, success, details); + } + + public Map getServerDetails() { + return serverDetails; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/PrepareExternalProvisioningCommand.java b/core/src/main/java/com/cloud/agent/api/PrepareExternalProvisioningCommand.java new file mode 100644 index 000000000000..aa49a968e8d2 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/PrepareExternalProvisioningCommand.java @@ -0,0 +1,46 @@ +// +// 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 com.cloud.agent.api; + +import com.cloud.agent.api.to.VirtualMachineTO; + +public class PrepareExternalProvisioningCommand extends Command { + + VirtualMachineTO virtualMachineTO; + + Long clusterId; + + public PrepareExternalProvisioningCommand(VirtualMachineTO vmUUID, Long clusterId) { + this.virtualMachineTO = vmUUID; + this.clusterId = clusterId; + } + + public VirtualMachineTO getVirtualMachineTO() { + return virtualMachineTO; + } + + public Long getClusterId() { + return clusterId; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/RebootCommand.java b/core/src/main/java/com/cloud/agent/api/RebootCommand.java index 74ed76283514..56a11f7e7105 100644 --- a/core/src/main/java/com/cloud/agent/api/RebootCommand.java +++ b/core/src/main/java/com/cloud/agent/api/RebootCommand.java @@ -21,9 +21,12 @@ import com.cloud.agent.api.to.VirtualMachineTO; +import java.util.Map; + public class RebootCommand extends Command { String vmName; VirtualMachineTO vm; + private Map _details; protected boolean executeInSequence = false; protected RebootCommand() { @@ -46,6 +49,14 @@ public VirtualMachineTO getVirtualMachine() { return vm; } + public void setDetails(Map details) { + _details = details; + } + + public Map getDetails() { + return _details; + } + @Override public boolean executeInSequence() { return this.executeInSequence; diff --git a/core/src/main/java/com/cloud/agent/api/StopCommand.java b/core/src/main/java/com/cloud/agent/api/StopCommand.java index 3923a35bd0a7..38fb96e42ee8 100644 --- a/core/src/main/java/com/cloud/agent/api/StopCommand.java +++ b/core/src/main/java/com/cloud/agent/api/StopCommand.java @@ -37,6 +37,9 @@ public class StopCommand extends RebootCommand { boolean forceStop = false; private Map dpdkInterfaceMapping; Map vlanToPersistenceMap; + boolean expungeVM = false; + + private Map _details; public Map getDpdkInterfaceMapping() { return dpdkInterfaceMapping; @@ -138,4 +141,20 @@ public Map getVlanToPersistenceMap() { public void setVlanToPersistenceMap(Map vlanToPersistenceMap) { this.vlanToPersistenceMap = vlanToPersistenceMap; } + + public boolean isExpungeVM() { + return expungeVM; + } + + public void setExpungeVM(boolean expungeVM) { + this.expungeVM = expungeVM; + } + + public void setDetails(Map details) { + _details = details; + } + + public Map getDetails() { + return _details; + } } diff --git a/debian/cloudstack-common.install b/debian/cloudstack-common.install index 08f56d4f117f..af23ba5b1289 100644 --- a/debian/cloudstack-common.install +++ b/debian/cloudstack-common.install @@ -27,6 +27,7 @@ /usr/share/cloudstack-common/scripts/vm/hypervisor/versions.sh /usr/share/cloudstack-common/scripts/vm/hypervisor/vmware/* /usr/share/cloudstack-common/scripts/vm/hypervisor/xenserver/* +/usr/share/cloudstack-common/scripts/vm/hypervisor/external/simpleExternalProvisioner/* /usr/share/cloudstack-common/lib/* /usr/share/cloudstack-common/vms/* /usr/bin/cloudstack-set-guest-password diff --git a/engine/components-api/src/main/java/com/cloud/hypervisor/ExternalProvisioner.java b/engine/components-api/src/main/java/com/cloud/hypervisor/ExternalProvisioner.java new file mode 100644 index 000000000000..638b1fd243d9 --- /dev/null +++ b/engine/components-api/src/main/java/com/cloud/hypervisor/ExternalProvisioner.java @@ -0,0 +1,60 @@ +// 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 com.cloud.hypervisor; + +import com.cloud.agent.api.HostVmStateReportEntry; +import com.cloud.agent.api.PrepareExternalProvisioningAnswer; +import com.cloud.agent.api.PrepareExternalProvisioningCommand; +import com.cloud.agent.api.PostExternalProvisioningAnswer; +import com.cloud.agent.api.PostExternalProvisioningCommand; +import com.cloud.agent.api.RebootAnswer; +import com.cloud.agent.api.RebootCommand; +import com.cloud.agent.api.StartAnswer; +import com.cloud.agent.api.StartCommand; +import com.cloud.agent.api.StopAnswer; +import com.cloud.agent.api.StopCommand; +import com.cloud.utils.component.Adapter; + +import java.util.HashMap; + +public interface ExternalProvisioner extends Adapter { + /** + * Returns the unique name of the provider + * @return returns provider name + */ + String getName(); + + /** + * Returns description about the provider + * @return returns description + */ + String getDescription(); + + PrepareExternalProvisioningAnswer prepareExternalProvisioning(PrepareExternalProvisioningCommand cmd); + + StartAnswer startInstance(StartCommand cmd); + + StopAnswer stopInstance(StopCommand cmd); + + RebootAnswer rebootInstance(RebootCommand cmd); + + StopAnswer expungeInstance(StopCommand cmd); + + PostExternalProvisioningAnswer postsetupInstance(PostExternalProvisioningCommand cmd); + + HashMap getHostVmStateReport(Long hostId); +} diff --git a/engine/orchestration/pom.xml b/engine/orchestration/pom.xml index 437c98dac877..151f95ff9441 100755 --- a/engine/orchestration/pom.xml +++ b/engine/orchestration/pom.xml @@ -73,6 +73,11 @@ cloud-plugin-maintenance ${project.version} + + org.apache.cloudstack + cloud-plugin-hypervisor-external + ${project.version} + diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index e3307caa54b6..aba5ede56b29 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -49,6 +49,9 @@ import javax.naming.ConfigurationException; import javax.persistence.EntityExistsException; +import com.cloud.host.dao.HostDetailsDao; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.network.NetworkService; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; @@ -324,6 +327,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Inject private HostDao _hostDao; @Inject + private HostDetailsDao _hostDetailsDao; + @Inject private AlertManager _alertMgr; @Inject private GuestOSCategoryDao _guestOsCategoryDao; @@ -406,6 +411,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Inject private DomainDao domainDao; @Inject + public NetworkService _networkService; + @Inject ResourceCleanupService resourceCleanupService; @Inject VmWorkJobDao vmWorkJobDao; @@ -571,8 +578,8 @@ private void allocateRootVolume(VMInstanceVO vm, VirtualMachineTemplate template if (template.getFormat() == ImageFormat.ISO) { volumeMgr.allocateRawVolume(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskOfferingInfo.getSize(), rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), vm, template, owner, null); - } else if (template.getFormat() == ImageFormat.BAREMETAL) { - logger.debug("%s has format [{}]. Skipping ROOT volume [{}] allocation.", template.toString(), ImageFormat.BAREMETAL, rootVolumeName); + } else if (template.getFormat() == ImageFormat.BAREMETAL || template.getFormat() == ImageFormat.EXTERNAL) { + logger.debug(String.format("%s has format [%s]. Skipping ROOT volume [%s] allocation.", template.toString(), template.getFormat(), rootVolumeName)); } else { volumeMgr.allocateTemplatedVolumes(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskSizeFinal, rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), template, vm, owner); @@ -632,6 +639,13 @@ protected void advanceExpunge(VMInstanceVO vm) throws ResourceUnavailableExcepti return; } + if (HypervisorType.External.equals(vm.getHypervisorType())) { + UserVmVO userVM = _userVmDao.findById(vm.getId()); + _userVmDao.loadDetails(userVM); + userVM.setDetail(VmDetailConstants.EXPUNGE_EXTERNAL_SERVER, Boolean.TRUE.toString()); + _userVmDao.saveDetails(userVM); + } + advanceStop(vm.getUuid(), VmDestroyForcestop.value()); vm = _vmDao.findByUuid(vm.getUuid()); @@ -1271,7 +1285,7 @@ public void orchestrateStart(final String vmUuid, final Map ipAddressDetails = new HashMap<>(sshAccessDetails); ipAddressDetails.remove(NetworkElementCommand.ROUTER_NAME); + final HostVO vmHost = _hostDao.findById(destHostId); + if (HypervisorType.External.equals(vmHost.getHypervisorType())) { + Map accessDetails = vmTO.getDetails(); + loadExternalHostAccessDetails(vmHost, accessDetails); + loadExternalInstanceDetails(vm.getId(), accessDetails); + + NicTO[] nics = vmTO.getNics(); + for (NicTO nic : nics) { + if (nic.isDefaultNic()) { + Networks.BroadcastDomainType broadcastDomainType = Networks.BroadcastDomainType.getSchemeValue(nic.getBroadcastUri()); + NetworkVO networkVO = _networkDao.findById(nic.getNetworkId()); + if (Networks.BroadcastDomainType.NSX.equals(broadcastDomainType)) { + String segmentName = _networkService.getNsxSegmentId(networkVO.getDomainId(), networkVO.getAccountId(), networkVO.getDataCenterId(), networkVO.getVpcId(), networkVO.getId()); + accessDetails.put(VmDetailConstants.EXTERNAL_DETAIL_PREFIX + VmDetailConstants.CLOUDSTACK_VLAN, segmentName); + } else { + accessDetails.put(VmDetailConstants.EXTERNAL_DETAIL_PREFIX + VmDetailConstants.CLOUDSTACK_VLAN, Networks.BroadcastDomainType.getValue(nic.getBroadcastUri())); + } + } + } + } + StartCommand command = new StartCommand(vmTO, dest.getHost(), getExecuteInSequence(vm.getHypervisorType())); cmds.addCommand(command); @@ -1346,7 +1381,6 @@ public void orchestrateStart(final String vmUuid, final Map 0; retries--) { @@ -1469,6 +1503,32 @@ public void orchestrateStart(final String vmUuid, final Map accessDetails) { + _hostDao.loadDetails(vmHost); + Map hostDetails = vmHost.getDetails(); + Map externalHostDetails = new HashMap<>(); + for (Map.Entry entry : hostDetails.entrySet()) { + if (entry.getKey().startsWith(VmDetailConstants.EXTERNAL_DETAIL_PREFIX)) { + externalHostDetails.put(entry.getKey(), entry.getValue()); + } + } + + accessDetails.putAll(externalHostDetails); + } + + private void loadExternalInstanceDetails(long vmId, Map accessDetails) { + UserVmVO userVm = _userVmDao.findById(vmId); + _userVmDao.loadDetails(userVm); + Map userVmDetails = userVm.getDetails(); + Map externalInstanceDetails = new HashMap<>(); + for (Map.Entry entry : userVmDetails.entrySet()) { + if (entry.getKey().startsWith(VmDetailConstants.EXTERNAL_DETAIL_PREFIX)) { + externalInstanceDetails.put(entry.getKey(), entry.getValue()); + } + } + accessDetails.putAll(externalInstanceDetails); + } + public void setVmNetworkDetails(VMInstanceVO vm, VirtualMachineTO vmTO) { Map networkToNetworkNameMap = new HashMap<>(); if (VirtualMachine.Type.User.equals(vm.getType())) { @@ -1856,7 +1916,19 @@ private List> getVolumesToDisconnect(VirtualMachine vm) { protected boolean sendStop(final VirtualMachineGuru guru, final VirtualMachineProfile profile, final boolean force, final boolean checkBeforeCleanup) { final VirtualMachine vm = profile.getVirtualMachine(); Map vlanToPersistenceMap = getVlanToPersistenceMapForVM(vm.getId()); + StopCommand stpCmd = new StopCommand(vm, getExecuteInSequence(vm.getHypervisorType()), checkBeforeCleanup); + if (HypervisorType.External.equals(vm.getHypervisorType())) { + Long hostID = profile.getHostId(); + if (hostID != null) { + HostVO host = _hostDao.findById(hostID); + HashMap accessDetails = new HashMap<>(); + loadExternalHostAccessDetails(host, accessDetails); + loadExternalInstanceDetails(vm.getId(), accessDetails); + + stpCmd.setDetails(accessDetails); + } + } if (MapUtils.isNotEmpty(vlanToPersistenceMap)) { stpCmd.setVlanToPersistenceMap(vlanToPersistenceMap); } @@ -1987,7 +2059,7 @@ protected void releaseVmResources(final VirtualMachineProfile profile, final boo } try { - if (vm.getHypervisorType() != HypervisorType.BareMetal) { + if (vm.getHypervisorType() != HypervisorType.BareMetal && vm.getHypervisorType() != HypervisorType.External) { volumeMgr.release(profile); logger.debug("Successfully released storage resources for the VM {} in {} state", vm, state); } @@ -2184,6 +2256,18 @@ private void advanceStop(final VMInstanceVO vm, final boolean cleanUpEvenIfUnabl Map vlanToPersistenceMap = getVlanToPersistenceMapForVM(vm.getId()); final StopCommand stop = new StopCommand(vm, getExecuteInSequence(vm.getHypervisorType()), false, cleanUpEvenIfUnableToStop); stop.setControlIp(getControlNicIpForVM(vm)); + + if (HypervisorType.External.equals(vm.getHypervisorType())) { + Long hostID = profile.getHostId(); + HostVO host = _hostDao.findById(hostID); + + HashMap accessDetails = new HashMap<>(); + loadExternalHostAccessDetails(host, accessDetails); + loadExternalInstanceDetails(vm.getId(), accessDetails); + + stop.setDetails(accessDetails); + } + if (MapUtils.isNotEmpty(vlanToPersistenceMap)) { stop.setVlanToPersistenceMap(vlanToPersistenceMap); } @@ -2233,6 +2317,14 @@ private void advanceStop(final VMInstanceVO vm, final boolean cleanUpEvenIfUnabl } else { logger.warn("Unable to actually stop {} but continue with release because it's a force stop", vm); vmGuru.finalizeStop(profile, answer); + if (HypervisorType.External.equals(profile.getHypervisorType())) { + try { + stateTransitTo(vm, VirtualMachine.Event.OperationSucceeded, null); + } catch (final NoTransitionException e) { + logger.warn("Unable to transition the state " + vm, e); + } + } + } } else { if (VirtualMachine.systemVMs.contains(vm.getType())) { @@ -3633,6 +3725,14 @@ private void orchestrateReboot(final String vmUuid, final Map accessDetails = new HashMap<>(); + loadExternalHostAccessDetails(hostVo, accessDetails); + loadExternalInstanceDetails(vm.getId(), accessDetails); + + rebootCmd.setDetails(accessDetails); + } VirtualMachineTO vmTo = getVmTO(vm.getId()); checkAndSetEnterSetupMode(vmTo, params); rebootCmd.setVirtualMachine(vmTo); diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index f72b10cc0c09..cb223c772370 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -38,12 +38,22 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.agent.api.PrepareExternalProvisioningAnswer; +import com.cloud.agent.api.PrepareExternalProvisioningCommand; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.manager.ExternalAgentManagerImpl; import com.cloud.dc.ASNumberVO; import com.cloud.bgp.BGPService; import com.cloud.dc.VlanDetailsVO; import com.cloud.dc.dao.ASNumberDao; import com.cloud.dc.dao.VlanDetailsDao; +import com.cloud.host.dao.HostDetailsDao; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.hypervisor.HypervisorGuruManager; import com.cloud.network.dao.NsxProviderDao; +import com.cloud.vm.VmDetailConstants; +import com.cloud.vm.dao.UserVmDetailsDao; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; @@ -285,6 +295,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra @Inject UserVmDao _userVmDao; @Inject + UserVmDetailsDao _userVmDetailsDao; + @Inject AlertManager _alertMgr; @Inject ConfigurationManager _configMgr; @@ -358,6 +370,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra private ASNumberDao asNumberDao; @Inject private BGPService bgpService; + @Inject + private HypervisorGuruManager _hvGuruMgr; @Override public List getNetworkGurus() { @@ -425,6 +439,8 @@ public void setDhcpProviders(final List dhcpProviders) { @Inject HostDao _hostDao; @Inject + HostDetailsDao _hostDetailsDao; + @Inject NetworkServiceMapDao _ntwkSrvcDao; @Inject VpcManager _vpcMgr; @@ -2147,6 +2163,8 @@ public NicProfile prepareNic(final VirtualMachineProfile vmProfile, final Deploy final Integer networkRate = _networkModel.getNetworkRate(network.getId(), vmProfile.getId()); final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); + + prepareNicIfExternalProvisionerInvolved(vmProfile, dest, nicId); final NicVO nic = _nicDao.findById(nicId); NicProfile profile = null; @@ -2215,6 +2233,90 @@ public NicProfile prepareNic(final VirtualMachineProfile vmProfile, final Deploy return profile; } + private void prepareNicIfExternalProvisionerInvolved(VirtualMachineProfile vmProfile, DeployDestination dest, long nicId) { + HostVO host = _hostDao.findById(dest.getHost().getId()); + if (Hypervisor.HypervisorType.External.equals(vmProfile.getHypervisorType())) { + if (_userVmDetailsDao.findDetail(vmProfile.getId(), VmDetailConstants.DEPLOY_VM) == null) { + return; + } + HypervisorGuru hvGuru = _hvGuruMgr.getGuru(vmProfile.getHypervisorType()); + VirtualMachineTO vmTO = hvGuru.implement(vmProfile); + Map accessDetails = vmTO.getDetails(); + + loadExternalHostAccessDetails(host, accessDetails); + loadExternalInstanceDetails(vmProfile.getId(), accessDetails); + PrepareExternalProvisioningCommand command = new PrepareExternalProvisioningCommand(vmTO, host.getClusterId()); + final PrepareExternalProvisioningAnswer prepareExternalProvisioningAnswer; + try { + Long hostID = dest.getHost().getId(); + final Answer answer = _agentMgr.send(hostID, command); + + if (!(answer instanceof PrepareExternalProvisioningAnswer)) { + String errorMsg = String.format("Trying to prepare the instance on external hypervisor for the CloudStack instance %s failed: %s", vmProfile.getUuid(), answer.getDetails()); + logger.debug(errorMsg); + throw new CloudRuntimeException(errorMsg); + } + + prepareExternalProvisioningAnswer = (PrepareExternalProvisioningAnswer) answer; + } catch (AgentUnavailableException | OperationTimedoutException e) { + String errorMsg = String.format("Trying to prepare the instance on external hypervisor for the CloudStack instance %s failed: %s", vmProfile.getUuid(), e); + logger.debug(errorMsg); + throw new CloudRuntimeException(errorMsg); + } + + if (prepareExternalProvisioningAnswer == null || !prepareExternalProvisioningAnswer.getResult()) { + if (prepareExternalProvisioningAnswer != null && StringUtils.isNotBlank(prepareExternalProvisioningAnswer.getDetails())) { + throw new CloudRuntimeException(String.format("Unable to prepare the instance on external system due to %s", prepareExternalProvisioningAnswer.getDetails())); + } else { + throw new CloudRuntimeException("Unable to prepare the instance on external system, please check the access details"); + } + } + + Map serverDetails = prepareExternalProvisioningAnswer.getServerDetails(); + if (ExternalAgentManagerImpl.expectMacAddressFromExternalProvisioner.valueIn(host.getClusterId())) { + String macAddress = serverDetails.get(String.format("%s%s",VmDetailConstants.EXTERNAL_DETAIL_PREFIX, VmDetailConstants.MAC_ADDRESS)); + if (StringUtils.isEmpty(macAddress)) { + throw new CloudRuntimeException("Unable to fetch macaddress from the external provisioner while preparing the instance"); + } + final NicVO nic = _nicDao.findById(nicId); + nic.setMacAddress(macAddress); + _nicDao.update(nicId, nic); + } + + UserVmVO userVm = _userVmDao.findById(vmProfile.getId()); + _userVmDao.loadDetails(userVm); + Map details = userVm.getDetails(); + details.putAll(serverDetails); + userVm.setDetails(details); + _userVmDao.saveDetails(userVm); + } + } + + private void loadExternalHostAccessDetails(HostVO vmHost, Map accessDetails) { + _hostDao.loadDetails(vmHost); + Map hostDetails = vmHost.getDetails(); + Map externalHostDetails = new HashMap<>(); + for (Map.Entry entry : hostDetails.entrySet()) { + if (entry.getKey().startsWith(VmDetailConstants.EXTERNAL_DETAIL_PREFIX)) { + externalHostDetails.put(entry.getKey(), entry.getValue()); + } + } + accessDetails.putAll(externalHostDetails); + } + + private void loadExternalInstanceDetails(long vmId, Map accessDetails) { + UserVmVO userVm = _userVmDao.findById(vmId); + _userVmDao.loadDetails(userVm); + Map userVmDetails = userVm.getDetails(); + Map externalInstanceDetails = new HashMap<>(); + for (Map.Entry entry : userVmDetails.entrySet()) { + if (entry.getKey().startsWith(VmDetailConstants.EXTERNAL_DETAIL_PREFIX)) { + externalInstanceDetails.put(entry.getKey(), entry.getValue()); + } + } + accessDetails.putAll(externalInstanceDetails); + } + @Override public Map getExtraDhcpOptions(long nicId) { List nicExtraDhcpOptionVOList = _nicExtraDhcpOptionDao.listByNicId(nicId); diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDao.java b/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDao.java index 69b5f0e146e8..fd3f674b06e3 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDao.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDao.java @@ -38,6 +38,8 @@ public interface ClusterDao extends GenericDao { List listByDcHyType(long dcId, String hyType); + List listByDatacenterExternalHypervisorProvisioner(long dcId, String provisioner); + Map> getPodClusterIdMap(List clusterIds); List listDisabledClusters(long zoneId, Long podId); diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java index 59614b547456..c292837354f5 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java @@ -47,6 +47,7 @@ import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.cloudstack.api.ApiConstants; @Component public class ClusterDaoImpl extends GenericDaoBase implements ClusterDao { @@ -155,6 +156,27 @@ public List listByDcHyType(long dcId, String hyType) { return listBy(sc); } + @Override + public List listByDatacenterExternalHypervisorProvisioner(long dcId, String provisioner) { + SearchBuilder ZoneExternalHyTypeSearch = createSearchBuilder(); + ZoneExternalHyTypeSearch.and("hypervisorType", ZoneExternalHyTypeSearch.entity().getHypervisorType(), SearchCriteria.Op.EQ); + ZoneExternalHyTypeSearch.and("dataCenterId", ZoneExternalHyTypeSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ); + SearchBuilder clusterDetails = clusterDetailsDao.createSearchBuilder(); + ClusterDetailsVO tagEntity = clusterDetails.entity(); + clusterDetails.and("provisioner", tagEntity.getName(), SearchCriteria.Op.EQ); + clusterDetails.and("value", tagEntity.getValue(), SearchCriteria.Op.EQ); + ZoneExternalHyTypeSearch.join("clusterDetails", clusterDetails, ZoneExternalHyTypeSearch.entity().getId(), tagEntity.getClusterId(), JoinBuilder.JoinType.INNER); + ZoneExternalHyTypeSearch.done(); + + SearchCriteria sc = ZoneExternalHyTypeSearch.create(); + sc.setParameters("dataCenterId", dcId); + sc.setParameters("hypervisorType", HypervisorType.External.toString()); + sc.setJoinParameters("clusterDetails", "provisioner", ApiConstants.EXTERNAL_PROVISIONER); + sc.setJoinParameters("clusterDetails", "value", provisioner); + + return listBy(sc); + } + @Override public List getAvailableHypervisorInZone(Long zoneId) { SearchCriteria sc = AvailHyperSearch.create(); diff --git a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeDaoImpl.java index 4811b59d31e5..09d9f1d7fbf9 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeDaoImpl.java @@ -125,7 +125,7 @@ public String getNetworkTag(long physicalNetworkId, TrafficType trafficType, Hyp sc = simulatorAllFieldsSearch.create(); } else if (hType == HypervisorType.Ovm) { sc = ovmAllFieldsSearch.create(); - } else if (hType == HypervisorType.BareMetal) { + } else if (hType == HypervisorType.BareMetal || hType == HypervisorType.External) { return null; } else if (hType == HypervisorType.Hyperv) { sc = hypervAllFieldsSearch.create(); diff --git a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateServiceImpl.java b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateServiceImpl.java index 38e0d0d081cb..caebe31bbfd5 100644 --- a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateServiceImpl.java +++ b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateServiceImpl.java @@ -236,6 +236,7 @@ public void downloadBootstrapSysTemplate(DataStore store) { } /* Baremetal need not to download any template */ availHypers.remove(HypervisorType.BareMetal); + availHypers.remove(HypervisorType.External); availHypers.add(HypervisorType.None); // bug 9809: resume ISO // download. @@ -526,6 +527,7 @@ public void handleTemplateSync(DataStore store) { } /* Baremetal need not to download any template */ availHypers.remove(HypervisorType.BareMetal); + availHypers.remove(HypervisorType.External); availHypers.add(HypervisorType.None); // bug 9809: resume ISO // download. for (VMTemplateVO tmplt : toBeDownloaded) { diff --git a/plugins/hypervisors/external/pom.xml b/plugins/hypervisors/external/pom.xml new file mode 100644 index 000000000000..ddb13329c3eb --- /dev/null +++ b/plugins/hypervisors/external/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + org.apache.cloudstack + cloudstack-plugins + 4.20.0.0-SNAPSHOT + ../../pom.xml + + cloud-plugin-hypervisor-external + Apache CloudStack Plugin - Hypervisor External + External Hypervisor for Cloudstack + + + org.apache.cloudstack + cloud-agent + ${project.version} + + + com.fasterxml.jackson.core + jackson-databind + ${cs.jackson.version} + compile + + + diff --git a/plugins/hypervisors/external/src/main/java/com/cloud/agent/manager/ExternalAgentManager.java b/plugins/hypervisors/external/src/main/java/com/cloud/agent/manager/ExternalAgentManager.java new file mode 100644 index 000000000000..b82797439558 --- /dev/null +++ b/plugins/hypervisors/external/src/main/java/com/cloud/agent/manager/ExternalAgentManager.java @@ -0,0 +1,37 @@ +// 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 com.cloud.agent.manager; + +import com.cloud.hypervisor.ExternalProvisioner; +import com.cloud.hypervisor.external.resource.ExternalResourceBase; +import com.cloud.utils.component.Manager; + +import javax.naming.ConfigurationException; +import java.util.List; +import java.util.Map; + +public interface ExternalAgentManager extends Manager { + + boolean configure(String name, Map params) throws ConfigurationException; + + Map> createServerResources(Map params); + + ExternalProvisioner getExternalProvisioner(String provisioner); + + List listExternalProvisioners(); +} diff --git a/plugins/hypervisors/external/src/main/java/com/cloud/agent/manager/ExternalAgentManagerImpl.java b/plugins/hypervisors/external/src/main/java/com/cloud/agent/manager/ExternalAgentManagerImpl.java new file mode 100644 index 000000000000..c285bb721248 --- /dev/null +++ b/plugins/hypervisors/external/src/main/java/com/cloud/agent/manager/ExternalAgentManagerImpl.java @@ -0,0 +1,144 @@ +// +// 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 com.cloud.agent.manager; + +import com.cloud.hypervisor.ExternalProvisioner; +import com.cloud.hypervisor.HypervisorGuruManagerImpl; +import com.cloud.hypervisor.external.resource.ExternalResourceBase; +import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; + +import javax.naming.ConfigurationException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class ExternalAgentManagerImpl extends ManagerBase implements ExternalAgentManager, Configurable { + private static final Logger logger = Logger.getLogger(ExternalAgentManagerImpl.class); + + public static final ConfigKey expectMacAddressFromExternalProvisioner = new ConfigKey<>(Boolean.class, "expect.macaddress.from.external.provisioner", "Advanced", "true", + "Sample external provisioning config, any value that has to be sent", true, ConfigKey.Scope.Cluster, null); + + private List externalProvisioners; + + protected static Map externalProvisionerMap = new HashMap<>(); + + public List getExternalProvisioners() { + return externalProvisioners; + } + + public void setExternalProvisioners(final List externalProvisioners) { + this.externalProvisioners = externalProvisioners; + } + + public boolean configure(String name, Map params) { + return true; + } + + @Override + public boolean start() { + initializeExternalProvisionerMap(); + return true; + } + + protected void initializeExternalProvisionerMap() { + logger.info("Initializing the external providers"); + if (StringUtils.isNotEmpty(HypervisorGuruManagerImpl.ExternalProvisioners.value())) { + if (externalProvisioners != null) { + List externalProvisionersListFromConfig = Arrays.stream(HypervisorGuruManagerImpl.ExternalProvisioners.value().split(",")) + .map(String::trim) + .map(String::toLowerCase) + .collect(Collectors.toList()); + logger.info(String.format("Found these external provisioners from global setting %s", externalProvisionersListFromConfig)); + logger.info(String.format("Found these external provisioners from the available plugins %s", externalProvisioners)); + for (final ExternalProvisioner externalProvisioner : externalProvisioners) { + if (externalProvisionersListFromConfig.contains(externalProvisioner.getName().toLowerCase())) { + externalProvisionerMap.put(externalProvisioner.getName().toLowerCase(), externalProvisioner); + } + } + logger.info(String.format("List of external providers that are enabled are %s", externalProvisionerMap)); + } else { + logger.info("No external provisioners found to initialize"); + } + } else { + logger.info("No external provisioners found to initialise, please check global setting external.provisioners and available plugins"); + } + } + + @Override + public ExternalProvisioner getExternalProvisioner(String provisioner) { + if (StringUtils.isEmpty(provisioner)) { + throw new CloudRuntimeException("External provisioner name cannot be empty"); + } + if (!externalProvisionerMap.containsKey(provisioner.toLowerCase())) { + throw new CloudRuntimeException(String.format("Failed to find external provisioner by the name: %s.", provisioner)); + } + return externalProvisionerMap.get(provisioner.toLowerCase()); + } + + @Override + public List listExternalProvisioners() { + return externalProvisioners; + } + + public Map> createServerResources(Map params) { + + Map args = new HashMap<>(); + Map> newResources = new HashMap<>(); + ExternalResourceBase agentResource; + String provisionerName = (String) params.get(ApiConstants.EXTERNAL_PROVISIONER); + logger.debug("Checking if the provided external provisioner is valid before "); + ExternalProvisioner externalProvisioner = getExternalProvisioner(provisionerName); + if (externalProvisioner == null) { + throw new CloudRuntimeException(String.format("Unable to find the provisioner with the name %s", provisionerName)); + } + synchronized (this) { + String guid = (String)params.get("guid"); + agentResource = new ExternalResourceBase(); + if (agentResource != null) { + try { + agentResource.start(); + agentResource.configure("ExternalHost-" + guid, params); + newResources.put(agentResource, args); + } catch (ConfigurationException e) { + logger.error("error while configuring server resource" + e.getMessage()); + } + } + } + return newResources; + } + + @Override + public String getConfigComponentName() { + return ExternalAgentManagerImpl.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[] {expectMacAddressFromExternalProvisioner}; + } +} diff --git a/plugins/hypervisors/external/src/main/java/com/cloud/agent/manager/ExternalServerPlanner.java b/plugins/hypervisors/external/src/main/java/com/cloud/agent/manager/ExternalServerPlanner.java new file mode 100644 index 000000000000..c28e05ab1f53 --- /dev/null +++ b/plugins/hypervisors/external/src/main/java/com/cloud/agent/manager/ExternalServerPlanner.java @@ -0,0 +1,152 @@ +// 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 com.cloud.agent.manager; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import com.cloud.storage.VMTemplateDetailVO; +import com.cloud.storage.dao.VMTemplateDetailsDao; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.commons.collections.CollectionUtils; +import org.apache.log4j.Logger; + +import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenter; +import com.cloud.dc.Pod; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.HostPodDao; +import com.cloud.deploy.DeployDestination; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.offering.ServiceOffering; +import com.cloud.org.Cluster; +import com.cloud.resource.ResourceManager; +import com.cloud.utils.component.AdapterBase; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; + +public class ExternalServerPlanner extends AdapterBase implements DeploymentPlanner { + private static final Logger s_logger = Logger.getLogger(ExternalServerPlanner.class); + @Inject + protected DataCenterDao _dcDao; + @Inject + protected HostPodDao _podDao; + @Inject + protected ClusterDao _clusterDao; + @Inject + protected HostDao _hostDao; + @Inject + protected ConfigurationDao _configDao; + @Inject + protected ResourceManager _resourceMgr; + @Inject + VMTemplateDetailsDao _tmpDetailsDao; + + @Override + public DeployDestination plan(VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid) throws InsufficientServerCapacityException { + VirtualMachine vm = vmProfile.getVirtualMachine(); + ServiceOffering offering = vmProfile.getServiceOffering(); + + String haVmTag = (String)vmProfile.getParameter(VirtualMachineProfile.Param.HaTag); + + if (vm.getLastHostId() != null) { + HostVO h = _hostDao.findById(vm.getLastHostId()); + DataCenter dc = _dcDao.findById(h.getDataCenterId()); + Pod pod = _podDao.findById(h.getPodId()); + Cluster c = _clusterDao.findById(h.getClusterId()); + s_logger.debug(String.format("Start external VM instance %s on last used host %d", vm.getId(), h.getId())); + return new DeployDestination(dc, pod, c, h); + } + + String hostTag = null; + if (haVmTag != null) { + hostTag = haVmTag; + } else if (offering.getHostTag() != null) { + String[] tags = offering.getHostTag().split(","); + if (tags.length > 0) { + hostTag = tags[0]; + } + } + + VMTemplateDetailVO provisionerDetail = _tmpDetailsDao.findDetail(vmProfile.getTemplateId(), ApiConstants.EXTERNAL_PROVISIONER); + String provisioner = provisionerDetail.getValue(); + List clusters = _clusterDao.listByDatacenterExternalHypervisorProvisioner(vm.getDataCenterId(), provisioner); + HostVO target = null; + List hosts; + for (ClusterVO cluster : clusters) { + hosts = _resourceMgr.listAllUpAndEnabledHosts(Host.Type.Routing, cluster.getId(), cluster.getPodId(), cluster.getDataCenterId()); + if (hostTag != null) { + for (HostVO host : hosts) { + _hostDao.loadHostTags(host); + List hostTags = host.getHostTags(); + if (hostTags.contains(hostTag)) { + target = host; + break; + } + } + } else { + if (CollectionUtils.isNotEmpty(hosts)) { + Collections.shuffle(hosts); + target = hosts.get(0); + } + } + } + + if (target != null) { + DataCenter dc = _dcDao.findById(target.getDataCenterId()); + Pod pod = _podDao.findById(target.getPodId()); + Cluster cluster = _clusterDao.findById(target.getClusterId()); + return new DeployDestination(dc, pod, cluster, target); + } + + s_logger.warn(String.format("Cannot find suitable host for deploying external instance %s", vmProfile.getInstanceName())); + return null; + } + + @Override + public boolean canHandle(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid) { + return vm.getHypervisorType() == HypervisorType.External; + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + return true; + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } + +} diff --git a/plugins/hypervisors/external/src/main/java/com/cloud/agent/manager/ExternalTemplateAdapter.java b/plugins/hypervisors/external/src/main/java/com/cloud/agent/manager/ExternalTemplateAdapter.java new file mode 100644 index 000000000000..fd4f093ea763 --- /dev/null +++ b/plugins/hypervisors/external/src/main/java/com/cloud/agent/manager/ExternalTemplateAdapter.java @@ -0,0 +1,267 @@ +// 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. +// +// Automatically generated by addcopyright.py at 01/29/2013 +// Apache License, Version 2.0 (the "License"); you may not use this +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// Automatically generated by addcopyright.py at 04/03/2012 +package com.cloud.agent.manager; + +import com.cloud.configuration.Resource; +import com.cloud.dc.DataCenterVO; +import com.cloud.event.EventTypes; +import com.cloud.event.UsageEventVO; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.ExternalProvisioner; +import com.cloud.resource.ResourceManager; +import com.cloud.storage.TemplateProfile; +import com.cloud.storage.VMTemplateStorageResourceAssoc; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.VMTemplateZoneVO; +import com.cloud.storage.Storage; +import com.cloud.template.TemplateAdapter; +import com.cloud.template.TemplateAdapterBase; +import com.cloud.template.VirtualMachineTemplate; +import com.cloud.user.Account; +import com.cloud.utils.db.DB; +import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.command.user.iso.DeleteIsoCmd; +import org.apache.cloudstack.api.command.user.iso.GetUploadParamsForIsoCmd; +import org.apache.cloudstack.api.command.user.iso.RegisterIsoCmd; +import org.apache.cloudstack.api.command.user.template.RegisterTemplateCmd; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand; +import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; + +import javax.inject.Inject; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class ExternalTemplateAdapter extends TemplateAdapterBase implements TemplateAdapter { + private final static Logger logger = Logger.getLogger(ExternalTemplateAdapter.class); + @Inject + HostDao _hostDao; + @Inject + ResourceManager _resourceMgr; + @Inject + ExternalAgentManagerImpl _externalAgentMgr; + + @Override + public String getName() { + return TemplateAdapterType.External.getName(); + } + + @Override + public TemplateProfile prepare(RegisterTemplateCmd cmd) throws ResourceAllocationException { + Account caller = CallContext.current().getCallingAccount(); + Account owner = _accountMgr.getAccount(cmd.getEntityOwnerId()); + _accountMgr.checkAccess(caller, null, true, owner); + Storage.TemplateType templateType = templateMgr.validateTemplateType(cmd, _accountMgr.isAdmin(caller.getAccountId()), + CollectionUtils.isEmpty(cmd.getZoneIds())); + + String provisioner = cmd.getExternalProvisioner(); + ExternalProvisioner externalProvisioner = _externalAgentMgr.getExternalProvisioner(provisioner); + if (externalProvisioner == null) { + throw new CloudRuntimeException(String.format("Unable to find the provisioner with the name %s", provisioner)); + } + + List zoneId = cmd.getZoneIds(); + // ignore passed zoneId if we are using region wide image store + List stores = _imgStoreDao.findRegionImageStores(); + if (stores != null && stores.size() > 0) { + zoneId = null; + } + + Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.getType(cmd.getHypervisor()); + if(hypervisorType == Hypervisor.HypervisorType.None) { + throw new InvalidParameterValueException(String.format( + "Hypervisor Type: %s is invalid. Supported Hypervisor types are: %s", + cmd.getHypervisor(), + StringUtils.join(Arrays.stream(Hypervisor.HypervisorType.values()).filter(h -> h != Hypervisor.HypervisorType.None).map(Hypervisor.HypervisorType::name).toArray(), ", "))); + } + + Map details = cmd.getDetails(); + Map externalDetails = cmd.getExternalDetails(); + if (details != null) { + details.putAll(externalDetails); + } else { + details = externalDetails; + } + details.put(ApiConstants.EXTERNAL_PROVISIONER, cmd.getExternalProvisioner()); + + return prepare(false, CallContext.current().getCallingUserId(), cmd.getTemplateName(), cmd.getDisplayText(), cmd.getArch(), cmd.getBits(), cmd.isPasswordEnabled(), cmd.getRequiresHvm(), + cmd.getUrl(), cmd.isPublic(), cmd.isFeatured(), cmd.isExtractable(), cmd.getFormat(), cmd.getOsTypeId(), zoneId, hypervisorType, cmd.getChecksum(), true, + cmd.getTemplateTag(), owner, details, cmd.isSshKeyEnabled(), null, cmd.isDynamicallyScalable(), templateType, + cmd.isDirectDownload(), cmd.isDeployAsIs()); + } + + @Override + public TemplateProfile prepare(RegisterIsoCmd cmd) throws ResourceAllocationException { + throw new CloudRuntimeException("External hypervisor doesn't support ISO template"); + } + + @Override + public TemplateProfile prepare(GetUploadParamsForIsoCmd cmd) throws ResourceAllocationException { + throw new CloudRuntimeException("External hypervisor doesn't support ISO template"); + } + + private void templateCreateUsage(VMTemplateVO template, long dcId) { + if (template.getAccountId() != Account.ACCOUNT_ID_SYSTEM) { + UsageEventVO usageEvent = + new UsageEventVO(EventTypes.EVENT_TEMPLATE_CREATE, template.getAccountId(), dcId, template.getId(), template.getName(), null, + template.getSourceTemplateId(), 0L); + _usageEventDao.persist(usageEvent); + } + } + + @Override + public VMTemplateVO create(TemplateProfile profile) { + VMTemplateVO template = persistTemplate(profile, VirtualMachineTemplate.State.Active); + List zones = profile.getZoneIdList(); + + // create an entry at template_store_ref with store_id = null to represent that this template is ready for use. + TemplateDataStoreVO vmTemplateHost = + new TemplateDataStoreVO(null, template.getId(), new Date(), 100, VMTemplateStorageResourceAssoc.Status.DOWNLOADED, null, null, null, null, template.getUrl()); + this._tmpltStoreDao.persist(vmTemplateHost); + + if (zones == null) { + List dcs = _dcDao.listAllIncludingRemoved(); + if (dcs != null && dcs.size() > 0) { + templateCreateUsage(template, dcs.get(0).getId()); + } + } else { + for (Long zoneId: zones) { + templateCreateUsage(template, zoneId); + } + } + + _resourceLimitMgr.incrementResourceCount(profile.getAccountId(), Resource.ResourceType.template); + return template; + } + + @Override + public List createTemplateForPostUpload(TemplateProfile profile) { + // TODO: support External hypervisor for postupload + return null; + } + + @Override + public TemplateProfile prepareDelete(DeleteIsoCmd cmd) { + throw new CloudRuntimeException("External hypervisor doesn't support ISO, how the delete get here???"); + } + + @Override + @DB + public boolean delete(TemplateProfile profile) { + VMTemplateVO template = profile.getTemplate(); + Long templateId = template.getId(); + boolean success = true; + String zoneName; + + if (profile.getZoneIdList() != null && profile.getZoneIdList().size() > 1) + throw new CloudRuntimeException("Operation is not supported for more than one zone id at a time"); + + if (!template.isCrossZones() && profile.getZoneIdList() != null) { + //get the first element in the list + zoneName = profile.getZoneIdList().get(0).toString(); + } else { + zoneName = "all zones"; + } + + logger.debug("Attempting to mark template host refs for template: " + template.getName() + " as destroyed in zone: " + zoneName); + Account account = _accountDao.findByIdIncludingRemoved(template.getAccountId()); + String eventType = EventTypes.EVENT_TEMPLATE_DELETE; + List templateHostVOs = this._tmpltStoreDao.listByTemplate(templateId); + + for (TemplateDataStoreVO vo : templateHostVOs) { + TemplateDataStoreVO lock = null; + try { + lock = _tmpltStoreDao.acquireInLockTable(vo.getId()); + if (lock == null) { + logger.debug("Failed to acquire lock when deleting templateDataStoreVO with ID: " + vo.getId()); + success = false; + break; + } + + vo.setDestroyed(true); + _tmpltStoreDao.update(vo.getId(), vo); + + } finally { + if (lock != null) { + _tmpltStoreDao.releaseFromLockTable(lock.getId()); + } + } + } + + if (profile.getZoneIdList() != null) { + UsageEventVO usageEvent = new UsageEventVO(eventType, account.getId(), profile.getZoneIdList().get(0), + templateId, null); + _usageEventDao.persist(usageEvent); + + VMTemplateZoneVO templateZone = _tmpltZoneDao.findByZoneTemplate(profile.getZoneIdList().get(0), templateId); + + if (templateZone != null) { + _tmpltZoneDao.remove(templateZone.getId()); + } + } else { + List dcs = _dcDao.listAllIncludingRemoved(); + for (DataCenterVO dc : dcs) { + UsageEventVO usageEvent = new UsageEventVO(eventType, account.getId(), dc.getId(), templateId, null); + _usageEventDao.persist(usageEvent); + } + } + + logger.debug("Successfully marked template host refs for template: " + template.getName() + " as destroyed in zone: " + zoneName); + + // If there are no more non-destroyed template host entries for this template, delete it + if (success && (_tmpltStoreDao.listByTemplate(templateId).size() == 0)) { + long accountId = template.getAccountId(); + + VMTemplateVO lock = _tmpltDao.acquireInLockTable(templateId); + + try { + if (lock == null) { + logger.debug("Failed to acquire lock when deleting template with ID: " + templateId); + success = false; + } else if (_tmpltDao.remove(templateId)) { + // Decrement the number of templates and total secondary storage space used by the account. + _resourceLimitMgr.decrementResourceCount(accountId, Resource.ResourceType.template); + _resourceLimitMgr.recalculateResourceCount(accountId, _accountMgr.getAccount(accountId).getDomainId(), Resource.ResourceType.secondary_storage.getOrdinal()); + } + + } finally { + if (lock != null) { + _tmpltDao.releaseFromLockTable(lock.getId()); + } + } + logger.debug("Removed template: " + template.getName() + " because all of its template host refs were marked as destroyed."); + } + + return success; + } +} diff --git a/plugins/hypervisors/external/src/main/java/com/cloud/hypervisor/external/discoverer/ExternalServerDiscoverer.java b/plugins/hypervisors/external/src/main/java/com/cloud/hypervisor/external/discoverer/ExternalServerDiscoverer.java new file mode 100644 index 000000000000..af3176c47236 --- /dev/null +++ b/plugins/hypervisors/external/src/main/java/com/cloud/hypervisor/external/discoverer/ExternalServerDiscoverer.java @@ -0,0 +1,248 @@ +// 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 com.cloud.hypervisor.external.discoverer; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.Listener; +import com.cloud.agent.api.AgentControlAnswer; +import com.cloud.agent.api.AgentControlCommand; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.StartupRoutingCommand; +import com.cloud.agent.manager.ExternalAgentManager; +import com.cloud.dc.ClusterDetailsVO; +import com.cloud.dc.ClusterVO; +import com.cloud.exception.ConnectionException; +import com.cloud.exception.DiscoveryException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.Status; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.resource.Discoverer; +import com.cloud.resource.DiscovererBase; +import com.cloud.hypervisor.external.resource.ExternalResourceBase; +import com.cloud.resource.ResourceStateAdapter; +import com.cloud.resource.ServerResource; +import com.cloud.resource.UnableDeleteHostException; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.log4j.Logger; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import java.net.URI; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class ExternalServerDiscoverer extends DiscovererBase implements Discoverer, Listener, ResourceStateAdapter { + + private static final Logger s_logger = Logger.getLogger(ExternalServerDiscoverer.class); + + @Inject + private AgentManager _agentMgr; + + @Inject + ExternalAgentManager _externalAgentMgr = null; + + @Override + public boolean processAnswers(long agentId, long seq, Answer[] answers) { + return false; + } + + @Override + public boolean processCommands(long agentId, long seq, Command[] commands) { + return false; + } + + @Override + public AgentControlAnswer processControlCommand(long agentId, AgentControlCommand cmd) { + return null; + } + + @Override + public void processHostAdded(long hostId) { + + } + + @Override + public void processConnect(Host host, StartupCommand cmd, boolean forRebalance) throws ConnectionException { + + } + + @Override + public boolean processDisconnect(long agentId, Status state) { + return false; + } + + @Override + public void processHostAboutToBeRemoved(long hostId) { + + } + + @Override + public void processHostRemoved(long hostId, long clusterId) { + + } + + @Override + public boolean isRecurring() { + return false; + } + + @Override + public int getTimeout() { + return 0; + } + + @Override + public boolean processTimeout(long agentId, long seq) { + return false; + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + super.configure(name, params); + _agentMgr.registerForHostEvents(this, true, false, true); + _resourceMgr.registerResourceStateAdapter(this.getClass().getSimpleName(), this); + return true; + } + + @Override + public Map> find(long dcId, Long podId, Long clusterId, URI uri, String username, String password, List hostTags) throws DiscoveryException { + Map> resources; + + try { + String cluster = null; + if (clusterId == null) { + String msg = "must specify cluster Id when adding host"; + if (s_logger.isDebugEnabled()) { + s_logger.debug(msg); + } + throw new RuntimeException(msg); + } else { + ClusterVO clu = _clusterDao.findById(clusterId); + if (clu == null || (clu.getHypervisorType() != Hypervisor.HypervisorType.External)) { + if (s_logger.isInfoEnabled()) + s_logger.info("invalid cluster id or cluster is not for Simulator hypervisors"); + return null; + } + cluster = Long.toString(clusterId); + if (clu.getGuid() == null) { + clu.setGuid(UUID.randomUUID().toString()); + } + _clusterDao.update(clusterId, clu); + } + + String pod; + if (podId == null) { + String msg = "must specify pod Id when adding host"; + if (s_logger.isDebugEnabled()) { + s_logger.debug(msg); + } + throw new RuntimeException(msg); + } else { + pod = Long.toString(podId); + } + + Map params = new HashMap(); + params.put("username", username); + params.put("password", password); + params.put("zone", Long.toString(dcId)); + params.put("pod", pod); + params.put("cluster", cluster); + params.put("guid", uri.toString()); + + if (_params.get(ApiConstants.EXTERNAL_PROVISIONER) == null) { + s_logger.error("External provisioner must be defined to discover"); + return null; + } + params.put(ApiConstants.EXTERNAL_PROVISIONER, _params.get(ApiConstants.EXTERNAL_PROVISIONER)); + + resources = createAgentResources(params); + return resources; + } catch (Exception ex) { + s_logger.error("Exception when discovering external hosts: " + ex.getMessage()); + } + return null; + } + + @Override + protected HashMap buildConfigParams(HostVO host) { + HashMap params = super.buildConfigParams(host); + if (host.getClusterId() != null) { + ClusterDetailsVO externalProvisioner = _clusterDetailsDao.findDetail(host.getClusterId(), ApiConstants.EXTERNAL_PROVISIONER); + params.put(ApiConstants.EXTERNAL_PROVISIONER, externalProvisioner.getValue()); + } + + return params; + } + + private Map> createAgentResources(Map params) { + try { + s_logger.info("Creating External Server Resources"); + return _externalAgentMgr.createServerResources(params); + } catch (Exception ex) { + s_logger.warn("Caught exception at agent resource creation: " + ex.getMessage(), ex); + } + return null; + } + + @Override + public void postDiscovery(List hosts, long msId) throws DiscoveryException { + + } + + @Override + public boolean matchHypervisor(String hypervisor) { + if (hypervisor == null) + return true; + + return getHypervisorType().toString().equalsIgnoreCase(hypervisor); + } + + @Override + public Hypervisor.HypervisorType getHypervisorType() { + return Hypervisor.HypervisorType.External; + } + + @Override + public HostVO createHostVOForConnectedAgent(HostVO host, StartupCommand[] cmd) { + return null; + } + + @Override + public HostVO createHostVOForDirectConnectAgent(HostVO host, StartupCommand[] startup, ServerResource resource, Map details, List hostTags) { + StartupCommand firstCmd = startup[0]; + if (!(firstCmd instanceof StartupRoutingCommand)) { + return null; + } + + StartupRoutingCommand ssCmd = ((StartupRoutingCommand)firstCmd); + if (ssCmd.getHypervisorType() != Hypervisor.HypervisorType.External) { + return null; + } + + return _resourceMgr.fillRoutingHostVO(host, ssCmd, Hypervisor.HypervisorType.External, details, hostTags); + } + + @Override + public DeleteHostAnswer deleteHost(HostVO host, boolean isForced, boolean isForceDeleteStorage) throws UnableDeleteHostException { + return new DeleteHostAnswer(true); + } +} diff --git a/plugins/hypervisors/external/src/main/java/com/cloud/hypervisor/external/provisioner/simpleprovisioner/SimpleExternalProvisioner.java b/plugins/hypervisors/external/src/main/java/com/cloud/hypervisor/external/provisioner/simpleprovisioner/SimpleExternalProvisioner.java new file mode 100644 index 000000000000..288889ade326 --- /dev/null +++ b/plugins/hypervisors/external/src/main/java/com/cloud/hypervisor/external/provisioner/simpleprovisioner/SimpleExternalProvisioner.java @@ -0,0 +1,422 @@ +// +// 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 com.cloud.hypervisor.external.provisioner.simpleprovisioner; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.HostVmStateReportEntry; +import com.cloud.agent.api.PrepareExternalProvisioningAnswer; +import com.cloud.agent.api.PrepareExternalProvisioningCommand; +import com.cloud.agent.api.PostExternalProvisioningAnswer; +import com.cloud.agent.api.PostExternalProvisioningCommand; +import com.cloud.agent.api.RebootAnswer; +import com.cloud.agent.api.RebootCommand; +import com.cloud.agent.api.StartAnswer; +import com.cloud.agent.api.StartCommand; +import com.cloud.agent.api.StopAnswer; +import com.cloud.agent.api.StopCommand; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.ExternalProvisioner; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.serializer.GsonHelper; +import com.cloud.utils.HumanReadableJson; +import com.cloud.utils.Pair; +import com.cloud.utils.StringUtils; +import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.OutputInterpreter; +import com.cloud.utils.script.Script; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.VirtualMachineProfileImpl; +import com.cloud.vm.VmDetailConstants; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.Gson; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.guru.ExternalHypervisorGuru; +import org.apache.commons.collections.CollectionUtils; +import org.apache.log4j.Logger; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SimpleExternalProvisioner extends AdapterBase implements ExternalProvisioner, Configurable { + + private static final Logger logger = Logger.getLogger(SimpleExternalProvisioner.class); + + public static final ConfigKey SampleExternalConfig = new ConfigKey<>(String.class, "sample.external.provisioning.config", "Advanced", "", + "Sample external provisioning config, any value that has to be sent", true, ConfigKey.Scope.Cluster, null); + + String ExternalProvisioningConfig = "external.provisioning.config"; + + public static final String EXTERNAL_PROVISIONER_SCRIPT = "scripts/vm/hypervisor/external/simpleExternalProvisioner/provisioner.sh"; + public static final String EXTERNAL_POWER_OPERATIONS_SCRIPT = "scripts/vm/hypervisor/external/simpleExternalProvisioner/powerOperations.sh"; + + @Inject + UserVmDao _uservmDao; + + @Inject + HostDao _hostDao; + + @Inject + VMInstanceDao _vmDao; + + + @Inject + private HypervisorGuruManager _hvGuruMgr; + + @Override + public String getName() { + return "simpleExternalProvisioner"; + } + + @Override + public String getDescription() { + return "Simple external provisioner"; + } + + private Map loadAccessDetails(Map accessDetails, Long clusterId, String vmTO) { + Map modifiedDetails = new HashMap<>(); + for (Map.Entry entry : accessDetails.entrySet()) { + String key = entry.getKey(); + if (key.startsWith(VmDetailConstants.EXTERNAL_DETAIL_PREFIX)) { + key = key.substring(VmDetailConstants.EXTERNAL_DETAIL_PREFIX.length()); + modifiedDetails.put(key, entry.getValue()); + } else { + modifiedDetails.put(key, entry.getValue()); + } + } + + if (modifiedDetails.get(ExternalProvisioningConfig) == null) { + modifiedDetails.put(ExternalProvisioningConfig, SampleExternalConfig.valueIn(clusterId)); + } + + modifiedDetails.put(VmDetailConstants.CLOUDSTACK_VM_DETAILS, vmTO); + + logger.debug(String.format("Using these access details for VM instance operation: %s", accessDetails)); + + return modifiedDetails; + } + + @Override + public PrepareExternalProvisioningAnswer prepareExternalProvisioning(PrepareExternalProvisioningCommand cmd) { + VirtualMachineTO vmTO = cmd.getVirtualMachineTO(); + String vmUUID = vmTO.getUuid(); + logger.debug(String.format("Executing PrepareExternalProvisioningCommand in the external provisioner " + + "for the VM %s as part of VM deployment", vmUUID)); + + String prepareExternalScript = Script.findScript("", EXTERNAL_PROVISIONER_SCRIPT); + Long clusterId = cmd.getClusterId(); + Map accessDetails = loadAccessDetails(vmTO.getDetails(), clusterId, getVirtualMachineTOJsonString(vmTO)); + + Pair result = prepareExternalProvisioningInternal(prepareExternalScript, vmUUID, accessDetails, cmd.getWait()); + String output = result.second(); + + if (!result.first()) { + return new PrepareExternalProvisioningAnswer(cmd, output, false); + } + + if (StringUtils.isEmpty(output)) { + return new PrepareExternalProvisioningAnswer(cmd, "", true); + } + + try { + Map resultMap = StringUtils.parseJsonToMap(output); + Map modifiedMap = new HashMap<>(); + for (Map.Entry entry : resultMap.entrySet()) { + modifiedMap.put(VmDetailConstants.EXTERNAL_DETAIL_PREFIX + entry.getKey(), entry.getValue()); + } + return new PrepareExternalProvisioningAnswer(cmd, modifiedMap, null); + } catch (CloudRuntimeException e) { + logger.debug(String.format("Failed to parse the output from preparing external provisioning operation as part of VM deployment with error %s", e.getMessage())); + return new PrepareExternalProvisioningAnswer(cmd, e.getMessage(), false); + } + } + + @Override + public StartAnswer startInstance(StartCommand cmd) { + VirtualMachineTO virtualMachineTO = cmd.getVirtualMachine(); + UserVmVO uservm = _uservmDao.findById(virtualMachineTO.getId()); + HostVO host = _hostDao.findById(uservm.getHostId()); + Map accessDetails = loadAccessDetails(virtualMachineTO.getDetails(), host.getClusterId(), getVirtualMachineTOJsonString(virtualMachineTO)); + String vmUUID = virtualMachineTO.getUuid(); + + logger.debug(String.format("Executing StartCommand in the external provisioner for VM %s", vmUUID)); + + String deployvm = accessDetails.get("deployvm"); + boolean isDeploy = (deployvm != null && Boolean.parseBoolean(deployvm)); + String operation = isDeploy ? "Deploying" : "Starting"; + String prepareExternalScript = isDeploy ? Script.findScript("", EXTERNAL_PROVISIONER_SCRIPT) : Script.findScript("", EXTERNAL_POWER_OPERATIONS_SCRIPT); + + try { + Pair result = executeStartCommandOnExternalSystem(isDeploy, prepareExternalScript, vmUUID, accessDetails, cmd.getWait()); + + if (!result.first()) { + String errMsg = String.format("%s VM %s on the external system failed: %s", operation, vmUUID, result.second()); + logger.debug(errMsg); + return new StartAnswer(cmd, result.second()); + } + logger.debug(String.format("%s VM %s on the external system", operation, vmUUID)); + return new StartAnswer(cmd); + + } catch (CloudRuntimeException e) { + String errMsg = String.format("%s VM %s on the external system failed: %s", operation, vmUUID, e.getMessage()); + logger.debug(errMsg); + return new StartAnswer(cmd, errMsg); + } + } + + private Pair executeStartCommandOnExternalSystem(boolean isDeploy, String filename, String vmUUID, Map accessDetails, int wait) { + if (isDeploy) { + return deployInstanceOnExternalSystem(filename, vmUUID, accessDetails, wait); + } else { + return startInstanceOnExternalSystem(filename, vmUUID, accessDetails, wait); + } + } + + @Override + public StopAnswer stopInstance(StopCommand cmd) { + return stopInstanceOnExternalSystem(cmd); + } + + @Override + public RebootAnswer rebootInstance(RebootCommand cmd) { + return rebootInstanceOnExternalSystem(cmd); + } + + @Override + public StopAnswer expungeInstance(StopCommand cmd) { + VirtualMachineTO virtualMachineTO = cmd.getVirtualMachine(); + UserVmVO uservm = _uservmDao.findById(virtualMachineTO.getId()); + String vmUUID = uservm.getUuid(); + logger.debug(String.format("Executing stop command in the external system for the VM %s", vmUUID)); + + String prepareExternalScript = Script.findScript("", EXTERNAL_PROVISIONER_SCRIPT); + HostVO host = _hostDao.findById(uservm.getLastHostId()); + Map accessDetails = loadAccessDetails(cmd.getDetails(), host.getClusterId(), getVirtualMachineTOJsonString(virtualMachineTO)); + + Pair result = deleteInstanceOnExternalSystem(prepareExternalScript, vmUUID, accessDetails, cmd.getWait()); + if (result.first()) { + return new StopAnswer(cmd, null, true); + } else { + return new StopAnswer(cmd, result.second(), false); + } + } + + @Override + public PostExternalProvisioningAnswer postsetupInstance(PostExternalProvisioningCommand cmd) { + PostExternalProvisioningAnswer answer = new PostExternalProvisioningAnswer(cmd, null, null); + return answer; + } + + @Override + public HashMap getHostVmStateReport(Long hostId) { + List vms = _uservmDao.listByHostId(hostId); + List stoppedVMs = _uservmDao.listByLastHostId(hostId); + List allVms = new ArrayList<>(); + allVms.addAll(vms); + allVms.addAll(stoppedVMs); + final HashMap vmStates = new HashMap<>(); + if (CollectionUtils.isNotEmpty(allVms)) { + for (UserVmVO vm: allVms) { + VirtualMachine.PowerState powerState = getVMpowerState(vm); + vmStates.put(vm.getInstanceName(), new HostVmStateReportEntry(powerState, "host-" + hostId)); + } + } + + return vmStates; + } + + private VirtualMachine.PowerState getVMpowerState(UserVmVO uservm) { + HostVO host; + if (uservm.getHostId() != null) { + host = _hostDao.findById(uservm.getHostId()); + } else if (uservm.getLastHostId() != null){ + host = _hostDao.findById(uservm.getLastHostId()); + } else { + return VirtualMachine.PowerState.PowerUnknown; + } + _hostDao.loadDetails(host); + _uservmDao.loadDetails(uservm); + Map hostDetails = host.getDetails(); + Map userVmDetails = uservm.getDetails(); + HashMap accessDetails = new HashMap<>(); + ExternalHypervisorGuru.loadExternalHostAccessDetails(hostDetails, accessDetails, host.getClusterId()); + ExternalHypervisorGuru.loadExternalInstanceDetails(userVmDetails, accessDetails); + final HypervisorGuru hvGuru = _hvGuruMgr.getGuru(Hypervisor.HypervisorType.External); + VirtualMachineProfile profile = new VirtualMachineProfileImpl(uservm); + VirtualMachineTO virtualMachineTO = hvGuru.implement(profile); + + Map modifiedDetails = loadAccessDetails(accessDetails, host.getClusterId(), getVirtualMachineTOJsonString(virtualMachineTO)); + + String vmUUID = uservm.getUuid(); + logger.debug(String.format("Trying to get VM power status from the external system for the VM %s", vmUUID)); + + String prepareExternalScript = Script.findScript("", EXTERNAL_POWER_OPERATIONS_SCRIPT); + + Pair result = getInstanceStatusOnExternalSystem(prepareExternalScript, vmUUID, modifiedDetails, AgentManager.Wait.value()); + if (result.first()) { + if (result.second().equalsIgnoreCase(VirtualMachine.PowerState.PowerOn.toString())) { + return VirtualMachine.PowerState.PowerOn; + } else if (result.second().equalsIgnoreCase(VirtualMachine.PowerState.PowerOff.toString())) { + return VirtualMachine.PowerState.PowerOff; + } else { + return VirtualMachine.PowerState.PowerUnknown; + } + } else { + logger.debug(String.format("Exception occurred while trying to fetch the power status of the VM %d : %s", uservm.getId(), result.second())); + return VirtualMachine.PowerState.PowerUnknown; + } + } + + private StopAnswer stopInstanceOnExternalSystem(StopCommand cmd) { + logger.debug(String.format("Executing stop command on the external provisioner")); + VMInstanceVO uservm = _vmDao.findVMByInstanceName(cmd.getVmName()); + final HypervisorGuru hvGuru = _hvGuruMgr.getGuru(Hypervisor.HypervisorType.External); + VirtualMachineProfile profile = new VirtualMachineProfileImpl(uservm); + VirtualMachineTO virtualMachineTO = hvGuru.implement(profile); + String vmUUID = profile.getUuid(); + logger.debug(String.format("Executing stop command in the external system for the VM %s", vmUUID)); + + String prepareExternalScript = Script.findScript("", EXTERNAL_POWER_OPERATIONS_SCRIPT); + HostVO host = _hostDao.findById(uservm.getLastHostId()); + Map accessDetails = loadAccessDetails(cmd.getDetails(), host.getClusterId(), getVirtualMachineTOJsonString(virtualMachineTO)); + + Pair result = stopInstanceOnExternalSystem(prepareExternalScript, vmUUID, accessDetails, cmd.getWait()); + if (result.first()) { + return new StopAnswer(cmd, null, true); + } else { + return new StopAnswer(cmd, result.second(), false); + } + } + + private RebootAnswer rebootInstanceOnExternalSystem(RebootCommand cmd) { + logger.debug(String.format("Executing reboot command using IPMI in the external provisioner")); + VirtualMachineTO virtualMachineTO = cmd.getVirtualMachine(); + UserVmVO uservm = _uservmDao.findById(virtualMachineTO.getId()); + String vmUUID = uservm.getUuid(); + logger.debug(String.format("Executing reboot command in the external system for the VM %s", vmUUID)); + + String prepareExternalScript = Script.findScript("", EXTERNAL_POWER_OPERATIONS_SCRIPT); + HostVO host = _hostDao.findById(uservm.getLastHostId()); + Map accessDetails = loadAccessDetails(cmd.getDetails(), host.getClusterId(), getVirtualMachineTOJsonString(virtualMachineTO)); + + Pair result = rebootInstanceOnExternalSystem(prepareExternalScript, vmUUID, accessDetails, cmd.getWait()); + if (result.first()) { + return new RebootAnswer(cmd, null, true); + } else { + return new RebootAnswer(cmd, result.second(), false); + } + } + + public Pair prepareExternalProvisioningInternal(String filename, String vmUUID, Map accessDetails, int wait) { + return executeExternalCommand(filename, "prepare", accessDetails, wait, + String.format("Failed to prepare external provisioner for deploying VM %s on external system", vmUUID)); + } + + public Pair deployInstanceOnExternalSystem(String filename, String vmUUID, Map accessDetails, int wait) { + return executeExternalCommand(filename, "create", accessDetails, wait, + String.format("Failed to create the instance %s on external system", vmUUID)); + } + + public Pair startInstanceOnExternalSystem(String filename, String vmUUID, Map accessDetails, int wait) { + return executeExternalCommand(filename, "start", accessDetails, wait, + String.format("Failed to start the instance %s on external system", vmUUID)); + } + + public Pair stopInstanceOnExternalSystem(String filename, String vmUUID, Map accessDetails, int wait) { + return executeExternalCommand(filename, "stop", accessDetails, wait, + String.format("Failed to stop the instance %s on external system", vmUUID)); + } + + public Pair rebootInstanceOnExternalSystem(String filename, String vmUUID, Map accessDetails, int wait) { + return executeExternalCommand(filename, "reboot", accessDetails, wait, + String.format("Failed to reboot the instance %s on external system", vmUUID)); + } + + public Pair deleteInstanceOnExternalSystem(String filename, String vmUUID, Map accessDetails, int wait) { + return executeExternalCommand(filename, "delete", accessDetails, wait, + String.format("Failed to delete the instance %s on external system", vmUUID)); + } + + public Pair getInstanceStatusOnExternalSystem(String filename, String vmUUID, Map accessDetails, int wait) { + return executeExternalCommand(filename, "status", accessDetails, wait, + String.format("Failed to get the instance power status %s on external system", vmUUID)); + } + + public Pair executeExternalCommand(String filename, String action, Map accessDetails, int wait, String logPrefix) { + try { + String parameters = prepareParameters(accessDetails); + final Script command = new Script("/bin/bash"); + command.add(filename); + command.add(action); + command.add(parameters); + command.add(Integer.toString(wait)); + + OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser(); + String result = command.execute(parser); + + if (result != null) { + logger.debug(String.format("%s: External API execution failed with result %s", logPrefix, result)); + return new Pair<>(false, result); + } + + result = parser.getLines(); + return new Pair<>(true, result); + + } catch (Exception e) { + throw new CloudRuntimeException(String.format("%s: External operation failed", logPrefix), e); + } + } + + private String prepareParameters(Map details) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + String parameters = objectMapper.writeValueAsString(details); + return parameters; + } + + private String getVirtualMachineTOJsonString(VirtualMachineTO vmTO) { + StringBuilder content = new StringBuilder(); + Gson s_gogger = GsonHelper.getGsonLogger(); + s_gogger.toJson(vmTO, content); + return HumanReadableJson.getHumanReadableBytesJson(content.toString()); + } + + @Override + public String getConfigComponentName() { + return SimpleExternalProvisioner.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[] {SampleExternalConfig}; + } +} diff --git a/plugins/hypervisors/external/src/main/java/com/cloud/hypervisor/external/resource/ExternalResourceBase.java b/plugins/hypervisors/external/src/main/java/com/cloud/hypervisor/external/resource/ExternalResourceBase.java new file mode 100644 index 000000000000..1eeaab927b39 --- /dev/null +++ b/plugins/hypervisors/external/src/main/java/com/cloud/hypervisor/external/resource/ExternalResourceBase.java @@ -0,0 +1,276 @@ +// 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 com.cloud.hypervisor.external.resource; + +import com.cloud.agent.IAgentControl; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CheckNetworkAnswer; +import com.cloud.agent.api.CheckNetworkCommand; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.GetHostStatsAnswer; +import com.cloud.agent.api.GetHostStatsCommand; +import com.cloud.agent.api.HostVmStateReportEntry; +import com.cloud.agent.api.MaintainAnswer; +import com.cloud.agent.api.MaintainCommand; +import com.cloud.agent.api.PingRoutingCommand; +import com.cloud.agent.api.RebootAnswer; +import com.cloud.agent.api.RebootCommand; +import com.cloud.agent.api.PrepareExternalProvisioningAnswer; +import com.cloud.agent.api.PrepareExternalProvisioningCommand; +import com.cloud.agent.api.PostExternalProvisioningCommand; +import com.cloud.agent.api.PostExternalProvisioningAnswer; +import com.cloud.agent.api.PingCommand; +import com.cloud.agent.api.PingTestCommand; +import com.cloud.agent.api.ReadyAnswer; +import com.cloud.agent.api.ReadyCommand; +import com.cloud.agent.api.StartAnswer; +import com.cloud.agent.api.StartCommand; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.StartupRoutingCommand; +import com.cloud.agent.api.StopAnswer; +import com.cloud.agent.api.StopCommand; +import com.cloud.agent.manager.ExternalAgentManager; +import com.cloud.agent.manager.ExternalAgentManagerImpl; +import com.cloud.host.Host; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.ExternalProvisioner; +import com.cloud.network.Networks; +import com.cloud.resource.ServerResource; +import com.cloud.utils.component.ComponentContext; +import com.cloud.vm.dao.UserVmDao; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.log4j.Logger; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ExternalResourceBase implements ServerResource { + + private static final Logger logger = Logger.getLogger(ExternalResourceBase.class); + + @Inject + ExternalAgentManager _externalAgentMgr = null; + + @Inject + UserVmDao _uservmDao; + + protected String _url; + protected String _dcId; + protected String _pod; + protected String _cluster; + protected String _guid; + private Host.Type _type; + private ExternalProvisioner externalProvisioner = null; + + public ExternalResourceBase() { + setType(Host.Type.Routing); + } + + @Override + public Host.Type getType() { + return null; + } + + public void setType(Host.Type type) { + this._type = type; + } + + @Override + public StartupCommand[] initialize() { + final List info = getHostInfo(); + + final StartupRoutingCommand cmd = + new StartupRoutingCommand((Integer)info.get(0), (Long)info.get(1), (Long)info.get(2), (Long)info.get(4), (String)info.get(3), Hypervisor.HypervisorType.External, + Networks.RouterPrivateIpStrategy.HostLocal); + cmd.setDataCenter(_dcId); + cmd.setPod(_pod); + cmd.setCluster(_cluster); + cmd.setHostType(Host.Type.Routing); + cmd.setName(_guid); + cmd.setPrivateIpAddress(Hypervisor.HypervisorType.External.toString()); + cmd.setGuid(_guid); + cmd.setIqn(_guid); + cmd.setVersion(ExternalResourceBase.class.getPackage().getImplementationVersion()); + return new StartupCommand[] {cmd}; + } + + protected List getHostInfo() { + final ArrayList info = new ArrayList(); + long speed = 4000L; + long cpus = 4L; + long ram = 16000L * 1024L * 1024L; + long dom0ram = Math.min(ram / 10, 768 * 1024 * 1024L); + + String cap = "hvm"; + info.add((int)cpus); + info.add(speed); + info.add(ram); + info.add(cap); + info.add(dom0ram); + return info; + } + + @Override + public PingCommand getCurrentStatus(long id) { + final HashMap vmStates = externalProvisioner.getHostVmStateReport(id); + + return new PingRoutingCommand(Host.Type.Routing, id, vmStates); + } + + @Override + public Answer executeRequest(Command cmd) { + try { + if (cmd instanceof CheckNetworkCommand) { + return new CheckNetworkAnswer((CheckNetworkCommand) cmd, true, "Network Setup check by names is done"); + } else if (cmd instanceof ReadyCommand) { + return new ReadyAnswer((ReadyCommand) cmd); + } else if (cmd instanceof StartCommand) { + return execute((StartCommand) cmd); + } else if (cmd instanceof StopCommand) { + return execute((StopCommand) cmd); + } else if (cmd instanceof RebootCommand) { + return execute((RebootCommand) cmd); + } else if (cmd instanceof PingTestCommand) { + return new Answer(cmd); + } else if (cmd instanceof PrepareExternalProvisioningCommand) { + return execute((PrepareExternalProvisioningCommand) cmd); + } else if (cmd instanceof GetHostStatsCommand) { + return execute((GetHostStatsCommand) cmd); + } else if (cmd instanceof PingTestCommand) { + return execute((PingTestCommand) cmd); + } else if (cmd instanceof MaintainCommand) { + return execute((MaintainCommand) cmd); + } else { + return null; + } + } catch (IllegalArgumentException e) { + return new Answer(cmd, false, e.getMessage()); + } + } + + private Answer execute(PingTestCommand cmd) { + return new Answer(cmd); + } + + private MaintainAnswer execute(MaintainCommand cmd) { + return new MaintainAnswer(cmd, false); + } + + public StopAnswer execute(StopCommand cmd) { + Boolean expungeVM = cmd.isExpungeVM(); + if (expungeVM != null && expungeVM) { + return externalProvisioner.expungeInstance(cmd); + } + return externalProvisioner.stopInstance(cmd); + } + + public RebootAnswer execute(RebootCommand cmd) { + return externalProvisioner.rebootInstance(cmd); + } + + public PostExternalProvisioningAnswer execute(PostExternalProvisioningCommand cmd) { + return externalProvisioner.postsetupInstance(cmd); + } + + public GetHostStatsAnswer execute(GetHostStatsCommand cmd) { + return new GetHostStatsAnswer(cmd, null); + } + + public PrepareExternalProvisioningAnswer execute(PrepareExternalProvisioningCommand cmd) { + return externalProvisioner.prepareExternalProvisioning(cmd); + } + + public StartAnswer execute(StartCommand cmd) { + return externalProvisioner.startInstance(cmd); + } + + @Override + public void disconnected() { + + } + + @Override + public IAgentControl getAgentControl() { + return null; + } + + @Override + public void setAgentControl(IAgentControl agentControl) { + + } + + @Override + public String getName() { + return null; + } + + @Override + public void setName(String name) { + + } + + @Override + public void setConfigParams(Map params) { + + } + + @Override + public Map getConfigParams() { + return null; + } + + @Override + public int getRunLevel() { + return 0; + } + + @Override + public void setRunLevel(int level) { + + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + _externalAgentMgr = ComponentContext.inject(ExternalAgentManagerImpl.class); + _externalAgentMgr.configure(name, params); + + String provisionerName = (String) params.get(ApiConstants.EXTERNAL_PROVISIONER); + externalProvisioner = _externalAgentMgr.getExternalProvisioner(provisionerName); + + _dcId = (String) params.get("zone"); + _pod = (String) params.get("pod"); + _cluster = (String) params.get("cluster"); + _guid = (String) params.get("guid"); + _url = (String) params.get("guid"); + + return true; + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + return true; + } +} diff --git a/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/guru/ExternalHypervisorGuru.java b/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/guru/ExternalHypervisorGuru.java new file mode 100644 index 000000000000..84fe1c242ef1 --- /dev/null +++ b/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/guru/ExternalHypervisorGuru.java @@ -0,0 +1,144 @@ +// 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.guru; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.StopCommand; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.host.dao.HostDetailsDao; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.hypervisor.HypervisorGuruBase; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.VirtualMachineProfileImpl; +import com.cloud.vm.VmDetailConstants; +import com.cloud.vm.dao.UserVmDao; +import org.apache.commons.collections.MapUtils; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ExternalHypervisorGuru extends HypervisorGuruBase implements HypervisorGuru { + + @Inject + private VirtualMachineManager virtualMachineManager; + @Inject + private UserVmDao _userVmDao; + @Inject + HostDao _hostDao; + @Inject + HostDetailsDao _hostDetailsDao; + + protected ExternalHypervisorGuru() { + super(); + } + + @Override + public Hypervisor.HypervisorType getHypervisorType() { + return Hypervisor.HypervisorType.External; + } + + @Override + public VirtualMachineTO implement(VirtualMachineProfile vm) { + VirtualMachineTO to = toVirtualMachineTO(vm); + return to; + } + + @Override + public boolean trackVmHostChange() { + return false; + } + + @Override + protected VirtualMachineTO toVirtualMachineTO(VirtualMachineProfile vmProfile) { + VirtualMachineTO to = super.toVirtualMachineTO(vmProfile); + + Map newDetails = new HashMap<>(); + Map toDetails = to.getDetails(); + newDetails.putAll(toDetails); + Map serviceOfferingDetails = _serviceOfferingDetailsDao.listDetailsKeyPairs(vmProfile.getServiceOfferingId()); + if (MapUtils.isNotEmpty(serviceOfferingDetails)) { + newDetails.putAll(serviceOfferingDetails); + } + if (MapUtils.isNotEmpty(newDetails)) { + to.setDetails(newDetails); + } + + return to; + } + + public List finalizeExpunge(VirtualMachine vm) { + + List commands = new ArrayList<>(); + + final StopCommand stop = new StopCommand(vm, virtualMachineManager.getExecuteInSequence(vm.getHypervisorType()), false, false); + VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + stop.setVirtualMachine(toVirtualMachineTO(profile)); + + if (Hypervisor.HypervisorType.External.equals(vm.getHypervisorType())) { + final Long hostId = vm.getHostId() != null ? vm.getHostId() : vm.getLastHostId(); + if (hostId != null) { + HostVO host = _hostDao.findById(hostId); + HashMap accessDetails = new HashMap<>(); + _hostDao.loadDetails(host); + Map hostDetails = host.getDetails(); + UserVmVO userVm = _userVmDao.findById(vm.getId()); + _userVmDao.loadDetails(userVm); + Map userVmDetails = userVm.getDetails(); + loadExternalHostAccessDetails(hostDetails, accessDetails, host.getClusterId()); + loadExternalInstanceDetails(userVmDetails, accessDetails); + + stop.setDetails(accessDetails); + stop.setExpungeVM(true); + } else { + return null; + } + } + + commands.add(stop); + + return commands; + } + + public static void loadExternalHostAccessDetails(Map hostDetails, Map accessDetails, Long clusterId) { + Map externalHostDetails = new HashMap<>(); + for (Map.Entry entry : hostDetails.entrySet()) { + if (entry.getKey().startsWith(VmDetailConstants.EXTERNAL_DETAIL_PREFIX)) { + externalHostDetails.put(entry.getKey(), entry.getValue()); + } + } + accessDetails.putAll(externalHostDetails); + } + + public static void loadExternalInstanceDetails(Map userVmDetails, Map accessDetails) { + Map externalInstanceDetails = new HashMap<>(); + for (Map.Entry entry : userVmDetails.entrySet()) { + if (entry.getKey().startsWith(VmDetailConstants.EXTERNAL_DETAIL_PREFIX)) { + externalInstanceDetails.put(entry.getKey(), entry.getValue()); + } + } + accessDetails.putAll(externalInstanceDetails); + } +} diff --git a/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/core/spring-external-core-context.xml b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/core/spring-external-core-context.xml new file mode 100644 index 000000000000..06114069fe9a --- /dev/null +++ b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/core/spring-external-core-context.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-compute/module.properties b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-compute/module.properties new file mode 100644 index 000000000000..726cb6d197d8 --- /dev/null +++ b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-compute/module.properties @@ -0,0 +1,18 @@ +# 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. +name=external-compute +parent=compute diff --git a/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-compute/spring-external-compute-context.xml b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-compute/spring-external-compute-context.xml new file mode 100644 index 000000000000..f80846178325 --- /dev/null +++ b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-compute/spring-external-compute-context.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-discoverer/module.properties b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-discoverer/module.properties new file mode 100644 index 000000000000..2220b459b441 --- /dev/null +++ b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-discoverer/module.properties @@ -0,0 +1,18 @@ +# 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. +name=external-discoverer +parent=discoverer diff --git a/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-discoverer/spring-external-discoverer-context.xml b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-discoverer/spring-external-discoverer-context.xml new file mode 100644 index 000000000000..5c8bc10db97f --- /dev/null +++ b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-discoverer/spring-external-discoverer-context.xml @@ -0,0 +1,34 @@ + + + + + + + + diff --git a/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-planner/module.properties b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-planner/module.properties new file mode 100644 index 000000000000..3d9d10b85377 --- /dev/null +++ b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-planner/module.properties @@ -0,0 +1,18 @@ +# 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. +name=external-planner +parent=planner diff --git a/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-planner/spring-external-planner-context.xml b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-planner/spring-external-planner-context.xml new file mode 100644 index 000000000000..a7407fb6f286 --- /dev/null +++ b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-planner/spring-external-planner-context.xml @@ -0,0 +1,34 @@ + + + + + + + + diff --git a/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-storage/module.properties b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-storage/module.properties new file mode 100644 index 000000000000..c040872c19c6 --- /dev/null +++ b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-storage/module.properties @@ -0,0 +1,18 @@ +# 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. +name=external-storage +parent=storage diff --git a/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-storage/spring-external-storage-context.xml b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-storage/spring-external-storage-context.xml new file mode 100644 index 000000000000..665a5dbdc303 --- /dev/null +++ b/plugins/hypervisors/external/src/main/resources/META-INF/cloudstack/external-storage/spring-external-storage-context.xml @@ -0,0 +1,32 @@ + + + + + + diff --git a/plugins/pom.xml b/plugins/pom.xml index db228881a915..2bedf138f001 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -87,6 +87,7 @@ host-allocators/random hypervisors/baremetal + hypervisors/external hypervisors/hyperv hypervisors/kvm hypervisors/ovm3 diff --git a/scripts/vm/hypervisor/external/simpleServerProvisioner/powerOperations.sh b/scripts/vm/hypervisor/external/simpleServerProvisioner/powerOperations.sh new file mode 100644 index 000000000000..7cbdfa595271 --- /dev/null +++ b/scripts/vm/hypervisor/external/simpleServerProvisioner/powerOperations.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# 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. + +parse_json() { + local json_string=$1 + declare -A arguments + while IFS= read -r line; do + key=$(echo "$line" | awk '{print $1}') + value=$(echo "$line" | awk '{print $2}') + arguments["$key"]="$value" + done < <(echo "$json_string" | jq -r 'to_entries | .[] | "\(.key) \(.value)"') + + echo "${arguments[@]}" +} + +start() { + parsed_arguments=$(parse_json "$1") + # Add code to handle start instance logic +} + +stop() { + parsed_arguments=$(parse_json "$1") + # Add code to handle stop instance logic +} + +reboot() { + parsed_arguments=$(parse_json "$1") + # Add code to handle reboot instance logic +} + +status() { + parsed_arguments=$(parse_json "$1") + # Add code to handle get power status of instance logic +} + +action=$1 +parameters=$2 +wait_time=$3 + +if [[ -z $action || -z $parameters || -z $wait_time ]]; then + exit 1 +fi + +case $action in + start) + prepare "$parameters" "$wait_time" + ;; + stop) + create "$parameters" "$wait_time" + ;; + reboot) + delete "$parameters" "$wait_time" + ;; + status) + status "$parameters" "$wait_time" + ;; + *) + exit 1 + ;; +esac + +exit 0 diff --git a/scripts/vm/hypervisor/external/simpleServerProvisioner/provisioner.sh b/scripts/vm/hypervisor/external/simpleServerProvisioner/provisioner.sh new file mode 100644 index 000000000000..46915c34ee6e --- /dev/null +++ b/scripts/vm/hypervisor/external/simpleServerProvisioner/provisioner.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# 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. + +parse_json() { + local json_string=$1 + declare -A arguments + while IFS= read -r line; do + key=$(echo "$line" | awk '{print $1}') + value=$(echo "$line" | awk '{print $2}') + arguments["$key"]="$value" + done < <(echo "$json_string" | jq -r 'to_entries | .[] | "\(.key) \(.value)"') + + echo "${arguments[@]}" +} + +generate_random_mac() { + # This is just an example + hexchars="0123456789ABCDEF" + echo "52:54:00:$(for i in {1..3}; do echo -n ${hexchars:$(( $RANDOM % 16 )):1}${hexchars:$(( $RANDOM % 16 )):1}; [[ $i -lt 3 ]] && echo -n ':'; done)" +} + +prepare() { + parsed_arguments=$(parse_json "$1") + mac_address=$(generate_random_mac) + mac_json=$(jq -n --arg mac "$mac_address" '{"mac_address": $mac}') + echo "$mac_json" + + # Add code to handle preparation logic +} + +create() { + parsed_arguments=$(parse_json "$1") + # Add code to handle creation logic +} + +delete() { + parsed_arguments=$(parse_json "$1") + # Add code to handle delete logic +} + +action=$1 +parameters=$2 +wait_time=$3 + +if [[ -z $action || -z $parameters || -z $wait_time ]]; then + exit 1 +fi + +case $action in + prepare) + prepare "$parameters" "$wait_time" + ;; + create) + create "$parameters" "$wait_time" + ;; + delete) + delete "$parameters" "$wait_time" + ;; + *) + exit 1 + ;; +esac + +exit 0 diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 474dfc096264..962274835865 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -43,6 +43,7 @@ import com.cloud.bgp.ASNumberRange; import com.cloud.dc.ASNumberRangeVO; import com.cloud.dc.ASNumberVO; +import com.cloud.dc.ClusterDetailsVO; import com.cloud.dc.VlanDetailsVO; import com.cloud.dc.dao.ASNumberDao; import com.cloud.dc.dao.ASNumberRangeDao; @@ -1530,6 +1531,11 @@ public ClusterResponse createClusterResponse(Cluster cluster, Boolean showCapaci clusterResponse.setCpuOvercommitRatio(cpuOvercommitRatio); clusterResponse.setMemoryOvercommitRatio(memoryOvercommitRatio); clusterResponse.setResourceDetails(_clusterDetailsDao.findDetails(cluster.getId())); + if (Hypervisor.HypervisorType.External.equals(cluster.getHypervisorType())) { + ClusterDetailsVO detail = _clusterDetailsDao.findDetail(cluster.getId(), ApiConstants.EXTERNAL_PROVISIONER); + clusterResponse.setExternalProvisioner(detail.getValue()); + } + if (cluster.getArch() != null) { clusterResponse.setArch(cluster.getArch().getType()); } diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 9290d2aa7014..964806ff5429 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -36,6 +36,9 @@ import javax.inject.Inject; +import com.cloud.cpu.CPU; +import com.cloud.storage.VMTemplateDetailVO; +import com.cloud.storage.dao.VMTemplateDetailsDao; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.SecurityChecker; @@ -617,6 +620,8 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q @Inject private AsyncJobManager jobManager; + @Inject + private VMTemplateDetailsDao templateDetailsDao; private SearchCriteria getMinimumCpuServiceOfferingJoinSearchCriteria(int cpu) { SearchCriteria sc = _srvOfferingJoinDao.createSearchCriteria(); @@ -4595,7 +4600,7 @@ private Pair, Integer> searchForTemplatesInternal(ListTempl null, cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), cmd.getStoragePoolId(), cmd.getImageStoreId(), hypervisorType, showDomr, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, tags, showRemovedTmpl, cmd.getIds(), parentTemplateId, cmd.getShowUnique(), - templateType, isVnf, cmd.getArch()); + templateType, isVnf, cmd.getArch(), cmd.getExternalProvisioner()); } private Pair, Integer> searchForTemplatesInternal(Long templateId, String name, String keyword, @@ -4604,7 +4609,7 @@ private Pair, Integer> searchForTemplatesInternal(Long temp boolean showDomr, boolean onlyReady, List permittedAccounts, Account caller, ListProjectResourcesCriteria listProjectResourcesCriteria, Map tags, boolean showRemovedTmpl, List ids, Long parentTemplateId, Boolean showUnique, String templateType, - Boolean isVnf, CPU.CPUArch arch) { + Boolean isVnf, CPU.CPUArch arch, String externalProvisioner) { // check if zone is configured, if not, just return empty list List hypers = null; @@ -4636,6 +4641,13 @@ private Pair, Integer> searchForTemplatesInternal(Long temp sb.join("storagePool", storagePoolSb, storagePoolSb.entity().getTemplateId(), sb.entity().getId(), JoinBuilder.JoinType.INNER); } + if (StringUtils.isNotEmpty(externalProvisioner)) { + SearchBuilder templateDetailSB = templateDetailsDao.createSearchBuilder(); + templateDetailSB.and("name", templateDetailSB.entity().getName(), SearchCriteria.Op.EQ); + templateDetailSB.and("value", templateDetailSB.entity().getValue(), SearchCriteria.Op.EQ); + sb.join("templateExternalDetail", templateDetailSB, templateDetailSB.entity().getResourceId(), sb.entity().getId(), JoinBuilder.JoinType.INNER); + } + SearchCriteria sc = sb.create(); if (imageStoreId != null) { @@ -4650,7 +4662,12 @@ private Pair, Integer> searchForTemplatesInternal(Long temp sc.setJoinParameters("storagePool", "pool_id", storagePoolId); } - // verify templateId parameter and specially handle it + if (StringUtils.isNotEmpty(externalProvisioner)) { + sc.setJoinParameters("templateExternalDetail", "name", ApiConstants.EXTERNAL_PROVISIONER); + sc.setJoinParameters("templateExternalDetail", "value", externalProvisioner); + } + + // verify templateId parameter and specially handle it if (templateId != null) { template = _templateDao.findByIdIncludingRemoved(templateId); // Done for backward compatibility - Bug-5221 if (template == null) { @@ -4900,7 +4917,7 @@ private Pair, Integer> templateChecks(boolean isIso, List readySc = _templateJoinDao.createSearchCriteria(); readySc.addOr("state", SearchCriteria.Op.EQ, TemplateState.Ready); - readySc.addOr("format", SearchCriteria.Op.EQ, ImageFormat.BAREMETAL); + readySc.addOr("format", SearchCriteria.Op.IN, ImageFormat.BAREMETAL, ImageFormat.EXTERNAL); SearchCriteria isoPerhostSc = _templateJoinDao.createSearchCriteria(); isoPerhostSc.addAnd("format", SearchCriteria.Op.EQ, ImageFormat.ISO); isoPerhostSc.addAnd("templateType", SearchCriteria.Op.EQ, TemplateType.PERHOST); @@ -5035,7 +5052,7 @@ private Pair, Integer> searchForIsosInternal(ListIsosCmd cm return searchForTemplatesInternal(cmd.getId(), cmd.getIsoName(), cmd.getKeyword(), isoFilter, true, cmd.isBootable(), cmd.getPageSizeVal(), cmd.getStartIndex(), cmd.getZoneId(), cmd.getStoragePoolId(), cmd.getImageStoreId(), hypervisorType, true, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, - tags, showRemovedISO, null, null, cmd.getShowUnique(), null, null, cmd.getArch()); + tags, showRemovedISO, null, null, cmd.getShowUnique(), null, null, cmd.getArch(), null); } @Override diff --git a/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java index feee12dcb205..17f00387470e 100644 --- a/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/HostJoinDaoImpl.java @@ -31,6 +31,7 @@ import com.cloud.user.AccountManager; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants.HostDetails; import org.apache.cloudstack.api.response.GpuResponse; import org.apache.cloudstack.api.response.HostForMigrationResponse; @@ -253,9 +254,14 @@ private void setNewHostResponseBase(HostJoinVO host, EnumSet detail } else { hostResponse.setUefiCapability(new Boolean(false)); } + if (host.getHypervisorType() != null) { + if (host.getHypervisorType() == Hypervisor.HypervisorType.External) { + hostResponse.setExternalProvisioner(hostDetails.get(ApiConstants.EXTERNAL_PROVISIONER)); + } + } } if (details.contains(HostDetails.all) && (host.getHypervisorType() == Hypervisor.HypervisorType.KVM || - host.getHypervisorType() == Hypervisor.HypervisorType.Custom)) { + host.getHypervisorType() == Hypervisor.HypervisorType.Custom || host.getHypervisorType() == Hypervisor.HypervisorType.External)) { //only kvm has the requirement to return host details try { hostResponse.setDetails(hostDetails, host.getHypervisorType()); diff --git a/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java index ac24f14b781d..1161c1de6e65 100644 --- a/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java @@ -32,10 +32,13 @@ import com.cloud.deployasis.TemplateDeployAsIsDetailVO; import com.cloud.deployasis.dao.TemplateDeployAsIsDetailsDao; import com.cloud.storage.DataStoreRole; +import com.cloud.storage.Storage; +import com.cloud.storage.VMTemplateVO; import com.cloud.storage.VMTemplateStoragePoolVO; -import com.cloud.storage.dao.VMTemplatePoolDao; -import com.cloud.storage.VnfTemplateDetailVO; import com.cloud.storage.VnfTemplateNicVO; +import com.cloud.storage.VnfTemplateDetailVO; +import com.cloud.storage.VMTemplateDetailVO; +import com.cloud.storage.dao.VMTemplatePoolDao; import com.cloud.storage.dao.VnfTemplateDetailsDao; import com.cloud.storage.dao.VnfTemplateNicDao; import com.cloud.user.dao.UserDataDao; @@ -67,10 +70,8 @@ import com.cloud.api.query.vo.ResourceTagJoinVO; import com.cloud.api.query.vo.TemplateJoinVO; import com.cloud.hypervisor.Hypervisor.HypervisorType; -import com.cloud.storage.Storage; import com.cloud.storage.Storage.TemplateType; import com.cloud.storage.VMTemplateStorageResourceAssoc.Status; -import com.cloud.storage.VMTemplateVO; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplateDetailsDao; import com.cloud.template.VirtualMachineTemplate; @@ -241,7 +242,7 @@ public TemplateResponse newTemplateResponse(EnumSet templateResponse.setDisplayText(template.getDisplayText()); templateResponse.setPublic(template.isPublicTemplate()); templateResponse.setCreated(template.getCreatedOnStore()); - if (template.getFormat() == Storage.ImageFormat.BAREMETAL) { + if (template.getFormat() == Storage.ImageFormat.BAREMETAL || template.getFormat() == Storage.ImageFormat.EXTERNAL) { // for baremetal template, we didn't download, but is ready to use. templateResponse.setReady(true); } else { @@ -253,12 +254,17 @@ public TemplateResponse newTemplateResponse(EnumSet templateResponse.setDynamicallyScalable(template.isDynamicallyScalable()); templateResponse.setSshKeyEnabled(template.isEnableSshKey()); templateResponse.setCrossZones(template.isCrossZones()); - templateResponse.setFormat(template.getFormat()); if (template.getTemplateType() != null) { templateResponse.setTemplateType(template.getTemplateType().toString()); } templateResponse.setHypervisor(template.getHypervisorType().getHypervisorDisplayName()); + if (template.getFormat() == Storage.ImageFormat.EXTERNAL) { + VMTemplateDetailVO details = _templateDetailsDao.findDetail(template.getId(), ApiConstants.EXTERNAL_PROVISIONER); + templateResponse.setExternalProvisioner(details.getValue()); + } else { + templateResponse.setFormat(template.getFormat()); + } templateResponse.setOsTypeId(template.getGuestOSUuid()); templateResponse.setOsTypeName(template.getGuestOSName()); @@ -404,6 +410,10 @@ public TemplateResponse newUpdateResponse(TemplateJoinVO result) { response.setOsTypeName(result.getGuestOSName()); response.setBootable(result.isBootable()); response.setHypervisor(result.getHypervisorType().getHypervisorDisplayName()); + if (result.getFormat() == Storage.ImageFormat.EXTERNAL) { + VMTemplateDetailVO details = _templateDetailsDao.findDetail(result.getId(), ApiConstants.EXTERNAL_PROVISIONER); + response.setExternalProvisioner(details.getValue()); + } response.setDynamicallyScalable(result.isDynamicallyScalable()); // populate owner. diff --git a/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java b/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java index 8eebc04ee683..cfbecda98101 100644 --- a/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java +++ b/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java @@ -179,6 +179,9 @@ public boolean releaseVmCapacity(VirtualMachine vm, final boolean moveFromReserv return true; } HostVO host = _hostDao.findById(hostId); + if (HypervisorType.External.equals(host.getHypervisorType())) { + return true; + } return releaseVmCapacity(vm, moveFromReserved, moveToReservered, host); } @@ -187,6 +190,9 @@ public boolean releaseVmCapacity(VirtualMachine vm, final boolean moveFromReserv if (host == null) { return true; } + if (HypervisorType.External.equals(host.getHypervisorType())) { + return true; + } final ServiceOfferingVO svo = _offeringsDao.findById(vm.getId(), vm.getServiceOfferingId()); CapacityVO capacityCpu = _capacityDao.findByHostIdType(host.getId(), Capacity.CAPACITY_TYPE_CPU); @@ -290,6 +296,9 @@ public void allocateVmCapacity(VirtualMachine vm, final boolean fromLastHost) { final long hostId = vm.getHostId(); final HostVO host = _hostDao.findById(hostId); + if (HypervisorType.External.equals(host.getHypervisorType())) { + return; + } final long clusterId = host.getClusterId(); final float cpuOvercommitRatio = Float.parseFloat(_clusterDetailsDao.findDetail(clusterId, VmDetailConstants.CPU_OVER_COMMIT_RATIO).getValue()); final float memoryOvercommitRatio = Float.parseFloat(_clusterDetailsDao.findDetail(clusterId, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO).getValue()); @@ -671,6 +680,9 @@ protected Map getVmDetailsForCapacityCalculation(long vmId) { @DB @Override public void updateCapacityForHost(final Host host) { + if (HypervisorType.External.equals(host.getHypervisorType())) { + return; + } long usedCpuCore = 0; long reservedCpuCore = 0; long usedCpu = 0; @@ -1128,6 +1140,10 @@ public boolean checkIfClusterCrossesThreshold(Long clusterId, Integer cpuRequest @Override public Pair checkIfHostHasCpuCapabilityAndCapacity(Host host, ServiceOffering offering, boolean considerReservedCapacity) { + if (HypervisorType.External.equals(host.getHypervisorType())) { + return new Pair<>(true, true); + } + int cpu_requested = offering.getCpu() * offering.getSpeed(); long ram_requested = offering.getRamSize() * 1024L * 1024L; Pair clusterDetails = getClusterValues(host.getClusterId()); diff --git a/server/src/main/java/com/cloud/configuration/Config.java b/server/src/main/java/com/cloud/configuration/Config.java index de882b4cf011..7d4a1c559653 100644 --- a/server/src/main/java/com/cloud/configuration/Config.java +++ b/server/src/main/java/com/cloud/configuration/Config.java @@ -665,7 +665,7 @@ public enum Config { String.class, "hypervisor.list", HypervisorType.KVM + "," + HypervisorType.VMware + "," + HypervisorType.XenServer + "," + HypervisorType.Hyperv + "," + - HypervisorType.BareMetal + "," + HypervisorType.Ovm + "," + HypervisorType.LXC + "," + HypervisorType.Ovm3, + HypervisorType.BareMetal + "," + HypervisorType.Ovm + "," + HypervisorType.LXC + "," + HypervisorType.Ovm3 + "," + HypervisorType.External, "The list of hypervisors that this deployment will use.", "hypervisorList", ConfigKey.Kind.CSV, diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index 56a86e65da02..001eefa22498 100644 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -50,6 +50,7 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.hypervisor.ExternalProvisioner; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroupService; @@ -384,6 +385,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Inject AlertManager _alertMgr; List _secChecker; + List externalProvisioners; @Inject CapacityDao _capacityDao; @@ -537,6 +539,14 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati private static final List SUPPORTED_ROUTING_MODE_STRS = Arrays.asList(Static.toString().toLowerCase(), Dynamic.toString().toLowerCase()); private static final long GiB_TO_BYTES = 1024 * 1024 * 1024; + public List getExternalProvisioners() { + return externalProvisioners; + } + + public void setExternalProvisioners(final List externalProvisioners) { + this.externalProvisioners = externalProvisioners; + } + @Override public boolean configure(final String name, final Map params) throws ConfigurationException { final String defaultPageSizeString = _configDao.getValue(Config.DefaultPageSize.key()); @@ -657,6 +667,25 @@ protected void validateIpAddressRelatedConfigValues(final String configName, fin } } + protected void validateExternalHypervisorConfigValues(final String configName, final String value) { + if (configName.equals("external.provisioners") && StringUtils.isNotEmpty(value)) { + if (externalProvisioners != null) { + logger.info(String.format("Found these external provisioners from the available plugins %s", externalProvisioners)); + Set externalProvisionersListFromConfig = Arrays.stream(value.split(",")) + .map(String::trim) + .collect(Collectors.toSet()); + Set externalProvisionersListFromPlugins = externalProvisioners.stream() + .map(ExternalProvisioner::getName) + .collect(Collectors.toSet()); + if (externalProvisionersListFromPlugins.containsAll(externalProvisionersListFromConfig)) { + logger.info(String.format("Found the suitable external provisioner names", value)); + return; + } + } + throw new InvalidParameterValueException(String.format("Invalid value %s for the external provisioners", value)); + } + } + @Override public boolean start() { @@ -1367,6 +1396,7 @@ protected String validateValueRange(String name, String value, Class type, Co } validateIpAddressRelatedConfigValues(name, value); + validateExternalHypervisorConfigValues(name, value); if (!shouldValidateConfigRange(name, value, configuration)) { return null; diff --git a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java index e7b926eb4e44..50c8d3bb8b51 100644 --- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -371,6 +371,8 @@ public DeployDestination planDeployment(VirtualMachineProfile vmProfile, Deploym if (plannerName == null) { if (vm.getHypervisorType() == HypervisorType.BareMetal) { plannerName = "BareMetalPlanner"; + } else if (vm.getHypervisorType() == HypervisorType.External) { + plannerName = "ExternalServerPlanner"; } else { plannerName = _configDao.getValue(Config.VmDeploymentPlanner.key()); } @@ -514,7 +516,7 @@ private DeployDestination deployInVmLastHost(VirtualMachineProfile vmProfile, De logger.debug("Last host [{}] of VM [{}] is UP and has enough capacity. Checking for suitable pools for this host under zone [{}], pod [{}] and cluster [{}].", host, vm, dc, pod, cluster); - if (vm.getHypervisorType() == HypervisorType.BareMetal) { + if (vm.getHypervisorType() == HypervisorType.BareMetal || vm.getHypervisorType() == HypervisorType.External) { DeployDestination dest = new DeployDestination(dc, pod, cluster, host, new HashMap<>(), displayStorage); logger.debug("Returning Deployment Destination: {}.", dest); return dest; @@ -627,7 +629,7 @@ private DeployDestination deployInSpecifiedHostWithoutHA(VirtualMachineProfile v host, dc, pod, cluster, vm); boolean displayStorage = getDisplayStorageFromVmProfile(vmProfile); - if (vm.getHypervisorType() == HypervisorType.BareMetal) { + if (vm.getHypervisorType() == HypervisorType.BareMetal || vm.getHypervisorType() == HypervisorType.External) { DeployDestination dest = new DeployDestination(dc, pod, cluster, host, new HashMap<>(), displayStorage); logger.debug("Returning Deployment Destination: {}.", dest); @@ -1324,7 +1326,7 @@ private DeployDestination checkClustersforDestination(List clusterList, Vi // if found suitable hosts in this cluster, find suitable storage // pools for each volume of the VM if (CollectionUtils.isNotEmpty(suitableHosts)) { - if (vmProfile.getHypervisorType() == HypervisorType.BareMetal) { + if (vmProfile.getHypervisorType() == HypervisorType.BareMetal || vmProfile.getHypervisorType() == HypervisorType.External) { DeployDestination dest = new DeployDestination(dc, pod, clusterVO, suitableHosts.get(0)); return dest; } @@ -2000,7 +2002,7 @@ public String doInTransaction(TransactionStatus status) { } Map volumeReservationMap = new HashMap<>(); - if (vm.getHypervisorType() != HypervisorType.BareMetal) { + if (vm.getHypervisorType() != HypervisorType.BareMetal && vm.getHypervisorType() != HypervisorType.External) { for (Volume vo : plannedDestination.getStorageForDisks().keySet()) { volumeReservationMap.put(vo.getId(), plannedDestination.getStorageForDisks().get(vo).getId()); } diff --git a/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java b/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java index abaf48400e23..e771a2ae4373 100644 --- a/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java +++ b/server/src/main/java/com/cloud/deploy/FirstFitPlanner.java @@ -550,7 +550,7 @@ private boolean isRootAdmin(VirtualMachineProfile vmProfile) { public boolean canHandle(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid) { // check what the ServiceOffering says. If null, check the global config ServiceOffering offering = vm.getServiceOffering(); - if (vm.getHypervisorType() != HypervisorType.BareMetal) { + if (vm.getHypervisorType() != HypervisorType.BareMetal && vm.getHypervisorType() != HypervisorType.External) { if (offering != null && offering.getDeploymentPlanner() != null) { if (offering.getDeploymentPlanner().equals(getName())) { return true; diff --git a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruManagerImpl.java b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruManagerImpl.java index 2c8af8cee1a0..d23b516e9cef 100644 --- a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruManagerImpl.java +++ b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruManagerImpl.java @@ -23,6 +23,8 @@ import javax.annotation.PostConstruct; import javax.inject.Inject; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; import org.springframework.stereotype.Component; import com.cloud.agent.api.Command; @@ -32,8 +34,10 @@ import com.cloud.utils.component.ManagerBase; @Component -public class HypervisorGuruManagerImpl extends ManagerBase implements HypervisorGuruManager { +public class HypervisorGuruManagerImpl extends ManagerBase implements HypervisorGuruManager, Configurable { + public static final ConfigKey ExternalProvisioners = new ConfigKey("Advanced", String.class, "external.provisioners", "simpleExternalProvisioner", + "List of external hypervisor provisioners", false); @Inject HostDao _hostDao; @@ -95,4 +99,13 @@ public void setHvGuruList(List hvGuruList) { this._hvGuruList = hvGuruList; } + @Override + public String getConfigComponentName() { + return HypervisorGuruManagerImpl.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[] {ExternalProvisioners}; + } } diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index df8a48bc957f..f7a607b6a900 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -263,6 +263,8 @@ import com.cloud.vm.dao.VMInstanceDao; import com.googlecode.ipv6.IPv6Address; +import static java.util.Objects.isNull; + /** * NetworkServiceImpl implements NetworkService. */ @@ -6307,4 +6309,13 @@ protected void checkCallerForPublicIpQuarantineAccess(PublicIpQuarantine publicI _accountMgr.checkAccess(callingAccount, domainOfThePreviousOwner); } + + @Override + public String getNsxSegmentId(long domainId, long accountId, long zoneId, Long vpcId, long networkId) { + String segmentName = String.format("D%s-A%s-Z%s", domainId, accountId, zoneId); + if (isNull(vpcId)) { + return String.format("%s-S%s", segmentName, networkId); + } + return String.format("%s-V%s-S%s",segmentName, vpcId, networkId); + } } diff --git a/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java b/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java index 263ff523ab6a..30703e2f5992 100644 --- a/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java +++ b/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java @@ -257,7 +257,7 @@ public boolean implement(final Network network, final NetworkOffering offering, @Override public boolean prepare(final Network network, final NicProfile nic, final VirtualMachineProfile vm, final DeployDestination dest, final ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException { - if (vm.getType() != VirtualMachine.Type.User || vm.getHypervisorType() == HypervisorType.BareMetal) { + if (vm.getType() != VirtualMachine.Type.User || vm.getHypervisorType() == HypervisorType.BareMetal || vm.getHypervisorType() == HypervisorType.External) { return false; } diff --git a/server/src/main/java/com/cloud/network/guru/GuestNetworkGuru.java b/server/src/main/java/com/cloud/network/guru/GuestNetworkGuru.java index 96c3da66c090..954090e23463 100644 --- a/server/src/main/java/com/cloud/network/guru/GuestNetworkGuru.java +++ b/server/src/main/java/com/cloud/network/guru/GuestNetworkGuru.java @@ -22,6 +22,8 @@ import javax.inject.Inject; +import com.cloud.agent.AgentManager; +import com.cloud.vm.dao.UserVmDetailsDao; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; @@ -121,6 +123,10 @@ public abstract class GuestNetworkGuru extends AdapterBase implements NetworkGur Ipv6AddressManager ipv6AddressManager; @Inject DomainRouterDao domainRouterDao; + @Inject + private AgentManager _agentMgr; + @Inject + private UserVmDetailsDao userVmDetailsDao; Random _rand = new Random(System.currentTimeMillis()); diff --git a/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java b/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java index eb1c5cfd8567..7ae11a6c382a 100644 --- a/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java +++ b/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java @@ -24,6 +24,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import javax.annotation.PostConstruct; import javax.inject.Inject; @@ -659,8 +660,10 @@ protected List getHypervisors(final RouterDeploymentDefinition r hypervisors.add(defaults); } if (dest.getCluster() != null) { - if (dest.getCluster().getHypervisorType() == HypervisorType.Ovm) { - hypervisors.add(getClusterToStartDomainRouterForOvm(dest.getCluster().getPodId())); + HypervisorType destClusterHypType = dest.getCluster().getHypervisorType(); + Set hypervisorsTypesToCheck = Set.of(HypervisorType.Ovm, HypervisorType.BareMetal, HypervisorType.External); + if (hypervisorsTypesToCheck.contains(destClusterHypType)) { + hypervisors.add(getClusterToStartDomainRouterOtherThanProvidedType(dest.getCluster().getPodId())); } else { hypervisors.add(dest.getCluster().getHypervisorType()); } @@ -685,10 +688,10 @@ protected List getHypervisors(final RouterDeploymentDefinition r * Ovm won't support any system. So we have to choose a partner cluster in * the same pod to start domain router for us */ - protected HypervisorType getClusterToStartDomainRouterForOvm(final long podId) { + protected HypervisorType getClusterToStartDomainRouterOtherThanProvidedType(final long podId) { final List clusters = _clusterDao.listByPodId(podId); for (final ClusterVO cv : clusters) { - if (cv.getHypervisorType() == HypervisorType.Ovm || cv.getHypervisorType() == HypervisorType.BareMetal) { + if (cv.getHypervisorType() == HypervisorType.Ovm || cv.getHypervisorType() == HypervisorType.BareMetal || cv.getHypervisorType() == HypervisorType.External) { continue; } diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java index dc6b6cd3d406..fc65b04d5af9 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -315,6 +315,7 @@ protected void setupSupportedVpcHypervisorsList() { hTypes.add(HypervisorType.LXC); hTypes.add(HypervisorType.Hyperv); hTypes.add(HypervisorType.Ovm3); + hTypes.add(HypervisorType.External); } private void checkVpcDns(VpcOffering vpcOffering, String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2) { diff --git a/server/src/main/java/com/cloud/resource/DiscovererBase.java b/server/src/main/java/com/cloud/resource/DiscovererBase.java index e594a0a0aebe..300fb7baf6f2 100644 --- a/server/src/main/java/com/cloud/resource/DiscovererBase.java +++ b/server/src/main/java/com/cloud/resource/DiscovererBase.java @@ -17,6 +17,7 @@ package com.cloud.resource; import com.cloud.configuration.Config; +import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterVO; import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; @@ -40,6 +41,8 @@ public abstract class DiscovererBase extends AdapterBase implements Discoverer { @Inject protected ClusterDao _clusterDao; @Inject + protected ClusterDetailsDao _clusterDetailsDao; + @Inject protected ConfigurationDao _configDao; @Inject protected NetworkModel _networkMgr; diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java index 7c997cc49bc3..cf9805afa810 100755 --- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java @@ -37,6 +37,16 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.alert.AlertManager; +import com.cloud.cpu.CPU; +import com.cloud.exception.StorageConflictException; +import com.cloud.exception.StorageUnavailableException; +import com.cloud.host.HostTagVO; +import com.cloud.hypervisor.HypervisorGuruManagerImpl; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.hypervisor.HypervisorGuru; import org.apache.cloudstack.alert.AlertService; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; @@ -60,6 +70,7 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.utils.identity.ManagementServerNode; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.ObjectUtils; import org.springframework.stereotype.Component; @@ -467,6 +478,14 @@ public List discoverCluster(final AddClusterCmd cmd) throws I throw new InvalidParameterValueException("Unable to resolve " + cmd.getHypervisor() + " to a supported "); } + if (Hypervisor.HypervisorType.External.equals(hypervisorType)) { + validateExternalHypervisorParams(cmd.getExternalProvisioner()); + } + + if (StringUtils.isNotEmpty(cmd.getExternalProvisioner()) && !hypervisorType.equals(HypervisorType.External)) { + throw new InvalidParameterValueException("externalprovisioner parameter is allowed only for hypervisor type External"); + } + if (zone.isSecurityGroupEnabled() && zone.getNetworkType().equals(NetworkType.Advanced)) { if (hypervisorType != HypervisorType.KVM && hypervisorType != HypervisorType.XenServer && hypervisorType != HypervisorType.LXC && hypervisorType != HypervisorType.Simulator) { @@ -534,6 +553,9 @@ public List discoverCluster(final AddClusterCmd cmd) throws I details.put("ovm3pool", allParams.get("ovm3pool")); details.put("ovm3cluster", allParams.get("ovm3cluster")); } + if (hypervisorType == HypervisorType.External) { + details.put(ApiConstants.EXTERNAL_PROVISIONER, cmd.getExternalProvisioner()); + } details.put(VmDetailConstants.CPU_OVER_COMMIT_RATIO, CapacityManager.CpuOverprovisioningFactor.value().toString()); details.put(VmDetailConstants.MEMORY_OVER_COMMIT_RATIO, CapacityManager.MemOverprovisioningFactor.value().toString()); _clusterDetailsDao.persist(cluster.getId(), details); @@ -594,6 +616,18 @@ public List discoverCluster(final AddClusterCmd cmd) throws I } } + private void validateExternalHypervisorParams(String provisioner) { + if (provisioner == null) { + throw new InvalidParameterValueException("For hypervisor type external, provisioner input is required"); + } + List externalProvisionersListFromConfig = Arrays.stream(HypervisorGuruManagerImpl.ExternalProvisioners.value().split(",")) + .map(String::trim) + .collect(Collectors.toList()); + if (!externalProvisionersListFromConfig.contains(provisioner)) { + throw new InvalidParameterValueException(String.format("Provisioner name %s is not valid", provisioner)); + } + } + @Override public Discoverer getMatchingDiscover(final Hypervisor.HypervisorType hypervisorType) { for (final Discoverer discoverer : _discoverers) { @@ -638,23 +672,34 @@ public List discoverHosts(final AddHostCmd cmd) throws IllegalAr throw ex; } } + String externalProvisioner = cmd.getExternalProvisioner(); + if (cluster.getHypervisorType().equals(HypervisorType.External) && StringUtils.isNotEmpty(externalProvisioner)) { + validateExternalHypervisorParams(externalProvisioner); + ClusterDetailsVO provisioner = _clusterDetailsDao.findDetail(clusterId, ApiConstants.EXTERNAL_PROVISIONER); + if (!provisioner.getValue().equals(externalProvisioner)) { + final CloudRuntimeException ex = + new CloudRuntimeException("Provisioner type of the host and cluster did not match"); + ex.addProxyObject(cluster.getUuid(), "clusterId"); + throw ex; + } + } } } String hypervisorType = cmd.getHypervisor().equalsIgnoreCase(HypervisorGuru.HypervisorCustomDisplayName.value()) ? "Custom" : cmd.getHypervisor(); - return discoverHostsFull(dcId, podId, clusterId, clusterName, url, username, password, hypervisorType, hostTags, cmd.getFullUrlParams(), false); + return discoverHostsFull(dcId, podId, clusterId, clusterName, url, username, password, hypervisorType, hostTags, cmd.getFullUrlParams(), false, cmd.getExternalDetails()); } @Override public List discoverHosts(final AddSecondaryStorageCmd cmd) throws IllegalArgumentException, DiscoveryException, InvalidParameterValueException { final Long dcId = cmd.getZoneId(); final String url = cmd.getUrl(); - return discoverHostsFull(dcId, null, null, null, url, null, null, "SecondaryStorage", null, null, false); + return discoverHostsFull(dcId, null, null, null, url, null, null, "SecondaryStorage", null, null, false, null); } private List discoverHostsFull(final Long dcId, final Long podId, Long clusterId, final String clusterName, String url, String username, String password, - final String hypervisorType, final List hostTags, final Map params, final boolean deferAgentCreation) throws IllegalArgumentException, DiscoveryException, + final String hypervisorType, final List hostTags, final Map params, final boolean deferAgentCreation, Map cmdDetails) throws IllegalArgumentException, DiscoveryException, InvalidParameterValueException { URI uri; @@ -801,6 +846,15 @@ private List discoverHostsFull(final Long dcId, final Long podId, Long c boolean isHypervisorTypeSupported = false; for (final Discoverer discoverer : _discoverers) { if (params != null) { + ClusterVO clusterVO = _clusterDao.findById(clusterId); + if (HypervisorType.External.equals(clusterVO.getHypervisorType())) { + if (params.get(ApiConstants.EXTERNAL_PROVISIONER) != null) { + params.put(ApiConstants.EXTERNAL_PROVISIONER, params.get(ApiConstants.EXTERNAL_PROVISIONER)); + } else { + ClusterDetailsVO provisioner = _clusterDetailsDao.findDetail(clusterId, ApiConstants.EXTERNAL_PROVISIONER); + params.put(ApiConstants.EXTERNAL_PROVISIONER, provisioner.getValue()); + } + } discoverer.putParam(params); } @@ -859,10 +913,21 @@ private List discoverHostsFull(final Long dcId, final Long podId, Long c } HostVO host; + Map details = entry.getValue(); + if (details == null) { + details = new HashMap<>(); + } + + details.putAll(cmdDetails); + ClusterVO clusterVO = _clusterDao.findById(clusterId); + if (HypervisorType.External.equals(clusterVO.getHypervisorType())) { + details.put(ApiConstants.EXTERNAL_PROVISIONER, params.get(ApiConstants.EXTERNAL_PROVISIONER)); + } + if (deferAgentCreation) { - host = (HostVO)createHostAndAgentDeferred(resource, entry.getValue(), true, hostTags, false); + host = (HostVO)createHostAndAgentDeferred(resource, details, true, hostTags, false); } else { - host = (HostVO)createHostAndAgent(resource, entry.getValue(), true, hostTags, false); + host = (HostVO)createHostAndAgent(resource, details, true, hostTags, false); } if (host != null) { hosts.add(host); @@ -1931,11 +1996,11 @@ private void updateHostTags(HostVO host, Long hostId, List hostTags, Boo @Override public Host updateHost(final UpdateHostCmd cmd) throws NoTransitionException { return updateHost(cmd.getId(), cmd.getName(), cmd.getOsCategoryId(), - cmd.getAllocationState(), cmd.getUrl(), cmd.getHostTags(), cmd.getIsTagARule(), cmd.getAnnotation(), false); + cmd.getAllocationState(), cmd.getUrl(), cmd.getHostTags(), cmd.getIsTagARule(), cmd.getAnnotation(), false, cmd.getExternalDetails()); } private Host updateHost(Long hostId, String name, Long guestOSCategoryId, String allocationState, - String url, List hostTags, Boolean isTagARule, String annotation, boolean isUpdateFromHostHealthCheck) throws NoTransitionException { + String url, List hostTags, Boolean isTagARule, String annotation, boolean isUpdateFromHostHealthCheck, Map externalDetails) throws NoTransitionException { // Verify that the host exists final HostVO host = _hostDao.findById(hostId); if (host == null) { @@ -1959,6 +2024,10 @@ private Host updateHost(Long hostId, String name, Long guestOSCategoryId, String updateHostTags(host, hostId, hostTags, isTagARule); } + if (MapUtils.isNotEmpty(externalDetails)) { + updateExternalHypervisorDetails(hostId, externalDetails); + } + if (url != null) { _storageMgr.updateSecondaryStorage(hostId, url); } @@ -1976,6 +2045,16 @@ private Host updateHost(Long hostId, String name, Long guestOSCategoryId, String return updatedHost; } + private void updateExternalHypervisorDetails(long hostId, Map externalDetails) { + HostVO host = _hostDao.findById(hostId); + _hostDao.loadDetails(host); + for (Map.Entry entry : externalDetails.entrySet()) { + host.getDetails().put(entry.getKey(), entry.getValue()); + } + + _hostDao.saveDetails(host); + } + private void sendAlertAndAnnotationForAutoEnableDisableKVMHostFeature(HostVO host, String allocationState, boolean isUpdateFromHostHealthCheck, boolean isUpdateHostAllocation, String annotation) { @@ -2015,7 +2094,7 @@ private void sendAlertAndAnnotationForAutoEnableDisableKVMHostFeature(HostVO hos @Override public Host autoUpdateHostAllocationState(Long hostId, ResourceState.Event resourceEvent) throws NoTransitionException { - return updateHost(hostId, null, null, resourceEvent.toString(), null, null, null, null, true); + return updateHost(hostId, null, null, resourceEvent.toString(), null, null, null, null, true, null); } @Override @@ -2066,7 +2145,7 @@ public List getSupportedHypervisorTypes(final long zoneId, final for (final ClusterVO cluster : clustersForZone) { final HypervisorType hType = cluster.getHypervisorType(); - if (!forVirtualRouter || (hType != HypervisorType.BareMetal && hType != HypervisorType.Ovm)) { + if (!forVirtualRouter || (hType != HypervisorType.BareMetal && hType != HypervisorType.External && hType != HypervisorType.Ovm)) { hypervisorTypes.add(hType); } } diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java index 622f73c1e37d..84b7c906b77e 100644 --- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java @@ -2600,6 +2600,13 @@ public Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId, Boolean checkDeviceId(deviceId, volumeToAttach, vm); + HypervisorType rootDiskHyperType = vm.getHypervisorType(); + HypervisorType volumeToAttachHyperType = _volsDao.getHypervisorType(volumeToAttach.getId()); + + if (HypervisorType.External.equals(rootDiskHyperType)) { + throw new InvalidParameterValueException("Volume operations are not allowed for External hypervisor type"); + } + checkNumberOfAttachedVolumes(deviceId, vm); excludeLocalStorageIfNeeded(volumeToAttach); @@ -2608,9 +2615,6 @@ public Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId, Boolean checkRightsToAttach(caller, volumeToAttach, vm); - HypervisorType rootDiskHyperType = vm.getHypervisorType(); - HypervisorType volumeToAttachHyperType = _volsDao.getHypervisorType(volumeToAttach.getId()); - StoragePoolVO volumeToAttachStoragePool = _storagePoolDao.findById(volumeToAttach.getPoolId()); if (logger.isTraceEnabled() && volumeToAttachStoragePool != null) { logger.trace("volume to attach {} has a primary storage assigned to begin with {}", @@ -3308,6 +3312,11 @@ public Volume migrateVolume(MigrateVolumeCmd cmd) { throw new InvalidParameterValueException("Volume " + vol + " is already on the destination storage pool"); } + HypervisorType hypervisorType = _volsDao.getHypervisorType(volumeId); + if (HypervisorType.External.equals(hypervisorType)) { + throw new InvalidParameterValueException("Volume migration operation is not allowed for hypervisor type External"); + } + boolean liveMigrateVolume = false; boolean srcAndDestOnStorPool = false; Long instanceId = vol.getInstanceId(); @@ -3410,7 +3419,6 @@ public Volume migrateVolume(MigrateVolumeCmd cmd) { throw new CloudRuntimeException("Storage pool " + destPool.getName() + " is not suitable to migrate volume " + vol.getName()); } - HypervisorType hypervisorType = _volsDao.getHypervisorType(volumeId); DiskProfile diskProfile = new DiskProfile(vol, diskOffering, hypervisorType); Pair volumeDiskProfilePair = new Pair<>(vol, diskProfile); if (!storageMgr.storagePoolHasEnoughSpace(Collections.singletonList(volumeDiskProfilePair), destPool)) { @@ -3772,6 +3780,9 @@ private Snapshot takeSnapshotInternal(Long volumeId, Long policyId, Long snapsho if (volume == null) { throw new InvalidParameterValueException("Creating snapshot failed due to volume:" + volumeId + " doesn't exist"); } + if (HypervisorType.External.equals(volume.getHypervisorType())) { + throw new InvalidParameterValueException("Snapshot operations are not allowed for External hypervisor type"); + } if (policyId != null && policyId > 0) { if (CollectionUtils.isNotEmpty(zoneIds)) { throw new InvalidParameterValueException(String.format("%s can not be specified for snapshots linked with snapshot policy", ApiConstants.ZONE_ID_LIST)); @@ -4550,7 +4561,12 @@ private VolumeVO sendAttachVolumeCommand(UserVmVO vm, VolumeVO volumeToAttach, L String errorMsg = "Failed to attach volume " + volumeToAttach.getName() + " to VM " + vm.getHostName(); boolean sendCommand = vm.getState() == State.Running; AttachAnswer answer = null; + HypervisorType rootDiskHyperType = vm.getHypervisorType(); StoragePoolVO volumeToAttachStoragePool = _storagePoolDao.findById(volumeToAttach.getPoolId()); + if (HypervisorType.External.equals(rootDiskHyperType)) { + throw new InvalidParameterValueException("Volume operations are not allowed for External hypervisor type"); + } + if (logger.isTraceEnabled() && volumeToAttachStoragePool != null) { logger.trace("storage is gotten from volume to attach: {}", volumeToAttachStoragePool); } diff --git a/server/src/main/java/com/cloud/template/TemplateAdapter.java b/server/src/main/java/com/cloud/template/TemplateAdapter.java index 27ff563655dd..eb55718247e6 100644 --- a/server/src/main/java/com/cloud/template/TemplateAdapter.java +++ b/server/src/main/java/com/cloud/template/TemplateAdapter.java @@ -43,6 +43,7 @@ public static class TemplateAdapterType { public static final TemplateAdapterType Hypervisor = new TemplateAdapterType("HypervisorAdapter"); public static final TemplateAdapterType BareMetal = new TemplateAdapterType("BareMetalAdapter"); + public static final TemplateAdapterType External = new TemplateAdapterType("ExternalAdapter"); public TemplateAdapterType(String name) { _name = name; diff --git a/server/src/main/java/com/cloud/template/TemplateAdapterBase.java b/server/src/main/java/com/cloud/template/TemplateAdapterBase.java index 5b7185b94c5d..4bc9a4241725 100644 --- a/server/src/main/java/com/cloud/template/TemplateAdapterBase.java +++ b/server/src/main/java/com/cloud/template/TemplateAdapterBase.java @@ -118,7 +118,7 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat protected @Inject ImageStoreDao _imgStoreDao; @Inject - TemplateManager templateMgr; + protected TemplateManager templateMgr; @Inject ConfigurationServer _configServer; @Inject @@ -312,6 +312,15 @@ public TemplateProfile prepare(RegisterTemplateCmd cmd) throws ResourceAllocatio } } } + + if (HypervisorType.External.equals(hypervisorType) && MapUtils.isNotEmpty(cmd.getExternalDetails())) { + if (details != null) { + details.putAll(cmd.getExternalDetails()); + } else { + details = cmd.getExternalDetails(); + } + } + return prepare(false, CallContext.current().getCallingUserId(), cmd.getTemplateName(), cmd.getDisplayText(), cmd.getArch(), cmd.getBits(), cmd.isPasswordEnabled(), cmd.getRequiresHvm(), cmd.getUrl(), cmd.isPublic(), cmd.isFeatured(), cmd.isExtractable(), cmd.getFormat(), cmd.getOsTypeId(), zoneId, hypervisorType, cmd.getChecksum(), true, cmd.getTemplateTag(), owner, details, cmd.isSshKeyEnabled(), null, cmd.isDynamicallyScalable(), templateType, diff --git a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java index 9f23bdef142d..7bf42cdcc776 100755 --- a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java @@ -324,6 +324,8 @@ private TemplateAdapter getAdapter(HypervisorType type) { TemplateAdapter adapter = null; if (type == HypervisorType.BareMetal) { adapter = AdapterBase.getAdapterByName(_adapters, TemplateAdapterType.BareMetal.getName()); + } else if (type == HypervisorType.External) { + adapter = AdapterBase.getAdapterByName(_adapters, TemplateAdapterType.External.getName()); } else { // Get template adapter according to hypervisor adapter = AdapterBase.getAdapterByName(_adapters, type.name()); @@ -936,7 +938,7 @@ public VirtualMachineTemplate copyTemplate(CopyTemplateCmd cmd) throws StorageUn List failedZones = new ArrayList<>(); boolean success = false; - if (template.getHypervisorType() == HypervisorType.BareMetal) { + if (template.getHypervisorType() == HypervisorType.BareMetal || template.getHypervisorType() == HypervisorType.External) { if (template.isCrossZones()) { logger.debug("Template {} is cross-zone, don't need to copy", template); return template; @@ -1195,6 +1197,10 @@ public boolean attachIso(long isoId, long vmId, boolean forced) { throw new InvalidParameterValueException("Operation not supported on Shared FileSystem Instance"); } + if (HypervisorType.External.equals(vm.getHypervisorType())) { + throw new InvalidParameterValueException("Attach ISO operation is not allowed for External hypervisor type"); + } + VMTemplateVO iso = _tmpltDao.findById(isoId); if (iso == null || iso.getRemoved() != null) { throw new InvalidParameterValueException("Unable to find an ISO with id " + isoId); @@ -2331,6 +2337,15 @@ void validateDetails(VMTemplateVO template, Map details) { if (MapUtils.isEmpty(details)) { return; } + + if (HypervisorType.External.equals(template.getHypervisorType())) { + _tmpltDao.loadDetails(template); + Map existingDetails = template.getDetails(); + if (!details.get(ApiConstants.EXTERNAL_PROVISIONER.toString()).equals(existingDetails.get(ApiConstants.EXTERNAL_PROVISIONER.toString()))) { + throw new InvalidParameterValueException("Provisioner name cannot be changed for the hypervisor type External"); + } + } + String bootMode = details.get(ApiConstants.BootType.UEFI.toString()); if (bootMode == null) { return; diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index bc21a3a92821..2f0c633a7517 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -4266,7 +4266,7 @@ private UserVm getUncheckedUserVmResource(DataCenter zone, String hostName, Stri } } - if (hypervisorType != HypervisorType.BareMetal) { + if (hypervisorType != HypervisorType.BareMetal && hypervisorType != HypervisorType.External) { // check if we have available pools for vm deployment long availablePools = _storagePoolDao.countPoolsByStatus(StoragePoolStatus.Up); if (availablePools < 1) { @@ -8230,6 +8230,10 @@ public UserVm restoreVM(RestoreVMCmd cmd) throws InsufficientCapacityException, } } + if (HypervisorType.External.equals(vm.getHypervisorType())) { + throw new InvalidParameterValueException("Restore VM instance operation is not allowed for External hypervisor type"); + } + //check if there are any active snapshots on volumes associated with the VM logger.debug("Checking if there are any ongoing snapshots on the ROOT volumes associated with VM {}", vm); if (checkStatusOfVolumeSnapshots(vm, Volume.Type.ROOT)) { diff --git a/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java b/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java index 907182edc2ab..aea0aa357f95 100644 --- a/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java @@ -327,6 +327,10 @@ public VMSnapshot allocVMSnapshot(Long vmId, String vsDisplayName, String vsDesc throw new InvalidParameterValueException("Creating VM snapshot failed due to VM:" + vmId + " is a system VM or does not exist"); } + if (HypervisorType.External.equals(userVmVo.getHypervisorType())) { + throw new InvalidParameterValueException("VM snapshot operation is not allowed for hypervisor type External"); + } + // VM snapshot with memory is not supported for VGPU Vms if (snapshotMemory && _serviceOfferingDetailsDao.findDetail(userVmVo.getServiceOfferingId(), GPU.Keys.vgpuType.toString()) != null) { throw new InvalidParameterValueException("VM snapshot with MEMORY is not supported for vGPU enabled VMs."); diff --git a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java index b480b37cd89c..65320e4de64e 100644 --- a/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java @@ -208,6 +208,11 @@ public ConsoleEndpoint generateConsoleEndpoint(Long vmId, String extraSecurityTo return new ConsoleEndpoint(false, null, "Cannot find VM with ID " + vmId); } + if (Hypervisor.HypervisorType.External.equals(vm.getHypervisorType())) { + logger.info("Console access to this instance cannot be provided in case of hypervisor type External"); + return new ConsoleEndpoint(false, null, "Console access to this instance cannot be provided"); + } + if (!checkSessionPermission(vm, account)) { return new ConsoleEndpoint(false, null, "Permission denied"); } diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml index 6edf206709c7..52205d022dc5 100644 --- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml @@ -135,6 +135,7 @@ + diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index f07d2af21af2..f11275a0ec97 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -743,7 +743,7 @@ private void prepareAndRunConfigureCustomRootDiskSizeTest(Map cu public void verifyIfHypervisorSupportRootdiskSizeOverrideTest() { Hypervisor.HypervisorType[] hypervisorTypeArray = Hypervisor.HypervisorType.values(); int exceptionCounter = 0; - int expectedExceptionCounter = hypervisorTypeArray.length - 5; + int expectedExceptionCounter = hypervisorTypeArray.length - 6; for(int i = 0; i < hypervisorTypeArray.length; i++) { if (hypervisorTypeArray[i].isFunctionalitySupported(Hypervisor.HypervisorType.Functionality.RootDiskSizeOverride)) { diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java index 7f4344f30e42..4b12fa0fab10 100644 --- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java @@ -1125,4 +1125,9 @@ public List getInternalLoadBalancerElements( @Override public void expungeLbVmRefs(List vmIds, Long batchSize) { } + + @Override + public String getNsxSegmentId(long domainId, long accountId, long zoneId, Long vpcId, long networkId) { + return null; + } } diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 7d2f0327dfb3..6dd7b50bb8f4 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -253,6 +253,7 @@ "label.add.disk.offering": "Add disk offering", "label.add.domain": "Add domain", "label.add.egress.rule": "Add egress rule", +"label.add.external.details": "Add external details", "label.add.f5.device": "Add F5 device", "label.add.firewall": "Add firewall rule", "label.add.firewallrule": "Add Firewall Rule", @@ -982,7 +983,10 @@ "label.export.rules": "Export Rules", "label.ext.hostname.tooltip": "External Host Name or IP Address", "label.external.managed": "ExternalManaged", +"label.external.details": "External provisioning details", +"label.external.details.tooltip": "Details that will be passed to the external provisioner while deploying an instance", "label.external": "External", +"label.externalprovisioner": "External provisioner", "label.external.link": "External link", "label.externalid": "External Id", "label.externalloadbalanceripaddress": "External load balancer IP address.", @@ -2094,6 +2098,7 @@ "label.servicelist": "Services", "label.serviceofferingid": "Compute offering", "label.serviceofferingname": "Compute offering", +"label.serviceofferingdetails": "Details", "label.sessions": "Active client sessions", "label.set.default.nic": "Set default NIC", "label.set.reservation": "Set reservation", @@ -2780,6 +2785,7 @@ "message.add.firewall.rule.processing": "Adding new Firewall rule...", "message.add.firewallrule.failed": "Adding Firewall Rule failed", "message.add.host": "Please specify the following parameters to add a new host.", +"message.add.external.details": "Details to be sent to external system on any operation.", "message.add.host.sshkey": "WARNING: In order to add a host with SSH key, you must ensure your hypervisor host has been configured correctly.", "message.add.iprange.processing": "Adding IP Range...", "message.add.ipv4.subnet.for.guest.network.failed": "Failed to add IPv4 subnet for guest network", @@ -3244,6 +3250,7 @@ "message.host.controlstate.retry": "Some actions on this Instance will fail, if so please wait a while and retry.", "message.host.dedicated": "Host Dedicated", "message.host.dedication.released": "Host dedication released.", +"message.host.external.datadisk": "Usage of data disks for the selected template is not applicable", "message.import.running.instance.warning": "The selected VM is powered-on on the VMware Datacenter. The recommended state to convert a VMware VM into KVM is powered-off after a graceful shutdown of the guest OS.", "message.import.volume": "Please specify the domain, account or project name.
If not set, the volume will be imported for the caller.", "message.info.cloudian.console": "Cloudian Management Console should open in another window.", diff --git a/ui/src/components/view/DetailSettings.vue b/ui/src/components/view/DetailSettings.vue index 9f808b907c75..84ee1e931f5c 100644 --- a/ui/src/components/view/DetailSettings.vue +++ b/ui/src/components/view/DetailSettings.vue @@ -42,8 +42,7 @@ :filterOption="(input, option) => filterOption(input, option, 'key')" v-model:value="newKey" :options="detailKeys" - :placeholder="$t('label.name')" - @change="e => onAddInputChange(e, 'newKey')" /> + :placeholder="$t('label.name')" /> + :placeholder="$t('label.value')" /> @@ -245,6 +243,7 @@ export default { }, onAddInputChange (val, obj) { this.error = false + console.log(val) this[obj] = val }, isAdminOrOwner () { diff --git a/ui/src/config/section/image.js b/ui/src/config/section/image.js index 46dec2e1b249..1855ffdf168f 100644 --- a/ui/src/config/section/image.js +++ b/ui/src/config/section/image.js @@ -58,7 +58,7 @@ export default { return fields }, details: () => { - var fields = ['name', 'id', 'displaytext', 'checksum', 'hypervisor', 'arch', 'format', 'ostypename', 'size', 'physicalsize', 'isready', 'passwordenabled', + var fields = ['name', 'id', 'displaytext', 'checksum', 'hypervisor', 'arch', 'format', 'externalprovisioner', 'ostypename', 'size', 'physicalsize', 'isready', 'passwordenabled', 'crossZones', 'templatetype', 'directdownload', 'deployasis', 'ispublic', 'isfeatured', 'isextractable', 'isdynamicallyscalable', 'crosszones', 'type', 'account', 'domain', 'created', 'userdatadetails', 'userdatapolicy'] if (['Admin'].includes(store.getters.userInfo.roletype)) { diff --git a/ui/src/config/section/infra/clusters.js b/ui/src/config/section/infra/clusters.js index 8021b1f6760a..5e61c5b18d33 100644 --- a/ui/src/config/section/infra/clusters.js +++ b/ui/src/config/section/infra/clusters.js @@ -35,7 +35,7 @@ export default { fields.push('zonename') return fields }, - details: ['name', 'id', 'allocationstate', 'clustertype', 'managedstate', 'arch', 'hypervisortype', 'podname', 'zonename', 'drsimbalance'], + details: ['name', 'id', 'allocationstate', 'clustertype', 'managedstate', 'arch', 'hypervisortype', 'externalprovisioner', 'podname', 'zonename', 'drsimbalance'], related: [{ name: 'host', title: 'label.hosts', diff --git a/ui/src/config/section/infra/hosts.js b/ui/src/config/section/infra/hosts.js index 5810c9b146fe..140f0e830fd3 100644 --- a/ui/src/config/section/infra/hosts.js +++ b/ui/src/config/section/infra/hosts.js @@ -47,7 +47,7 @@ export default { fields.push('managementservername') return fields }, - details: ['name', 'id', 'resourcestate', 'ipaddress', 'hypervisor', 'arch', 'type', 'clustername', 'podname', 'zonename', 'managementservername', 'disconnected', 'created'], + details: ['name', 'id', 'resourcestate', 'ipaddress', 'hypervisor', 'externalprovisioner', 'arch', 'type', 'clustername', 'podname', 'zonename', 'managementservername', 'disconnected', 'created', 'details'], tabs: [{ name: 'details', component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue'))) diff --git a/ui/src/config/section/offering.js b/ui/src/config/section/offering.js index f83daaea7638..0f4255ae69f1 100644 --- a/ui/src/config/section/offering.js +++ b/ui/src/config/section/offering.js @@ -40,7 +40,7 @@ export default { filters: ['active', 'inactive'], columns: ['name', 'displaytext', 'state', 'cpunumber', 'cpuspeed', 'memory', 'domain', 'zone', 'order'], details: () => { - var fields = ['name', 'id', 'displaytext', 'offerha', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'storagetags', 'domain', 'zone', 'created', 'dynamicscalingenabled', 'diskofferingstrictness', 'encryptroot', 'purgeresources'] + var fields = ['name', 'id', 'displaytext', 'offerha', 'provisioningtype', 'storagetype', 'iscustomized', 'iscustomizediops', 'limitcpuuse', 'cpunumber', 'cpuspeed', 'memory', 'hosttags', 'tags', 'storagetags', 'domain', 'zone', 'created', 'dynamicscalingenabled', 'diskofferingstrictness', 'encryptroot', 'purgeresources', 'serviceofferingdetails'] if (store.getters.apis.createServiceOffering && store.getters.apis.createServiceOffering.params.filter(x => x.name === 'storagepolicy').length > 0) { fields.splice(6, 0, 'vspherestoragepolicy') diff --git a/ui/src/views/compute/DeployVM.vue b/ui/src/views/compute/DeployVM.vue index c6b6842bb687..77e480996348 100644 --- a/ui/src/views/compute/DeployVM.vue +++ b/ui/src/views/compute/DeployVM.vue @@ -173,7 +173,7 @@ :key="templateKey" @handle-search-filter="($event) => fetchAllTemplates($event)" @update-template-iso="updateFieldValue" /> -
+
{{ $t('label.override.rootdisk.size') }} - + {{ $t('label.override.root.diskoffering') }} - + @@ -785,6 +789,58 @@