diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 01ad12a71e0..689676290b3 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -29,9 +29,9 @@ import org.apache.cloudstack.api.response.PodResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.ha.HAConfig; +import org.apache.cloudstack.quota.QuotaTariff; import org.apache.cloudstack.storage.object.Bucket; import org.apache.cloudstack.storage.object.ObjectStore; -import org.apache.cloudstack.quota.QuotaTariff; import org.apache.cloudstack.usage.Usage; import org.apache.cloudstack.vm.schedule.VMSchedule; @@ -1229,4 +1229,8 @@ public class EventTypes { public static boolean isVpcEvent(String eventType) { return EventTypes.EVENT_VPC_CREATE.equals(eventType) || EventTypes.EVENT_VPC_DELETE.equals(eventType); } + + public static void addEntityEventDetail(String event, Class clazz) { + entityEventDetails.put(event, clazz); + } } diff --git a/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesServiceHelper.java similarity index 94% rename from api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java rename to api/src/main/java/com/cloud/kubernetes/cluster/KubernetesServiceHelper.java index 577d60e0939..a13c1b3a6a8 100644 --- a/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelper.java +++ b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesServiceHelper.java @@ -21,7 +21,7 @@ import org.apache.cloudstack.acl.ControlledEntity; import com.cloud.uservm.UserVm; import com.cloud.utils.component.Adapter; -public interface KubernetesClusterHelper extends Adapter { +public interface KubernetesServiceHelper extends Adapter { ControlledEntity findByUuid(String uuid); ControlledEntity findByVmId(long vmId); diff --git a/api/src/main/java/com/cloud/network/NetworkService.java b/api/src/main/java/com/cloud/network/NetworkService.java index 51799e25cda..b8dd464b365 100644 --- a/api/src/main/java/com/cloud/network/NetworkService.java +++ b/api/src/main/java/com/cloud/network/NetworkService.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import com.cloud.dc.DataCenter; +import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.api.command.admin.address.ReleasePodIpCmdByAdmin; import org.apache.cloudstack.api.command.admin.network.DedicateGuestVlanRangeCmd; import org.apache.cloudstack.api.command.admin.network.ListDedicatedGuestVlanRangesCmd; @@ -102,6 +103,10 @@ public interface NetworkService { Network createGuestNetwork(CreateNetworkCmd cmd) throws InsufficientCapacityException, ConcurrentOperationException, ResourceAllocationException; + Network createGuestNetwork(long networkOfferingId, String name, String displayText, Account owner, + PhysicalNetwork physicalNetwork, long zoneId, ControlledEntity.ACLType aclType) throws + InsufficientCapacityException, ConcurrentOperationException, ResourceAllocationException; + Pair, Integer> searchForNetworks(ListNetworksCmd cmd); boolean deleteNetwork(long networkId, boolean forced); diff --git a/api/src/main/java/com/cloud/vm/UserVmService.java b/api/src/main/java/com/cloud/vm/UserVmService.java index 9d8b196a4ff..a30c4491f9d 100644 --- a/api/src/main/java/com/cloud/vm/UserVmService.java +++ b/api/src/main/java/com/cloud/vm/UserVmService.java @@ -45,6 +45,7 @@ import com.cloud.dc.DataCenter; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.ManagementServerException; +import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.StorageUnavailableException; @@ -66,10 +67,7 @@ public interface UserVmService { /** * Destroys one virtual machine * - * @param userId - * the id of the user performing the action - * @param vmId - * the id of the virtual machine. + * @param cmd the API Command Object containg the parameters to use for this service action * @throws ConcurrentOperationException * @throws ResourceUnavailableException */ @@ -112,6 +110,8 @@ public interface UserVmService { UserVm rebootVirtualMachine(RebootVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ResourceAllocationException; + void startVirtualMachine(UserVm vm) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException; + UserVm updateVirtualMachine(UpdateVMCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException; /** @@ -148,14 +148,6 @@ public interface UserVmService { * Creates a Basic Zone User VM in the database and returns the VM to the * caller. * - * - * - * @param sshKeyPair - * - name of the ssh key pair used to login to the virtual - * machine - * @param cpuSpeed - * @param memory - * @param cpuNumber * @param zone * - availability zone for the virtual machine * @param serviceOffering @@ -231,9 +223,6 @@ public interface UserVmService { * Creates a User VM in Advanced Zone (Security Group feature is enabled) in * the database and returns the VM to the caller. * - * - * - * @param type * @param zone * - availability zone for the virtual machine * @param serviceOffering @@ -309,14 +298,6 @@ public interface UserVmService { * Creates a User VM in Advanced Zone (Security Group feature is disabled) * in the database and returns the VM to the caller. * - * - * - * @param sshKeyPair - * - name of the ssh key pair used to login to the virtual - * machine - * @param cpuSpeed - * @param memory - * @param cpuNumber * @param zone * - availability zone for the virtual machine * @param serviceOffering diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java b/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java index aafc039b36b..93893676516 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java @@ -17,7 +17,9 @@ package org.apache.cloudstack.api; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.cloudstack.region.PortableIp; import org.apache.commons.collections.CollectionUtils; @@ -81,15 +83,22 @@ public enum ApiCommandResourceType { ManagementServer(org.apache.cloudstack.management.ManagementServerHost.class), ObjectStore(org.apache.cloudstack.storage.object.ObjectStore.class), Bucket(org.apache.cloudstack.storage.object.Bucket.class), - QuotaTariff(org.apache.cloudstack.quota.QuotaTariff.class); + QuotaTariff(org.apache.cloudstack.quota.QuotaTariff.class), + KubernetesCluster(null), + KubernetesSupportedVersion(null); private final Class clazz; + static final Map> additionalClassMappings = new HashMap<>(); + private ApiCommandResourceType(Class clazz) { this.clazz = clazz; } public Class getAssociatedClass() { + if (this.clazz == null && additionalClassMappings.containsKey(this)) { + return additionalClassMappings.get(this); + } return this.clazz; } @@ -119,4 +128,8 @@ public enum ApiCommandResourceType { } return null; } + + public static void setClassMapping(ApiCommandResourceType type, Class clazz) { + additionalClassMappings.put(type, clazz); + } } 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 4562aa7da19..4dfa76bb9db 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 @@ -54,7 +54,11 @@ public class CreateServiceOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.CPU_NUMBER, type = CommandType.INTEGER, required = false, description = "the CPU number of the service offering") private Integer cpuNumber; - @Parameter(name = ApiConstants.CPU_SPEED, type = CommandType.INTEGER, required = false, description = "the CPU speed of the service offering in MHz.") + @Parameter(name = ApiConstants.CPU_SPEED, type = CommandType.INTEGER, required = false, description = "For VMware and Xen based hypervisors this is the CPU speed of the service offering in MHz.\n" + + "For the KVM hypervisor," + + " the values of the parameters cpuSpeed and cpuNumber will be used to calculate the `shares` value. This value is used by the KVM hypervisor to calculate how much time" + + " the VM will have access to the host's CPU. The `shares` value does not have a unit, and its purpose is being a weight value for the host to compare between its guest" + + " VMs. For more information, see https://libvirt.org/formatdomain.html#cpu-tuning.") private Integer cpuSpeed; @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, description = "The display text of the service offering, defaults to 'name'.") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java index 12795f15a2b..50e1798112d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java @@ -97,8 +97,8 @@ public class ListVMsCmd extends BaseListRetrieveOnlyResourceCountCmd implements collectionType = CommandType.STRING, description = "comma separated list of vm details requested, " + "value can be a list of [all, group, nics, stats, secgrp, tmpl, servoff, diskoff, backoff, iso, volume, min, affgrp]." - + " If no parameter is passed in, the details will be defaulted to all. When return.vm.stats.on.vm.list is true, the default" + - "details change to [group, nics, secgrp, tmpl, servoff, diskoff, backoff, iso, volume, min, affgrp], thus the stats will not be returned. ") + + " When no parameters are passed, all the details are returned if list.vm.default.details.stats is true (default)," + + " otherwise when list.vm.default.details.stats is false the API response will exclude the stats details.") private List viewDetails; @Parameter(name = ApiConstants.TEMPLATE_ID, type = CommandType.UUID, entityType = TemplateResponse.class, description = "list vms by template") diff --git a/api/src/main/java/org/apache/cloudstack/query/QueryService.java b/api/src/main/java/org/apache/cloudstack/query/QueryService.java index dd5aaf4e6d7..4c53314aef5 100644 --- a/api/src/main/java/org/apache/cloudstack/query/QueryService.java +++ b/api/src/main/java/org/apache/cloudstack/query/QueryService.java @@ -125,9 +125,8 @@ public interface QueryService { static final ConfigKey SharePublicTemplatesWithOtherDomains = new ConfigKey<>("Advanced", Boolean.class, "share.public.templates.with.other.domains", "true", "If false, templates of this domain will not show up in the list templates of other domains.", true, ConfigKey.Scope.Domain); - ConfigKey ReturnVmStatsOnVmList = new ConfigKey<>("Advanced", Boolean.class, "return.vm.stats.on.vm.list", "true", - "If false, changes the listVirtualMachines default details to [group, nics, secgrp, tmpl, servoff, diskoff, backoff, iso, volume, min, affgrp], so that the VMs' stats" + - " are not returned by default when listing VMs; only when the 'stats' or 'all' detail is informed.", true, ConfigKey.Scope.Global); + ConfigKey ReturnVmStatsOnVmList = new ConfigKey<>("Advanced", Boolean.class, "list.vm.default.details.stats", "true", + "Determines whether VM stats should be returned when details are not explicitly specified in listVirtualMachines API request. When false, details default to [group, nics, secgrp, tmpl, servoff, diskoff, backoff, iso, volume, min, affgrp]. When true, all details are returned including 'stats'.", true, ConfigKey.Scope.Global); ListResponse searchForUsers(ListUsersCmd cmd) throws PermissionDeniedException; diff --git a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml index 2d6632def40..732f36534d2 100644 --- a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml +++ b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml @@ -339,7 +339,7 @@ class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry"> - diff --git a/core/src/main/resources/META-INF/cloudstack/kubernetes/spring-core-lifecycle-kubernetes-context-inheritable.xml b/core/src/main/resources/META-INF/cloudstack/kubernetes/spring-core-lifecycle-kubernetes-context-inheritable.xml index df1a4b5c229..96a9a634bae 100644 --- a/core/src/main/resources/META-INF/cloudstack/kubernetes/spring-core-lifecycle-kubernetes-context-inheritable.xml +++ b/core/src/main/resources/META-INF/cloudstack/kubernetes/spring-core-lifecycle-kubernetes-context-inheritable.xml @@ -25,8 +25,8 @@ > - - + + diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index a2921452e28..c6d156d470b 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -1185,8 +1185,9 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati logger.error("Unable to destroy existing volume [{}] due to [{}].", volumeToString, e.getMessage()); } // In case of VMware VM will continue to use the old root disk until expunged, so force expunge old root disk - if (vm.getHypervisorType() == HypervisorType.VMware) { - logger.info("Trying to expunge volume [{}] from primary data storage.", volumeToString); + // For system VM we do not need volume entry in Destroy state + if (vm.getHypervisorType() == HypervisorType.VMware || vm.getType().isUsedBySystem()) { + logger.info(String.format("Trying to expunge volume [%s] from primary data storage.", volumeToString)); AsyncCallFuture future = volService.expungeVolumeAsync(volFactory.getVolume(existingVolume.getId())); try { future.get(); diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41900to41910-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-41900to41910-cleanup.sql index b580d42686f..2d57db2b778 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41900to41910-cleanup.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41900to41910-cleanup.sql @@ -18,3 +18,7 @@ --; -- Schema upgrade cleanup from 4.19.0.0 to 4.19.1.0 --; + +-- List VMs response optimisation, don't sum during API handling +UPDATE cloud.configuration set value='false' where name='vm.stats.increment.metrics'; +DELETE from cloud.configuration where name='vm.stats.increment.metrics.in.memory'; diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql index 25f95709721..62294ed5d89 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql @@ -196,7 +196,7 @@ FROM LEFT JOIN `networks` ON ((`nics`.`network_id` = `networks`.`id`))) LEFT JOIN `vpc` ON (((`networks`.`vpc_id` = `vpc`.`id`) AND ISNULL(`vpc`.`removed`)))) - LEFT JOIN `user_ip_address` ON ((`user_ip_address`.`vm_id` = `vm_instance`.`id`))) + LEFT JOIN `user_ip_address` FORCE INDEX(`fk_user_ip_address__vm_id`) ON ((`user_ip_address`.`vm_id` = `vm_instance`.`id`))) LEFT JOIN `user_vm_details` `ssh_details` ON (((`ssh_details`.`vm_id` = `vm_instance`.`id`) AND (`ssh_details`.`name` = 'SSH.KeyPairNames')))) LEFT JOIN `resource_tags` ON (((`resource_tags`.`resource_id` = `vm_instance`.`id`) diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapperTest.java index d70f5f08884..b9316c6c042 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapperTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtConvertInstanceCommandWrapperTest.java @@ -214,6 +214,7 @@ public class LibvirtConvertInstanceCommandWrapperTest { LibvirtDomainXMLParser parser = Mockito.mock(LibvirtDomainXMLParser.class); Mockito.when(parser.getDisks()).thenReturn(List.of(diskDef)); + Mockito.doReturn(new Pair(null, null)).when(convertInstanceCommandWrapper).getNfsStoragePoolHostAndPath(destinationPool); List unmanagedInstanceDisks = convertInstanceCommandWrapper.getUnmanagedInstanceDisks(disks, parser); Assert.assertEquals(1, unmanagedInstanceDisks.size()); diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java index d9350a6b1be..151344575dc 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java @@ -40,12 +40,11 @@ import java.util.concurrent.TimeUnit; import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.uservm.UserVm; -import com.cloud.vm.UserVmService; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants.VMDetails; import org.apache.cloudstack.api.BaseCmd; @@ -90,6 +89,7 @@ import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; import com.cloud.deploy.DeployDestination; import com.cloud.domain.Domain; +import com.cloud.event.ActionEvent; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InsufficientServerCapacityException; @@ -157,6 +157,7 @@ import com.cloud.user.UserAccount; import com.cloud.user.UserVO; import com.cloud.user.dao.SSHKeyPairDao; import com.cloud.user.dao.UserDao; +import com.cloud.uservm.UserVm; import com.cloud.utils.Pair; import com.cloud.utils.Ternary; import com.cloud.utils.component.ComponentContext; @@ -174,6 +175,7 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.fsm.NoTransitionException; import com.cloud.utils.fsm.StateMachine2; import com.cloud.utils.net.NetUtils; +import com.cloud.vm.UserVmService; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.dao.VMInstanceDao; @@ -864,13 +866,15 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne logger.info(String.format("Creating network for account ID: %s from the network offering ID: %s as part of Kubernetes cluster: %s deployment process", owner.getUuid(), networkOffering.getUuid(), clusterName)); } + CallContext networkContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Network); try { - network = networkMgr.createGuestNetwork(networkOffering.getId(), clusterName + "-network", owner.getAccountName() + "-network", - null, null, null, false, null, owner, null, physicalNetwork, zone.getId(), - ControlledEntity.ACLType.Account, null, null, null, null, true, null, - null, null, null, null, null, null, null, null, null); + network = networkService.createGuestNetwork(networkOffering.getId(), clusterName + "-network", + owner.getAccountName() + "-network", owner, physicalNetwork, zone.getId(), + ControlledEntity.ACLType.Account); } catch (ConcurrentOperationException | InsufficientCapacityException | ResourceAllocationException e) { logAndThrow(Level.ERROR, String.format("Unable to create network for the Kubernetes cluster: %s", clusterName)); + } finally { + CallContext.unregister(); } } return network; @@ -1139,6 +1143,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne } @Override + @ActionEvent(eventType = KubernetesClusterEventTypes.EVENT_KUBERNETES_CLUSTER_CREATE, + eventDescription = "creating Kubernetes cluster", create = true) public KubernetesCluster createUnmanagedKubernetesCluster(CreateKubernetesClusterCmd cmd) throws CloudRuntimeException { if (!KubernetesServiceEnabled.value()) { logAndThrow(Level.ERROR, "Kubernetes Service plugin is disabled"); @@ -1185,10 +1191,13 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne if (logger.isInfoEnabled()) { logger.info(String.format("Kubernetes cluster with name: %s and ID: %s has been created", cluster.getName(), cluster.getUuid())); } + CallContext.current().putContextParameter(KubernetesCluster.class, cluster.getUuid()); return cluster; } @Override + @ActionEvent(eventType = KubernetesClusterEventTypes.EVENT_KUBERNETES_CLUSTER_CREATE, + eventDescription = "creating Kubernetes cluster", create = true) public KubernetesCluster createManagedKubernetesCluster(CreateKubernetesClusterCmd cmd) throws CloudRuntimeException { if (!KubernetesServiceEnabled.value()) { logAndThrow(Level.ERROR, "Kubernetes Service plugin is disabled"); @@ -1245,6 +1254,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne if (logger.isInfoEnabled()) { logger.info(String.format("Kubernetes cluster name: %s and ID: %s has been created", cluster.getName(), cluster.getUuid())); } + CallContext.current().putContextParameter(KubernetesCluster.class, cluster.getUuid()); return cluster; } @@ -1271,29 +1281,64 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne return securityGroup; } + @Override + @ActionEvent(eventType = KubernetesClusterEventTypes.EVENT_KUBERNETES_CLUSTER_CREATE, + eventDescription = "creating Kubernetes cluster", async = true) + public void startKubernetesCluster(CreateKubernetesClusterCmd cmd) throws CloudRuntimeException { + final Long id = cmd.getEntityId(); + if (KubernetesCluster.ClusterType.valueOf(cmd.getClusterType()) != KubernetesCluster.ClusterType.CloudManaged) { + return; + } + final KubernetesClusterVO kubernetesCluster = kubernetesClusterDao.findById(id); + if (kubernetesCluster == null) { + throw new InvalidParameterValueException("Failed to find Kubernetes cluster with given ID"); + } + if (!startKubernetesCluster(kubernetesCluster, true)) { + throw new CloudRuntimeException(String.format("Failed to start created Kubernetes cluster: %s", + kubernetesCluster.getName())); + } + } + + @Override + @ActionEvent(eventType = KubernetesClusterEventTypes.EVENT_KUBERNETES_CLUSTER_START, + eventDescription = "starting Kubernetes cluster", async = true) + public void startKubernetesCluster(StartKubernetesClusterCmd cmd) throws CloudRuntimeException { + final Long id = cmd.getId(); + if (id == null || id < 1L) { + throw new InvalidParameterValueException("Invalid Kubernetes cluster ID provided"); + } + final KubernetesClusterVO kubernetesCluster = kubernetesClusterDao.findById(id); + if (kubernetesCluster == null) { + throw new InvalidParameterValueException("Given Kubernetes cluster was not found"); + } + if (!isCommandSupported(kubernetesCluster, cmd.getActualCommandName())) { + throw new InvalidParameterValueException(String.format("Start kubernetes cluster is not supported for " + + "an externally managed cluster (%s)", kubernetesCluster.getName())); + } + if (!startKubernetesCluster(kubernetesCluster, false)) { + throw new CloudRuntimeException(String.format("Failed to start Kubernetes cluster: %s", + kubernetesCluster.getName())); + } + } + /** * Start operation can be performed at two different life stages of Kubernetes cluster. First when a freshly created cluster * in which case there are no resources provisioned for the Kubernetes cluster. So during start all the resources * are provisioned from scratch. Second kind of start, happens on Stopped Kubernetes cluster, in which all resources * are provisioned (like volumes, nics, networks etc). It just that VM's are not in running state. So just * start the VM's (which can possibly implicitly start the network also). - * @param kubernetesClusterId + * @param kubernetesCluster * @param onCreate * @return * @throws CloudRuntimeException */ - - @Override - public boolean startKubernetesCluster(long kubernetesClusterId, boolean onCreate) throws CloudRuntimeException { + public boolean startKubernetesCluster(KubernetesClusterVO kubernetesCluster, boolean onCreate) throws CloudRuntimeException { if (!KubernetesServiceEnabled.value()) { logAndThrow(Level.ERROR, "Kubernetes Service plugin is disabled"); } - final KubernetesClusterVO kubernetesCluster = kubernetesClusterDao.findById(kubernetesClusterId); - if (kubernetesCluster == null) { - throw new InvalidParameterValueException("Failed to find Kubernetes cluster with given ID"); - } if (kubernetesCluster.getRemoved() != null) { - throw new InvalidParameterValueException(String.format("Kubernetes cluster : %s is already deleted", kubernetesCluster.getName())); + throw new InvalidParameterValueException(String.format("Kubernetes cluster : %s is already deleted", + kubernetesCluster.getName())); } accountManager.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.OperateEntry, false, kubernetesCluster); if (kubernetesCluster.getState().equals(KubernetesCluster.State.Running)) { @@ -1351,6 +1396,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne } @Override + @ActionEvent(eventType = KubernetesClusterEventTypes.EVENT_KUBERNETES_CLUSTER_STOP, + eventDescription = "stopping Kubernetes cluster", async = true) public boolean stopKubernetesCluster(StopKubernetesClusterCmd cmd) throws CloudRuntimeException { long kubernetesClusterId = cmd.getId(); if (!KubernetesServiceEnabled.value()) { @@ -1385,6 +1432,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne } @Override + @ActionEvent(eventType = KubernetesClusterEventTypes.EVENT_KUBERNETES_CLUSTER_DELETE, + eventDescription = "deleting Kubernetes cluster", async = true) public boolean deleteKubernetesCluster(DeleteKubernetesClusterCmd cmd) throws CloudRuntimeException { if (!KubernetesServiceEnabled.value()) { logAndThrow(Level.ERROR, "Kubernetes Service plugin is disabled"); @@ -1487,13 +1536,13 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne if (clusterType != null) { sc.setParameters("cluster_type", clusterType); } - List kubernetesClusters = kubernetesClusterDao.search(sc, searchFilter); - for (KubernetesClusterVO cluster : kubernetesClusters) { + Pair, Integer> kubernetesClustersAndCount = kubernetesClusterDao.searchAndCount(sc, searchFilter); + for (KubernetesClusterVO cluster : kubernetesClustersAndCount.first()) { KubernetesClusterResponse clusterResponse = createKubernetesClusterResponse(cluster.getId()); responsesList.add(clusterResponse); } - ListResponse response = new ListResponse(); - response.setResponses(responsesList); + ListResponse response = new ListResponse<>(); + response.setResponses(responsesList, kubernetesClustersAndCount.second()); return response; } @@ -1527,6 +1576,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne } @Override + @ActionEvent(eventType = KubernetesClusterEventTypes.EVENT_KUBERNETES_CLUSTER_SCALE, + eventDescription = "scaling Kubernetes cluster", async = true) public boolean scaleKubernetesCluster(ScaleKubernetesClusterCmd cmd) throws CloudRuntimeException { if (!KubernetesServiceEnabled.value()) { logAndThrow(Level.ERROR, "Kubernetes Service plugin is disabled"); @@ -1534,22 +1585,29 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne validateKubernetesClusterScaleParameters(cmd); KubernetesClusterVO kubernetesCluster = kubernetesClusterDao.findById(cmd.getId()); + final Long clusterSize = cmd.getClusterSize(); + if (clusterSize != null) { + CallContext.current().setEventDetails(String.format("Kubernetes cluster ID: %s scaling from size: %d to %d", + kubernetesCluster.getUuid(), kubernetesCluster.getNodeCount(), clusterSize)); + } String[] keys = getServiceUserKeys(kubernetesCluster); KubernetesClusterScaleWorker scaleWorker = new KubernetesClusterScaleWorker(kubernetesClusterDao.findById(cmd.getId()), - serviceOfferingDao.findById(cmd.getServiceOfferingId()), - cmd.getClusterSize(), - cmd.getNodeIds(), - cmd.isAutoscalingEnabled(), - cmd.getMinSize(), - cmd.getMaxSize(), - this); + serviceOfferingDao.findById(cmd.getServiceOfferingId()), + clusterSize, + cmd.getNodeIds(), + cmd.isAutoscalingEnabled(), + cmd.getMinSize(), + cmd.getMaxSize(), + this); scaleWorker.setKeys(keys); scaleWorker = ComponentContext.inject(scaleWorker); return scaleWorker.scaleCluster(); } @Override + @ActionEvent(eventType = KubernetesClusterEventTypes.EVENT_KUBERNETES_CLUSTER_UPGRADE, + eventDescription = "upgrading Kubernetes cluster", async = true) public boolean upgradeKubernetesCluster(UpgradeKubernetesClusterCmd cmd) throws CloudRuntimeException { if (!KubernetesServiceEnabled.value()) { logAndThrow(Level.ERROR, "Kubernetes Service plugin is disabled"); diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterService.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterService.java index 39b926537f5..6acc876493e 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterService.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterService.java @@ -23,6 +23,7 @@ import org.apache.cloudstack.api.command.user.kubernetes.cluster.GetKubernetesCl import org.apache.cloudstack.api.command.user.kubernetes.cluster.ListKubernetesClustersCmd; import org.apache.cloudstack.api.command.user.kubernetes.cluster.RemoveVirtualMachinesFromKubernetesClusterCmd; import org.apache.cloudstack.api.command.user.kubernetes.cluster.ScaleKubernetesClusterCmd; +import org.apache.cloudstack.api.command.user.kubernetes.cluster.StartKubernetesClusterCmd; import org.apache.cloudstack.api.command.user.kubernetes.cluster.StopKubernetesClusterCmd; import org.apache.cloudstack.api.command.user.kubernetes.cluster.UpgradeKubernetesClusterCmd; import org.apache.cloudstack.api.response.KubernetesClusterConfigResponse; @@ -98,7 +99,9 @@ public interface KubernetesClusterService extends PluggableService, Configurable KubernetesCluster createManagedKubernetesCluster(CreateKubernetesClusterCmd cmd) throws CloudRuntimeException; - boolean startKubernetesCluster(long kubernetesClusterId, boolean onCreate) throws CloudRuntimeException; + void startKubernetesCluster(CreateKubernetesClusterCmd cmd) throws CloudRuntimeException; + + void startKubernetesCluster(StartKubernetesClusterCmd cmd) throws CloudRuntimeException; boolean stopKubernetesCluster(StopKubernetesClusterCmd cmd) throws CloudRuntimeException; diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesServiceHelperImpl.java similarity index 64% rename from plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java rename to plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesServiceHelperImpl.java index 9d8bb48dc94..c7a4f94d6c6 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesServiceHelperImpl.java @@ -16,33 +16,59 @@ // under the License. package com.cloud.kubernetes.cluster; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Objects; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; + +import com.cloud.event.EventTypes; import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao; import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao; +import com.cloud.kubernetes.version.KubernetesSupportedVersion; +import com.cloud.kubernetes.version.KubernetesVersionEventTypes; import com.cloud.uservm.UserVm; import com.cloud.utils.component.AdapterBase; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.UserVmManager; -import javax.inject.Inject; - -import org.apache.cloudstack.acl.ControlledEntity; -import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.framework.config.Configurable; -import org.springframework.stereotype.Component; - -import java.util.Objects; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.stereotype.Component; @Component -public class KubernetesClusterHelperImpl extends AdapterBase implements KubernetesClusterHelper, Configurable { - private static final Logger logger = LogManager.getLogger(KubernetesClusterHelperImpl.class); +public class KubernetesServiceHelperImpl extends AdapterBase implements KubernetesServiceHelper, Configurable { + private static final Logger logger = LogManager.getLogger(KubernetesServiceHelperImpl.class); @Inject private KubernetesClusterDao kubernetesClusterDao; @Inject private KubernetesClusterVmMapDao kubernetesClusterVmMapDao; + protected void setEventTypeEntityDetails(Class eventTypeDefinedClass, Class entityClass) { + Field[] declaredFields = eventTypeDefinedClass.getDeclaredFields(); + for (Field field : declaredFields) { + int modifiers = field.getModifiers(); + if (!Modifier.isPublic(modifiers) || !Modifier.isStatic(modifiers)) { + continue; + } + try { + Object value = field.get(null); + if (ObjectUtils.allNotNull(value, value.toString())) { + EventTypes.addEntityEventDetail(value.toString(), entityClass); + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + @Override public ControlledEntity findByUuid(String uuid) { return kubernetesClusterDao.findByUuid(uuid); @@ -79,11 +105,21 @@ public class KubernetesClusterHelperImpl extends AdapterBase implements Kubernet @Override public String getConfigComponentName() { - return KubernetesClusterHelper.class.getSimpleName(); + return KubernetesServiceHelper.class.getSimpleName(); } @Override public ConfigKey[] getConfigKeys() { return new ConfigKey[]{}; } + + @Override + public boolean start() { + setEventTypeEntityDetails(KubernetesClusterEventTypes.class, KubernetesCluster.class); + setEventTypeEntityDetails(KubernetesVersionEventTypes.class, KubernetesSupportedVersion.class); + ApiCommandResourceType.setClassMapping(ApiCommandResourceType.KubernetesCluster, KubernetesCluster.class); + ApiCommandResourceType.setClassMapping(ApiCommandResourceType.KubernetesSupportedVersion, + KubernetesSupportedVersion.class); + return super.start(); + } } diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java index f9a1d5dd48f..0ef3ee96d32 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java @@ -34,9 +34,11 @@ import javax.inject.Inject; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.ca.CAManager; import org.apache.cloudstack.config.ApiServiceConfiguration; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.commons.collections.CollectionUtils; @@ -92,7 +94,7 @@ import com.cloud.utils.ssh.SshHelper; import com.cloud.vm.UserVmDetailVO; import com.cloud.vm.UserVmService; import com.cloud.vm.UserVmVO; -import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.VirtualMachine; import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.UserVmDetailsDao; @@ -150,8 +152,6 @@ public class KubernetesClusterActionWorker { @Inject protected VlanDao vlanDao; @Inject - protected VirtualMachineManager itMgr; - @Inject protected LaunchPermissionDao launchPermissionDao; @Inject public ProjectService projectService; @@ -475,6 +475,8 @@ public class KubernetesClusterActionWorker { } for (UserVm vm : clusterVMs) { + CallContext vmContext = CallContext.register(CallContext.current(), ApiCommandResourceType.VirtualMachine); + vmContext.putContextParameter(VirtualMachine.class, vm.getUuid()); try { templateService.attachIso(iso.getId(), vm.getId(), true); if (logger.isInfoEnabled()) { @@ -482,6 +484,8 @@ public class KubernetesClusterActionWorker { } } catch (CloudRuntimeException ex) { logTransitStateAndThrow(Level.ERROR, String.format("Failed to attach binaries ISO for VM : %s in the Kubernetes cluster name: %s", vm.getDisplayName(), kubernetesCluster.getName()), kubernetesCluster.getId(), failedEvent, ex); + } finally { + CallContext.unregister(); } } } @@ -493,10 +497,14 @@ public class KubernetesClusterActionWorker { protected void detachIsoKubernetesVMs(List clusterVMs) { for (UserVm vm : clusterVMs) { boolean result = false; + CallContext vmContext = CallContext.register(CallContext.current(), ApiCommandResourceType.VirtualMachine); + vmContext.putContextParameter(VirtualMachine.class, vm.getUuid()); try { result = templateService.detachIso(vm.getId(), true); } catch (CloudRuntimeException ex) { logger.warn(String.format("Failed to detach binaries ISO from VM : %s in the Kubernetes cluster : %s ", vm.getDisplayName(), kubernetesCluster.getName()), ex); + } finally { + CallContext.unregister(); } if (result) { if (logger.isInfoEnabled()) { diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterDestroyWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterDestroyWorker.java index 6e87d2071cd..047d32f1f9f 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterDestroyWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterDestroyWorker.java @@ -25,6 +25,7 @@ import javax.inject.Inject; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.context.CallContext; import org.apache.commons.collections.CollectionUtils; @@ -93,6 +94,9 @@ public class KubernetesClusterDestroyWorker extends KubernetesClusterResourceMod if (userVM == null || userVM.isRemoved()) { continue; } + CallContext vmContext = CallContext.register(CallContext.current(), + ApiCommandResourceType.VirtualMachine); + vmContext.setEventResourceId(vmID); try { UserVm vm = userVmService.destroyVm(vmID, true); if (!userVmManager.expunge(userVM)) { @@ -106,6 +110,8 @@ public class KubernetesClusterDestroyWorker extends KubernetesClusterResourceMod } catch (ResourceUnavailableException | ConcurrentOperationException e) { logger.warn(String.format("Failed to destroy VM : %s part of the Kubernetes cluster : %s cleanup. Moving on with destroying remaining resources provisioned for the Kubernetes cluster", userVM.getDisplayName(), kubernetesCluster.getName()), e); return false; + } finally { + CallContext.unregister(); } } } diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java index 6604cda0adf..d75076eb3d3 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java @@ -31,14 +31,13 @@ import java.util.stream.Collectors; import javax.inject.Inject; -import com.cloud.offering.NetworkOffering; -import com.cloud.offerings.dao.NetworkOfferingDao; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.command.user.firewall.CreateFirewallRuleCmd; import org.apache.cloudstack.api.command.user.network.CreateNetworkACLCmd; -import org.apache.cloudstack.api.command.user.vm.StartVMCmd; import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd; +import org.apache.cloudstack.context.CallContext; import org.apache.commons.codec.binary.Base64; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -86,7 +85,9 @@ import com.cloud.network.vpc.NetworkACLItem; import com.cloud.network.vpc.NetworkACLItemDao; import com.cloud.network.vpc.NetworkACLItemVO; import com.cloud.network.vpc.NetworkACLService; +import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; +import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.resource.ResourceManager; import com.cloud.storage.Volume; import com.cloud.storage.VolumeApiService; @@ -323,18 +324,19 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu } protected void startKubernetesVM(final UserVm vm) throws ManagementServerException { + CallContext vmContext = null; + if (!ApiCommandResourceType.VirtualMachine.equals(CallContext.current().getEventResourceType())); { + vmContext = CallContext.register(CallContext.current(), ApiCommandResourceType.VirtualMachine); + vmContext.setEventResourceId(vm.getId()); + } try { - StartVMCmd startVm = new StartVMCmd(); - startVm = ComponentContext.inject(startVm); - Field f = startVm.getClass().getDeclaredField("id"); - f.setAccessible(true); - f.set(startVm, vm.getId()); - itMgr.advanceStart(vm.getUuid(), null, null); - if (logger.isInfoEnabled()) { - logger.info(String.format("Started VM : %s in the Kubernetes cluster : %s", vm.getDisplayName(), kubernetesCluster.getName())); - } - } catch (IllegalAccessException | NoSuchFieldException | OperationTimedoutException | ResourceUnavailableException | InsufficientCapacityException ex) { + userVmManager.startVirtualMachine(vm); + } catch (OperationTimedoutException | ResourceUnavailableException | InsufficientCapacityException ex) { throw new ManagementServerException(String.format("Failed to start VM in the Kubernetes cluster : %s", kubernetesCluster.getName()), ex); + } finally { + if (vmContext != null) { + CallContext.unregister(); + } } UserVm startVm = userVmDao.findById(vm.getId()); @@ -347,19 +349,23 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu ResourceUnavailableException, InsufficientCapacityException { List nodes = new ArrayList<>(); for (int i = offset + 1; i <= nodeCount; i++) { - UserVm vm = createKubernetesNode(publicIpAddress); - addKubernetesClusterVm(kubernetesCluster.getId(), vm.getId(), false); - if (kubernetesCluster.getNodeRootDiskSize() > 0) { - resizeNodeVolume(vm); - } - startKubernetesVM(vm); - vm = userVmDao.findById(vm.getId()); - if (vm == null) { - throw new ManagementServerException(String.format("Failed to provision worker VM for Kubernetes cluster : %s" , kubernetesCluster.getName())); - } - nodes.add(vm); - if (logger.isInfoEnabled()) { - logger.info(String.format("Provisioned node VM : %s in to the Kubernetes cluster : %s", vm.getDisplayName(), kubernetesCluster.getName())); + CallContext vmContext = CallContext.register(CallContext.current(), ApiCommandResourceType.VirtualMachine); + try { + UserVm vm = createKubernetesNode(publicIpAddress); + vmContext.setEventResourceId(vm.getId()); + addKubernetesClusterVm(kubernetesCluster.getId(), vm.getId(), false); + if (kubernetesCluster.getNodeRootDiskSize() > 0) { + resizeNodeVolume(vm); + } + startKubernetesVM(vm); + vm = userVmDao.findById(vm.getId()); + if (vm == null) { + throw new ManagementServerException(String.format("Failed to provision worker VM for Kubernetes cluster : %s", kubernetesCluster.getName())); + } + nodes.add(vm); + logger.info("Provisioned node VM : {} in to the Kubernetes cluster : {}", vm.getDisplayName(), kubernetesCluster.getName()); + } finally { + CallContext.unregister(); } } return nodes; @@ -630,6 +636,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu protected void createFirewallRules(IpAddress publicIp, List clusterVMIds, boolean apiRule) throws ManagementServerException { // Firewall rule for SSH access on each node VM + CallContext.register(CallContext.current(), null); try { int endPort = CLUSTER_NODES_DEFAULT_START_SSH_PORT + clusterVMIds.size() - 1; provisionFirewallRules(publicIp, owner, CLUSTER_NODES_DEFAULT_START_SSH_PORT, endPort); @@ -638,11 +645,14 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu } } catch (NoSuchFieldException | IllegalAccessException | ResourceUnavailableException | NetworkRuleConflictException e) { throw new ManagementServerException(String.format("Failed to provision firewall rules for SSH access for the Kubernetes cluster : %s", kubernetesCluster.getName()), e); + } finally { + CallContext.unregister(); } if (!apiRule) { return; } // Firewall rule for API access for control node VMs + CallContext.register(CallContext.current(), null); try { provisionFirewallRules(publicIp, owner, CLUSTER_API_PORT, CLUSTER_API_PORT); if (logger.isInfoEnabled()) { @@ -651,6 +661,8 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu } } catch (NoSuchFieldException | IllegalAccessException | ResourceUnavailableException | NetworkRuleConflictException e) { throw new ManagementServerException(String.format("Failed to provision firewall rules for API access for the Kubernetes cluster : %s", kubernetesCluster.getName()), e); + } finally { + CallContext.unregister(); } } @@ -691,6 +703,7 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu return; } // ACL rule for API access for control node VMs + CallContext.register(CallContext.current(), null); try { provisionVpcTierAllowPortACLRule(network, CLUSTER_API_PORT, CLUSTER_API_PORT); if (logger.isInfoEnabled()) { @@ -699,7 +712,10 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu } } catch (NoSuchFieldException | IllegalAccessException | ResourceUnavailableException | InvalidParameterValueException | PermissionDeniedException e) { throw new ManagementServerException(String.format("Failed to provision firewall rules for API access for the Kubernetes cluster : %s", kubernetesCluster.getName()), e); + } finally { + CallContext.unregister(); } + CallContext.register(CallContext.current(), null); try { provisionVpcTierAllowPortACLRule(network, DEFAULT_SSH_PORT, DEFAULT_SSH_PORT); if (logger.isInfoEnabled()) { @@ -708,6 +724,8 @@ public class KubernetesClusterResourceModifierActionWorker extends KubernetesClu } } catch (NoSuchFieldException | IllegalAccessException | ResourceUnavailableException | InvalidParameterValueException | PermissionDeniedException e) { throw new ManagementServerException(String.format("Failed to provision firewall rules for API access for the Kubernetes cluster : %s", kubernetesCluster.getName()), e); + } finally { + CallContext.unregister(); } } diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterScaleWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterScaleWorker.java index ec04907cf9d..f5ac553733d 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterScaleWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterScaleWorker.java @@ -26,7 +26,9 @@ import java.util.stream.Collectors; import javax.inject.Inject; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.context.CallContext; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -318,6 +320,9 @@ public class KubernetesClusterScaleWorker extends KubernetesClusterResourceModif if (!removeKubernetesClusterNode(publicIpAddress, sshPort, userVM, 3, 30000)) { logTransitStateAndThrow(Level.ERROR, String.format("Scaling failed for Kubernetes cluster : %s, failed to remove Kubernetes node: %s running on VM : %s", kubernetesCluster.getName(), userVM.getHostName(), userVM.getDisplayName()), kubernetesCluster.getId(), KubernetesCluster.Event.OperationFailed); } + CallContext vmContext = CallContext.register(CallContext.current(), + ApiCommandResourceType.VirtualMachine); + vmContext.setEventResourceId(userVM.getId()); try { UserVm vm = userVmService.destroyVm(userVM.getId(), true); if (!userVmManager.expunge(userVM)) { @@ -327,6 +332,8 @@ public class KubernetesClusterScaleWorker extends KubernetesClusterResourceModif } catch (ResourceUnavailableException e) { logTransitStateAndThrow(Level.ERROR, String.format("Scaling Kubernetes cluster %s failed, unable to remove VM ID: %s", kubernetesCluster.getName() , userVM.getDisplayName()), kubernetesCluster.getId(), KubernetesCluster.Event.OperationFailed, e); + } finally { + CallContext.unregister(); } kubernetesClusterVmMapDao.expunge(vmMapVO.getId()); if (System.currentTimeMillis() > scaleTimeoutTime) { @@ -438,10 +445,10 @@ public class KubernetesClusterScaleWorker extends KubernetesClusterResourceModif if (existingServiceOffering == null) { logAndThrow(Level.ERROR, String.format("Scaling Kubernetes cluster : %s failed, service offering for the Kubernetes cluster not found!", kubernetesCluster.getName())); } - final boolean autscalingChanged = isAutoscalingChanged(); + final boolean autoscalingChanged = isAutoscalingChanged(); final boolean serviceOfferingScalingNeeded = serviceOffering != null && serviceOffering.getId() != existingServiceOffering.getId(); - if (autscalingChanged) { + if (autoscalingChanged) { boolean autoScaled = autoscaleCluster(this.isAutoscalingEnabled, minSize, maxSize); if (autoScaled && serviceOfferingScalingNeeded) { scaleKubernetesClusterOffering(); diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStopWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStopWorker.java index e77268b0654..60802d12e72 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStopWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStopWorker.java @@ -20,6 +20,8 @@ package com.cloud.kubernetes.cluster.actionworkers; import java.util.List; import org.apache.logging.log4j.Level; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.context.CallContext; import com.cloud.exception.ConcurrentOperationException; import com.cloud.kubernetes.cluster.KubernetesCluster; @@ -44,11 +46,15 @@ public class KubernetesClusterStopWorker extends KubernetesClusterActionWorker { if (vm == null) { logTransitStateAndThrow(Level.ERROR, String.format("Failed to find all VMs in Kubernetes cluster : %s", kubernetesCluster.getName()), kubernetesCluster.getId(), KubernetesCluster.Event.OperationFailed); } + CallContext vmContext = CallContext.register(CallContext.current(), ApiCommandResourceType.VirtualMachine); + vmContext.setEventResourceId(vm.getId()); try { userVmService.stopVirtualMachine(vm.getId(), false); } catch (ConcurrentOperationException ex) { logger.warn(String.format("Failed to stop VM : %s in Kubernetes cluster : %s", vm.getDisplayName(), kubernetesCluster.getName()), ex); + } finally { + CallContext.unregister(); } } for (final UserVm userVm : clusterVMs) { diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/version/KubernetesVersionManagerImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/version/KubernetesVersionManagerImpl.java index 8f4e36550dc..ba2b4288d48 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/version/KubernetesVersionManagerImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/version/KubernetesVersionManagerImpl.java @@ -22,6 +22,7 @@ import java.util.List; import javax.inject.Inject; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.kubernetes.version.AddKubernetesSupportedVersionCmd; import org.apache.cloudstack.api.command.admin.kubernetes.version.DeleteKubernetesSupportedVersionCmd; @@ -31,6 +32,7 @@ import org.apache.cloudstack.api.command.user.iso.RegisterIsoCmd; import org.apache.cloudstack.api.command.user.kubernetes.version.ListKubernetesSupportedVersionsCmd; import org.apache.cloudstack.api.response.KubernetesSupportedVersionResponse; import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.commons.lang3.StringUtils; import com.cloud.api.query.dao.TemplateJoinDao; @@ -51,6 +53,7 @@ import com.cloud.storage.dao.VMTemplateZoneDao; import com.cloud.template.TemplateApiService; import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.AccountManager; +import com.cloud.utils.Pair; import com.cloud.utils.component.ComponentContext; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.db.Filter; @@ -117,13 +120,14 @@ public class KubernetesVersionManagerImpl extends ManagerBase implements Kuberne return response; } - private ListResponse createKubernetesSupportedVersionListResponse(List versions) { + private ListResponse createKubernetesSupportedVersionListResponse( + List versions, Integer count) { List responseList = new ArrayList<>(); for (KubernetesSupportedVersionVO version : versions) { responseList.add(createKubernetesSupportedVersionResponse(version)); } ListResponse response = new ListResponse<>(); - response.setResponses(responseList); + response.setResponses(responseList, count); return response; } @@ -157,6 +161,7 @@ public class KubernetesVersionManagerImpl extends ManagerBase implements Kuberne private VirtualMachineTemplate registerKubernetesVersionIso(final Long zoneId, final String versionName, final String isoUrl, final String isoChecksum, final boolean directDownload) throws IllegalAccessException, NoSuchFieldException, IllegalArgumentException, ResourceAllocationException { + CallContext.register(CallContext.current(), ApiCommandResourceType.Iso); String isoName = String.format("%s-Kubernetes-Binaries-ISO", versionName); RegisterIsoCmd registerIsoCmd = new RegisterIsoCmd(); registerIsoCmd = ComponentContext.inject(registerIsoCmd); @@ -174,15 +179,25 @@ public class KubernetesVersionManagerImpl extends ManagerBase implements Kuberne registerIsoCmd.setDirectDownload(directDownload); registerIsoCmd.setAccountName(accountManager.getSystemAccount().getAccountName()); registerIsoCmd.setDomainId(accountManager.getSystemAccount().getDomainId()); - return templateService.registerIso(registerIsoCmd); + try { + return templateService.registerIso(registerIsoCmd); + } finally { + CallContext.unregister(); + } } private void deleteKubernetesVersionIso(long templateId) throws IllegalAccessException, NoSuchFieldException, IllegalArgumentException { + CallContext isoContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Iso); + isoContext.setEventResourceId(templateId); DeleteIsoCmd deleteIsoCmd = new DeleteIsoCmd(); deleteIsoCmd = ComponentContext.inject(deleteIsoCmd); deleteIsoCmd.setId(templateId); - templateService.deleteIso(deleteIsoCmd); + try { + templateService.deleteIso(deleteIsoCmd); + } finally { + CallContext.unregister(); + } } public static int compareSemanticVersions(String v1, String v2) throws IllegalArgumentException { @@ -278,14 +293,17 @@ public class KubernetesVersionManagerImpl extends ManagerBase implements Kuberne if(keyword != null){ sc.setParameters("keyword", "%" + keyword + "%"); } - List versions = kubernetesSupportedVersionDao.search(sc, searchFilter); - versions = filterKubernetesSupportedVersions(versions, minimumSemanticVersion); + Pair, Integer> versionsAndCount = + kubernetesSupportedVersionDao.searchAndCount(sc, searchFilter); + List versions = + filterKubernetesSupportedVersions(versionsAndCount.first(), minimumSemanticVersion); - return createKubernetesSupportedVersionListResponse(versions); + return createKubernetesSupportedVersionListResponse(versions, versionsAndCount.second()); } @Override - @ActionEvent(eventType = KubernetesVersionEventTypes.EVENT_KUBERNETES_VERSION_ADD, eventDescription = "Adding Kubernetes supported version") + @ActionEvent(eventType = KubernetesVersionEventTypes.EVENT_KUBERNETES_VERSION_ADD, + eventDescription = "Adding Kubernetes supported version") public KubernetesSupportedVersionResponse addKubernetesSupportedVersion(final AddKubernetesSupportedVersionCmd cmd) { if (!KubernetesClusterService.KubernetesServiceEnabled.value()) { throw new CloudRuntimeException("Kubernetes Service plugin is disabled"); @@ -337,12 +355,14 @@ public class KubernetesVersionManagerImpl extends ManagerBase implements Kuberne KubernetesSupportedVersionVO supportedVersionVO = new KubernetesSupportedVersionVO(name, semanticVersion, template.getId(), zoneId, minimumCpu, minimumRamSize); supportedVersionVO = kubernetesSupportedVersionDao.persist(supportedVersionVO); + CallContext.current().putContextParameter(KubernetesSupportedVersion.class, supportedVersionVO.getUuid()); return createKubernetesSupportedVersionResponse(supportedVersionVO); } @Override - @ActionEvent(eventType = KubernetesVersionEventTypes.EVENT_KUBERNETES_VERSION_DELETE, eventDescription = "Deleting Kubernetes supported version", async = true) + @ActionEvent(eventType = KubernetesVersionEventTypes.EVENT_KUBERNETES_VERSION_DELETE, + eventDescription = "deleting Kubernetes supported version", async = true) public boolean deleteKubernetesSupportedVersion(final DeleteKubernetesSupportedVersionCmd cmd) { if (!KubernetesClusterService.KubernetesServiceEnabled.value()) { throw new CloudRuntimeException("Kubernetes Service plugin is disabled"); @@ -373,7 +393,8 @@ public class KubernetesVersionManagerImpl extends ManagerBase implements Kuberne } @Override - @ActionEvent(eventType = KubernetesVersionEventTypes.EVENT_KUBERNETES_VERSION_UPDATE, eventDescription = "Updating Kubernetes supported version") + @ActionEvent(eventType = KubernetesVersionEventTypes.EVENT_KUBERNETES_VERSION_UPDATE, + eventDescription = "Updating Kubernetes supported version") public KubernetesSupportedVersionResponse updateKubernetesSupportedVersion(final UpdateKubernetesSupportedVersionCmd cmd) { if (!KubernetesClusterService.KubernetesServiceEnabled.value()) { throw new CloudRuntimeException("Kubernetes Service plugin is disabled"); diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/admin/kubernetes/version/AddKubernetesSupportedVersionCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/admin/kubernetes/version/AddKubernetesSupportedVersionCmd.java index 5a86bc74fed..139b79a182a 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/admin/kubernetes/version/AddKubernetesSupportedVersionCmd.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/admin/kubernetes/version/AddKubernetesSupportedVersionCmd.java @@ -21,6 +21,7 @@ import javax.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseCmd; @@ -134,6 +135,11 @@ public class AddKubernetesSupportedVersionCmd extends BaseCmd implements AdminCm return CallContext.current().getCallingAccountId(); } + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.KubernetesSupportedVersion; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/admin/kubernetes/version/DeleteKubernetesSupportedVersionCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/admin/kubernetes/version/DeleteKubernetesSupportedVersionCmd.java index b70a46804e0..a7799a33f18 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/admin/kubernetes/version/DeleteKubernetesSupportedVersionCmd.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/admin/kubernetes/version/DeleteKubernetesSupportedVersionCmd.java @@ -21,6 +21,7 @@ import javax.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseAsyncCmd; @@ -85,6 +86,11 @@ public class DeleteKubernetesSupportedVersionCmd extends BaseAsyncCmd implements return description; } + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.KubernetesSupportedVersion; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/admin/kubernetes/version/UpdateKubernetesSupportedVersionCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/admin/kubernetes/version/UpdateKubernetesSupportedVersionCmd.java index 35a9c068837..990b6a936b7 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/admin/kubernetes/version/UpdateKubernetesSupportedVersionCmd.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/admin/kubernetes/version/UpdateKubernetesSupportedVersionCmd.java @@ -21,6 +21,7 @@ import javax.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseCmd; @@ -29,6 +30,7 @@ import org.apache.cloudstack.api.ResponseObject; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.admin.AdminCmd; import org.apache.cloudstack.api.response.KubernetesSupportedVersionResponse; +import org.apache.cloudstack.context.CallContext; import com.cloud.exception.ConcurrentOperationException; import com.cloud.kubernetes.version.KubernetesSupportedVersion; @@ -73,7 +75,17 @@ public class UpdateKubernetesSupportedVersionCmd extends BaseCmd implements Admi @Override public long getEntityOwnerId() { - return 0; + return CallContext.current().getCallingAccountId(); + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.KubernetesSupportedVersion; + } + + @Override + public Long getApiResourceId() { + return getId(); } ///////////////////////////////////////////////////// diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java index c5551021772..56d7b0c5838 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/CreateKubernetesClusterCmd.java @@ -272,26 +272,23 @@ public class CreateKubernetesClusterCmd extends BaseAsyncCreateCmd { @Override public String getCreateEventDescription() { - return "creating Kubernetes cluster"; + return "Creating Kubernetes cluster"; } @Override public String getEventDescription() { - return "Creating Kubernetes cluster. Cluster Id: " + getEntityId(); + return "Creating Kubernetes cluster Id: " + getEntityId(); } @Override public ApiCommandResourceType getApiResourceType() { - return ApiCommandResourceType.VirtualMachine; + return ApiCommandResourceType.KubernetesCluster; } @Override public void execute() { try { - if (KubernetesCluster.ClusterType.valueOf(getClusterType()) == KubernetesCluster.ClusterType.CloudManaged - && !kubernetesClusterService.startKubernetesCluster(getEntityId(), true)) { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to start Kubernetes cluster"); - } + kubernetesClusterService.startKubernetesCluster(this); KubernetesClusterResponse response = kubernetesClusterService.createKubernetesClusterResponse(getEntityId()); response.setResponseName(getCommandName()); setResponseObject(response); diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/DeleteKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/DeleteKubernetesClusterCmd.java index 05080de9277..2ce8151c063 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/DeleteKubernetesClusterCmd.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/DeleteKubernetesClusterCmd.java @@ -20,6 +20,7 @@ import javax.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseAsyncCmd; @@ -112,6 +113,16 @@ public class DeleteKubernetesClusterCmd extends BaseAsyncCmd { return KubernetesClusterEventTypes.EVENT_KUBERNETES_CLUSTER_DELETE; } + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.KubernetesCluster; + } + + @Override + public Long getApiResourceId() { + return getId(); + } + @Override public String getEventDescription() { String description = "Deleting Kubernetes cluster"; diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/RemoveVirtualMachinesFromKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/RemoveVirtualMachinesFromKubernetesClusterCmd.java index a6452b829c7..5c87f01862f 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/RemoveVirtualMachinesFromKubernetesClusterCmd.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/RemoveVirtualMachinesFromKubernetesClusterCmd.java @@ -20,6 +20,7 @@ import com.cloud.kubernetes.cluster.KubernetesClusterService; import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseCmd; @@ -83,6 +84,11 @@ public class RemoveVirtualMachinesFromKubernetesClusterCmd extends BaseListCmd { return CallContext.current().getCallingAccount().getId(); } + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.KubernetesCluster; + } + @Override public void execute() throws ServerApiException { try { diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/ScaleKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/ScaleKubernetesClusterCmd.java index 02865357b8a..ee3566564e4 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/ScaleKubernetesClusterCmd.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/ScaleKubernetesClusterCmd.java @@ -24,6 +24,7 @@ import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseAsyncCmd; @@ -144,6 +145,11 @@ public class ScaleKubernetesClusterCmd extends BaseAsyncCmd { return CallContext.current().getCallingAccount().getId(); } + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.KubernetesCluster; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/StartKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/StartKubernetesClusterCmd.java index bb0111af232..bfe00ca27b2 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/StartKubernetesClusterCmd.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/StartKubernetesClusterCmd.java @@ -20,6 +20,7 @@ import javax.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseAsyncCmd; @@ -85,33 +86,20 @@ public class StartKubernetesClusterCmd extends BaseAsyncCmd { return CallContext.current().getCallingAccount().getId(); } + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.KubernetesCluster; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// - public KubernetesCluster validateRequest() { - if (getId() == null || getId() < 1L) { - throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid Kubernetes cluster ID provided"); - } - final KubernetesCluster kubernetesCluster = kubernetesClusterService.findById(getId()); - if (kubernetesCluster == null) { - throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Given Kubernetes cluster was not found"); - } - if (!kubernetesClusterService.isCommandSupported(kubernetesCluster, getActualCommandName())) { - throw new ServerApiException(ApiErrorCode.PARAM_ERROR, - String.format("Start kubernetes cluster is not supported for an externally managed cluster (%s)", kubernetesCluster.getName())); - } - return kubernetesCluster; - } - @Override public void execute() throws ServerApiException, ConcurrentOperationException { - final KubernetesCluster kubernetesCluster = validateRequest(); try { - if (!kubernetesClusterService.startKubernetesCluster(kubernetesCluster.getId(), false)) { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to start Kubernetes cluster ID: %d", getId())); - } - final KubernetesClusterResponse response = kubernetesClusterService.createKubernetesClusterResponse(kubernetesCluster.getId()); + kubernetesClusterService.startKubernetesCluster(this); + final KubernetesClusterResponse response = kubernetesClusterService.createKubernetesClusterResponse(getId()); response.setResponseName(getCommandName()); setResponseObject(response); } catch (CloudRuntimeException ex) { diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/StopKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/StopKubernetesClusterCmd.java index 5c7dc92ef67..23d6878cf72 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/StopKubernetesClusterCmd.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/StopKubernetesClusterCmd.java @@ -20,6 +20,7 @@ import javax.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseAsyncCmd; @@ -86,6 +87,11 @@ public class StopKubernetesClusterCmd extends BaseAsyncCmd { return CallContext.current().getCallingAccount().getId(); } + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.KubernetesCluster; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/UpgradeKubernetesClusterCmd.java b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/UpgradeKubernetesClusterCmd.java index 18bdbb53527..a3f2e057645 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/UpgradeKubernetesClusterCmd.java +++ b/plugins/integrations/kubernetes-service/src/main/java/org/apache/cloudstack/api/command/user/kubernetes/cluster/UpgradeKubernetesClusterCmd.java @@ -21,6 +21,7 @@ import javax.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseAsyncCmd; @@ -96,6 +97,11 @@ public class UpgradeKubernetesClusterCmd extends BaseAsyncCmd { return CallContext.current().getCallingAccount().getId(); } + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.KubernetesCluster; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/plugins/integrations/kubernetes-service/src/main/resources/META-INF/cloudstack/kubernetes-service/spring-kubernetes-service-context.xml b/plugins/integrations/kubernetes-service/src/main/resources/META-INF/cloudstack/kubernetes-service/spring-kubernetes-service-context.xml index cf9faeeead5..9d236eed26c 100644 --- a/plugins/integrations/kubernetes-service/src/main/resources/META-INF/cloudstack/kubernetes-service/spring-kubernetes-service-context.xml +++ b/plugins/integrations/kubernetes-service/src/main/resources/META-INF/cloudstack/kubernetes-service/spring-kubernetes-service-context.xml @@ -34,8 +34,8 @@ - - + + diff --git a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImplTest.java b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesServiceHelperImplTest.java similarity index 89% rename from plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImplTest.java rename to plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesServiceHelperImplTest.java index 167ade67cfb..8749fe2b7dc 100644 --- a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesClusterHelperImplTest.java +++ b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/cluster/KubernetesServiceHelperImplTest.java @@ -31,20 +31,20 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.UserVmManager; @RunWith(MockitoJUnitRunner.class) -public class KubernetesClusterHelperImplTest { +public class KubernetesServiceHelperImplTest { @Mock KubernetesClusterVmMapDao kubernetesClusterVmMapDao; @Mock KubernetesClusterDao kubernetesClusterDao; @InjectMocks - KubernetesClusterHelperImpl kubernetesClusterHelper = new KubernetesClusterHelperImpl(); + KubernetesServiceHelperImpl kubernetesServiceHelper = new KubernetesServiceHelperImpl(); @Test public void testCheckVmCanBeDestroyedNotCKSNode() { UserVm vm = Mockito.mock(UserVm.class); Mockito.when(vm.getUserVmType()).thenReturn(""); - kubernetesClusterHelper.checkVmCanBeDestroyed(vm); + kubernetesServiceHelper.checkVmCanBeDestroyed(vm); Mockito.verify(kubernetesClusterVmMapDao, Mockito.never()).findByVmId(Mockito.anyLong()); } @@ -54,7 +54,7 @@ public class KubernetesClusterHelperImplTest { Mockito.when(vm.getId()).thenReturn(1L); Mockito.when(vm.getUserVmType()).thenReturn(UserVmManager.CKS_NODE); Mockito.when(kubernetesClusterVmMapDao.findByVmId(1L)).thenReturn(null); - kubernetesClusterHelper.checkVmCanBeDestroyed(vm); + kubernetesServiceHelper.checkVmCanBeDestroyed(vm); } @Test(expected = CloudRuntimeException.class) @@ -66,6 +66,6 @@ public class KubernetesClusterHelperImplTest { Mockito.when(map.getClusterId()).thenReturn(1L); Mockito.when(kubernetesClusterVmMapDao.findByVmId(1L)).thenReturn(map); Mockito.when(kubernetesClusterDao.findById(1L)).thenReturn(Mockito.mock(KubernetesClusterVO.class)); - kubernetesClusterHelper.checkVmCanBeDestroyed(vm); + kubernetesServiceHelper.checkVmCanBeDestroyed(vm); } } diff --git a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/version/KubernetesVersionServiceTest.java b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/version/KubernetesVersionServiceTest.java index d92b2c438c0..f7e816596c2 100644 --- a/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/version/KubernetesVersionServiceTest.java +++ b/plugins/integrations/kubernetes-service/src/test/java/com/cloud/kubernetes/version/KubernetesVersionServiceTest.java @@ -35,6 +35,7 @@ import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.commons.collections.CollectionUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -66,6 +67,7 @@ import com.cloud.user.AccountManager; import com.cloud.user.AccountVO; import com.cloud.user.User; import com.cloud.user.UserVO; +import com.cloud.utils.Pair; import com.cloud.utils.component.ComponentContext; import com.cloud.utils.db.Filter; import com.cloud.utils.db.SearchBuilder; @@ -142,14 +144,13 @@ public class KubernetesVersionServiceTest { when(versionVO.getSemanticVersion()).thenReturn(KubernetesVersionService.MIN_KUBERNETES_VERSION); versionVOs.add(versionVO); when(kubernetesSupportedVersionDao.findById(Mockito.anyLong())).thenReturn(versionVO); - when(kubernetesSupportedVersionDao.search(Mockito.any(SearchCriteria.class), Mockito.any(Filter.class))).thenReturn(versionVOs); - ListResponse response = - kubernetesVersionService.listKubernetesSupportedVersions( - cmd); - Assert.assertNotNull(response); - Assert.assertEquals(Integer.valueOf(1), response.getCount()); - Assert.assertEquals(1, response.getResponses().size()); - Assert.assertEquals(KubernetesVersionService.MIN_KUBERNETES_VERSION, response.getResponses().get(0).getSemanticVersion()); + when(kubernetesSupportedVersionDao.searchAndCount(Mockito.any(SearchCriteria.class), + Mockito.any(Filter.class))).thenReturn(new Pair<>(versionVOs, versionVOs.size())); + ListResponse versionsResponse = + kubernetesVersionService.listKubernetesSupportedVersions(cmd); + Assert.assertEquals(versionVOs.size(), versionsResponse.getCount().intValue()); + Assert.assertTrue(CollectionUtils.isNotEmpty(versionsResponse.getResponses())); + Assert.assertEquals(versionVOs.size(), versionsResponse.getResponses().size()); } @Test(expected = InvalidParameterValueException.class) @@ -214,9 +215,12 @@ public class KubernetesVersionServiceTest { when(cmd.getMinimumRamSize()).thenReturn(KubernetesClusterService.MIN_KUBERNETES_CLUSTER_NODE_RAM_SIZE); Account systemAccount = new AccountVO("system", 1L, "", Account.Type.ADMIN, "uuid"); when(accountManager.getSystemAccount()).thenReturn(systemAccount); - try (MockedStatic mockedComponentContext = Mockito.mockStatic(ComponentContext.class)) { + CallContext callContext = Mockito.mock(CallContext.class); + try (MockedStatic mockedComponentContext = Mockito.mockStatic(ComponentContext.class); + MockedStatic mockedCallContext = Mockito.mockStatic(CallContext.class)) { mockedComponentContext.when(() -> ComponentContext.inject(Mockito.any(RegisterIsoCmd.class))).thenReturn( new RegisterIsoCmd()); + mockedCallContext.when(CallContext::current).thenReturn(callContext); when(templateService.registerIso(Mockito.any(RegisterIsoCmd.class))).thenReturn( Mockito.mock(VirtualMachineTemplate.class)); diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java index 61eb7041a23..3d09fce8bab 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java @@ -100,6 +100,7 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.datastore.util.LinstorConfigurationManager; import org.apache.cloudstack.storage.datastore.util.LinstorUtil; +import org.apache.cloudstack.storage.snapshot.SnapshotObject; import org.apache.cloudstack.storage.to.SnapshotObjectTO; import org.apache.cloudstack.storage.to.VolumeObjectTO; import org.apache.cloudstack.storage.volume.VolumeObject; @@ -231,7 +232,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver try { - ApiCallRcList answers = linstorApi.resourceSnapshotDelete(rscDefName, snapshotName); + ApiCallRcList answers = linstorApi.resourceSnapshotDelete(rscDefName, snapshotName, Collections.emptyList()); if (answers.hasError()) { for (ApiCallRc answer : answers) @@ -1004,25 +1005,29 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver * @param api Linstor Developer api object * @param pool StoragePool this resource resides on * @param rscName rscName of the snapshotted resource - * @param snapshotInfo snapshot info of the snapshot + * @param snapshotName Name of the snapshot to copy from + * @param snapshotObject snapshot object of the origCmd, so the path can be modified * @param origCmd original LinstorBackupSnapshotCommand that needs to have a patched path * @return answer from agent operation * @throws ApiException if any Linstor api operation fails */ private Answer copyFromTemporaryResource( - DevelopersApi api, StoragePoolVO pool, String rscName, SnapshotInfo snapshotInfo, CopyCommand origCmd) + DevelopersApi api, + StoragePoolVO pool, + String rscName, + String snapshotName, + SnapshotObject snapshotObject, + CopyCommand origCmd) throws ApiException { Answer answer; String restoreName = rscName + "-rst"; - String snapshotName = LinstorUtil.RSC_PREFIX + snapshotInfo.getUuid(); String devName = restoreResourceFromSnapshot(api, pool, rscName, snapshotName, restoreName); Optional optEPAny = getLinstorEP(api, restoreName); if (optEPAny.isPresent()) { // patch the src device path to the temporary linstor resource - SnapshotObjectTO soTO = (SnapshotObjectTO)snapshotInfo.getTO(); - soTO.setPath(devName); - origCmd.setSrcTO(soTO); + snapshotObject.setPath(devName); + origCmd.setSrcTO(snapshotObject.getTO()); answer = optEPAny.get().sendMessage(origCmd); } else{ answer = new Answer(origCmd, false, "Unable to get matching Linstor endpoint."); @@ -1032,13 +1037,36 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver return answer; } + /** + * vmsnapshots don't have our typical snapshot path set + * instead the path is the internal snapshot name e.g.: {vm}_VS_{datestr} + * we have to find out and modify the path here before + * @return the original snapshotObject.getPath() + */ + private String setCorrectSnapshotPath(DevelopersApi api, String rscName, SnapshotObject snapshotObject) + throws ApiException { + String originalPath = LinstorUtil.RSC_PREFIX + snapshotObject.getUuid(); + if (!(snapshotObject.getPath().startsWith("/dev/mapper/") || + snapshotObject.getPath().startsWith("zfs://"))) { + originalPath = snapshotObject.getPath(); + com.linbit.linstor.api.model.StoragePool linStoragePool = + LinstorUtil.getDiskfulStoragePool(api, rscName); + if (linStoragePool == null) { + throw new CloudRuntimeException("Linstor: Unable to find storage pool for resource " + rscName); + } + final String path = LinstorUtil.getSnapshotPath(linStoragePool, rscName, snapshotObject.getPath()); + snapshotObject.setPath(path); + } + return originalPath; + } + protected Answer copySnapshot(DataObject srcData, DataObject destData) { String value = _configDao.getValue(Config.BackupSnapshotWait.toString()); int _backupsnapshotwait = NumbersUtil.parseInt( value, Integer.parseInt(Config.BackupSnapshotWait.getDefaultValue())); - SnapshotInfo snapshotInfo = (SnapshotInfo)srcData; - Boolean snapshotFullBackup = snapshotInfo.getFullBackup(); + SnapshotObject snapshotObject = (SnapshotObject)srcData; + Boolean snapshotFullBackup = snapshotObject.getFullBackup(); final StoragePoolVO pool = _storagePoolDao.findById(srcData.getDataStore().getId()); final DevelopersApi api = LinstorUtil.getLinstorAPI(pool.getHostAddress()); boolean fullSnapshot = true; @@ -1049,28 +1077,30 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver options.put("fullSnapshot", fullSnapshot + ""); options.put(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.key(), String.valueOf(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value())); - options.put("volumeSize", snapshotInfo.getBaseVolume().getSize() + ""); + options.put("volumeSize", snapshotObject.getBaseVolume().getSize() + ""); try { + final String rscName = LinstorUtil.RSC_PREFIX + snapshotObject.getBaseVolume().getUuid(); + String snapshotName = setCorrectSnapshotPath(api, rscName, snapshotObject); + CopyCommand cmd = new LinstorBackupSnapshotCommand( - srcData.getTO(), + snapshotObject.getTO(), destData.getTO(), _backupsnapshotwait, VirtualMachineManager.ExecuteInSequence.value()); cmd.setOptions(options); - String rscName = LinstorUtil.RSC_PREFIX + snapshotInfo.getBaseVolume().getUuid(); Optional optEP = getDiskfullEP(api, rscName); Answer answer; if (optEP.isPresent()) { answer = optEP.get().sendMessage(cmd); } else { logger.debug("No diskfull endpoint found to copy image, creating diskless endpoint"); - answer = copyFromTemporaryResource(api, pool, rscName, snapshotInfo, cmd); + answer = copyFromTemporaryResource(api, pool, rscName, snapshotName, snapshotObject, cmd); } return answer; } catch (Exception e) { - logger.debug("copy snapshot failed: ", e); + logger.debug("copy snapshot failed, please cleanup snapshot manually: ", e); throw new CloudRuntimeException(e.toString()); } diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java index f507147001c..bdb99945958 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java @@ -113,7 +113,8 @@ public class LinstorUtil { Collections.singletonList(storagePoolName), Collections.emptyList(), null, - null + null, + true ); return sps != null ? sps : Collections.emptyList(); } @@ -167,7 +168,8 @@ public class LinstorUtil { rscGrps.get(0).getSelectFilter().getStoragePoolList(), null, null, - null + null, + true ); } diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/snapshot/LinstorVMSnapshotStrategy.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/snapshot/LinstorVMSnapshotStrategy.java new file mode 100644 index 00000000000..af7b6978db5 --- /dev/null +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/snapshot/LinstorVMSnapshotStrategy.java @@ -0,0 +1,371 @@ +// +//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.storage.snapshot; + +import com.linbit.linstor.api.ApiException; +import com.linbit.linstor.api.DevelopersApi; +import com.linbit.linstor.api.model.ApiCallRcList; +import com.linbit.linstor.api.model.CreateMultiSnapshotRequest; +import com.linbit.linstor.api.model.Snapshot; + +import javax.inject.Inject; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.cloud.agent.api.VMSnapshotTO; +import com.cloud.event.EventTypes; +import com.cloud.event.UsageEventUtils; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.uservm.UserVm; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.snapshot.VMSnapshot; +import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; +import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.datastore.util.LinstorUtil; +import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.apache.cloudstack.storage.vmsnapshot.DefaultVMSnapshotStrategy; +import org.apache.cloudstack.storage.vmsnapshot.VMSnapshotHelper; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; + +@Component +public class LinstorVMSnapshotStrategy extends DefaultVMSnapshotStrategy { + private static final Logger log = Logger.getLogger(LinstorVMSnapshotStrategy.class); + + @Inject + private VMSnapshotHelper _vmSnapshotHelper; + @Inject + private UserVmDao _userVmDao; + @Inject + private VMSnapshotDao vmSnapshotDao; + @Inject + private VolumeDao volumeDao; + @Inject + private DiskOfferingDao diskOfferingDao; + @Inject + private PrimaryDataStoreDao _storagePoolDao; + + private void linstorCreateMultiSnapshot( + DevelopersApi api, VMSnapshotVO vmSnapshotVO, List volumeTOs) + throws ApiException { + CreateMultiSnapshotRequest cmsReq = new CreateMultiSnapshotRequest(); + for (VolumeObjectTO vol : volumeTOs) { + Snapshot snap = new Snapshot(); + snap.setName(vmSnapshotVO.getName()); + snap.setResourceName(LinstorUtil.RSC_PREFIX + vol.getPath()); + log.debug(String.format("Add volume %s;%s to snapshot", vol.getName(), snap.getResourceName())); + cmsReq.addSnapshotsItem(snap); + } + log.debug(String.format("Creating multi snapshot %s", vmSnapshotVO.getName())); + ApiCallRcList answers = api.createMultiSnapshot(cmsReq); + log.debug(String.format("Created multi snapshot %s", vmSnapshotVO.getName())); + if (answers.hasError()) { + throw new CloudRuntimeException( + "Error creating vm snapshots: " + LinstorUtil.getBestErrorMessage(answers)); + } + } + + private VMSnapshotVO findAndSetCurrentSnapshot(long vmId, VMSnapshotVO vmSnapshotVO) { + VMSnapshotTO current = null; + VMSnapshotVO currentSnapshot = vmSnapshotDao.findCurrentSnapshotByVmId(vmId); + if (currentSnapshot != null) { + current = _vmSnapshotHelper.getSnapshotWithParents(currentSnapshot); + } + + if (current == null) { + vmSnapshotVO.setParent(null); + } else { + vmSnapshotVO.setParent(current.getId()); + } + + return vmSnapshotVO; + } + + private long getNewChainSizeAndPublishCreate(VMSnapshot vmSnapshot, List volumeTOs, UserVm userVm) { + long new_chain_size = 0; + for (VolumeObjectTO volumeObjectTO : volumeTOs) { + publishUsageEvents(EventTypes.EVENT_VM_SNAPSHOT_CREATE, vmSnapshot, userVm, volumeObjectTO); + new_chain_size += volumeObjectTO.getSize(); + log.info("EventTypes.EVENT_VM_SNAPSHOT_CREATE publishUsageEvent" + volumeObjectTO); + } + return new_chain_size; + } + + @Override + public VMSnapshot takeVMSnapshot(VMSnapshot vmSnapshot) { + log.info("Take vm snapshot: " + vmSnapshot.getName()); + UserVm userVm = _userVmDao.findById(vmSnapshot.getVmId()); + VMSnapshotVO vmSnapshotVO = (VMSnapshotVO) vmSnapshot; + + try { + _vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshotVO, VMSnapshot.Event.CreateRequested); + } catch (NoTransitionException e) { + throw new CloudRuntimeException("No transition: " + e.getMessage()); + } + + boolean result = false; + try { + final List volumeTOs = _vmSnapshotHelper.getVolumeTOList(userVm.getId()); + final StoragePoolVO storagePool = _storagePoolDao.findById(volumeTOs.get(0).getPoolId()); + final DevelopersApi api = LinstorUtil.getLinstorAPI(storagePool.getHostAddress()); + + long prev_chain_size = 0; + long virtual_size = 0; + for (VolumeObjectTO volume : volumeTOs) { + virtual_size += volume.getSize(); + VolumeVO volumeVO = volumeDao.findById(volume.getId()); + prev_chain_size += volumeVO.getVmSnapshotChainSize() == null ? 0 : volumeVO.getVmSnapshotChainSize(); + } + + findAndSetCurrentSnapshot(userVm.getId(), vmSnapshotVO); + + linstorCreateMultiSnapshot(api, vmSnapshotVO, volumeTOs); + + log.debug(String.format("finalize vm snapshot create for %s", vmSnapshotVO.getName())); + finalizeCreate(vmSnapshotVO, volumeTOs); + + result = _vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationSucceeded); + long new_chain_size = getNewChainSizeAndPublishCreate(vmSnapshot, volumeTOs, userVm); + publishUsageEvents( + EventTypes.EVENT_VM_SNAPSHOT_ON_PRIMARY, vmSnapshot, userVm, new_chain_size - prev_chain_size, virtual_size); + return vmSnapshot; + } catch (Exception e) { + log.debug("Could not create VM snapshot:" + e.getMessage()); + throw new CloudRuntimeException("Could not create VM snapshot:" + e.getMessage()); + } finally { + if (!result) { + try { + _vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationFailed); + log.info(String.format("VMSnapshot.Event.OperationFailed vmSnapshot=%s", vmSnapshot)); + } catch (NoTransitionException nte) { + log.error("Cannot set vm state:" + nte.getMessage()); + } + } + } + } + + @Override + public StrategyPriority canHandle(Long vmId, Long rootPoolId, boolean snapshotMemory) { + if (snapshotMemory) { + return StrategyPriority.CANT_HANDLE; + } + return allVolumesOnLinstor(vmId); + } + + @Override + public StrategyPriority canHandle(VMSnapshot vmSnapshot) { + VMSnapshotVO vmSnapshotVO = (VMSnapshotVO) vmSnapshot; + if (vmSnapshotVO.getType() != VMSnapshot.Type.Disk) { + return StrategyPriority.CANT_HANDLE; + } + return allVolumesOnLinstor(vmSnapshot.getVmId()); + } + + private StrategyPriority allVolumesOnLinstor(Long vmId) { + List volumeTOs = _vmSnapshotHelper.getVolumeTOList(vmId); + if (volumeTOs == null || volumeTOs.isEmpty()) { + return StrategyPriority.CANT_HANDLE; + } + for (VolumeObjectTO volumeTO : volumeTOs) { + Long poolId = volumeTO.getPoolId(); + StoragePoolVO pool = _storagePoolDao.findById(poolId); + if (!pool.getStorageProviderName().equals(LinstorUtil.PROVIDER_NAME)) { + return StrategyPriority.CANT_HANDLE; + } + } + return StrategyPriority.HIGHEST; + } + + private String linstorDeleteSnapshot(final DevelopersApi api, final String rscName, final String snapshotName) { + String resultMsg = null; + try { + ApiCallRcList answers = api.resourceSnapshotDelete(rscName, snapshotName, Collections.emptyList()); + if (answers.hasError()) { + resultMsg = LinstorUtil.getBestErrorMessage(answers); + } + } catch (ApiException apiEx) { + log.error("Linstor: ApiEx - " + apiEx.getBestMessage()); + resultMsg = apiEx.getBestMessage(); + } + + return resultMsg; + } + + @Override + public boolean deleteVMSnapshot(VMSnapshot vmSnapshot) { + UserVmVO userVm = _userVmDao.findById(vmSnapshot.getVmId()); + VMSnapshotVO vmSnapshotVO = (VMSnapshotVO) vmSnapshot; + try { + _vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.ExpungeRequested); + } catch (NoTransitionException e) { + log.debug("Failed to change vm snapshot state with event ExpungeRequested"); + throw new CloudRuntimeException( + "Failed to change vm snapshot state with event ExpungeRequested: " + e.getMessage()); + } + + List volumeTOs = _vmSnapshotHelper.getVolumeTOList(vmSnapshot.getVmId()); + final StoragePoolVO storagePool = _storagePoolDao.findById(volumeTOs.get(0).getPoolId()); + final DevelopersApi api = LinstorUtil.getLinstorAPI(storagePool.getHostAddress()); + + final String snapshotName = vmSnapshotVO.getName(); + final List failedToDelete = new ArrayList<>(); + for (VolumeObjectTO volumeObjectTO : volumeTOs) { + final String rscName = LinstorUtil.RSC_PREFIX + volumeObjectTO.getUuid(); + String err = linstorDeleteSnapshot(api, rscName, snapshotName); + + if (err != null) + { + String errMsg = String.format("Unable to delete Linstor resource %s snapshot %s: %s", + rscName, snapshotName, err); + log.error(errMsg); + failedToDelete.add(errMsg); + } + log.info("Linstor: Deleted snapshot " + snapshotName + " for resource " + rscName); + } + + if (!failedToDelete.isEmpty()) { + throw new CloudRuntimeException(StringUtils.join(failedToDelete, "\n")); + } + + finalizeDelete(vmSnapshotVO, volumeTOs); + vmSnapshotDao.remove(vmSnapshot.getId()); + + long full_chain_size = 0; + for (VolumeObjectTO volumeTo : volumeTOs) { + publishUsageEvents(EventTypes.EVENT_VM_SNAPSHOT_DELETE, vmSnapshot, userVm, volumeTo); + full_chain_size += volumeTo.getSize(); + } + publishUsageEvents(EventTypes.EVENT_VM_SNAPSHOT_OFF_PRIMARY, vmSnapshot, userVm, full_chain_size, 0L); + return true; + } + + private String linstorRevertSnapshot(final DevelopersApi api, final String rscName, final String snapshotName) { + String resultMsg = null; + try { + ApiCallRcList answers = api.resourceSnapshotRollback(rscName, snapshotName); + if (answers.hasError()) { + resultMsg = LinstorUtil.getBestErrorMessage(answers); + } + } catch (ApiException apiEx) { + log.error("Linstor: ApiEx - " + apiEx.getBestMessage()); + resultMsg = apiEx.getBestMessage(); + } + + return resultMsg; + } + + private boolean revertVMSnapshotOperation(VMSnapshot vmSnapshot, long userVmId) throws NoTransitionException { + VMSnapshotVO vmSnapshotVO = (VMSnapshotVO) vmSnapshot; + List volumeTOs = _vmSnapshotHelper.getVolumeTOList(userVmId); + + final StoragePoolVO storagePool = _storagePoolDao.findById(volumeTOs.get(0).getPoolId()); + final DevelopersApi api = LinstorUtil.getLinstorAPI(storagePool.getHostAddress()); + final String snapshotName = vmSnapshotVO.getName(); + + for (VolumeObjectTO volumeObjectTO : volumeTOs) { + final String rscName = LinstorUtil.RSC_PREFIX + volumeObjectTO.getUuid(); + String err = linstorRevertSnapshot(api, rscName, snapshotName); + if (err != null) { + throw new CloudRuntimeException(String.format( + "Unable to revert Linstor resource %s with snapshot %s: %s", rscName, snapshotName, err)); + } + } + finalizeRevert(vmSnapshotVO, volumeTOs); + return _vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationSucceeded); + } + + @Override + public boolean revertVMSnapshot(VMSnapshot vmSnapshot) { + log.debug("Revert vm snapshot: " + vmSnapshot.getName()); + VMSnapshotVO vmSnapshotVO = (VMSnapshotVO) vmSnapshot; + UserVmVO userVm = _userVmDao.findById(vmSnapshot.getVmId()); + + if (userVm.getState() == VirtualMachine.State.Running && vmSnapshotVO.getType() == VMSnapshot.Type.Disk) { + throw new CloudRuntimeException("Virtual machine should be in stopped state for revert operation"); + } + + try { + _vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshotVO, VMSnapshot.Event.RevertRequested); + } catch (NoTransitionException e) { + throw new CloudRuntimeException(e.getMessage()); + } + + boolean result = false; + try { + result = revertVMSnapshotOperation(vmSnapshot, userVm.getId()); + } catch (CloudRuntimeException | NoTransitionException e) { + String errMsg = String.format( + "Error while finalize create vm snapshot [%s] due to %s", vmSnapshot.getName(), e.getMessage()); + log.error(errMsg, e); + throw new CloudRuntimeException(errMsg); + } finally { + if (!result) { + try { + _vmSnapshotHelper.vmSnapshotStateTransitTo(vmSnapshot, VMSnapshot.Event.OperationFailed); + } catch (NoTransitionException e1) { + log.error("Cannot set vm snapshot state due to: " + e1.getMessage()); + } + } + } + return result; + } + + private void publishUsageEvents(String type, VMSnapshot vmSnapshot, UserVm userVm, VolumeObjectTO volumeTo) { + VolumeVO volume = volumeDao.findById(volumeTo.getId()); + Long diskOfferingId = volume.getDiskOfferingId(); + Long offeringId = null; + if (diskOfferingId != null) { + DiskOfferingVO offering = diskOfferingDao.findById(diskOfferingId); + if (offering != null && offering.isComputeOnly()) { + offeringId = offering.getId(); + } + } + UsageEventUtils.publishUsageEvent(type, vmSnapshot.getAccountId(), userVm.getDataCenterId(), userVm.getId(), + vmSnapshot.getName(), offeringId, volume.getId(), volumeTo.getSize(), VMSnapshot.class.getName(), + vmSnapshot.getUuid()); + } + + private void publishUsageEvents( + String type, + VMSnapshot vmSnapshot, + UserVm userVm, + Long vmSnapSize, + Long virtualSize) { + try { + UsageEventUtils.publishUsageEvent(type, vmSnapshot.getAccountId(), userVm.getDataCenterId(), userVm.getId(), + vmSnapshot.getName(), 0L, 0L, vmSnapSize, virtualSize, VMSnapshot.class.getName(), + vmSnapshot.getUuid()); + } catch (Exception e) { + log.error("Failed to publish usage event " + type, e); + } + } +} diff --git a/plugins/storage/volume/linstor/src/main/resources/META-INF/cloudstack/storage-volume-linstor/spring-storage-volume-linstor-context.xml b/plugins/storage/volume/linstor/src/main/resources/META-INF/cloudstack/storage-volume-linstor/spring-storage-volume-linstor-context.xml index 48913cdb3f2..a900323ede5 100644 --- a/plugins/storage/volume/linstor/src/main/resources/META-INF/cloudstack/storage-volume-linstor/spring-storage-volume-linstor-context.xml +++ b/plugins/storage/volume/linstor/src/main/resources/META-INF/cloudstack/storage-volume-linstor/spring-storage-volume-linstor-context.xml @@ -29,6 +29,8 @@ + diff --git a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java index fb4f4cc00a5..6741a0b4068 100644 --- a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java +++ b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmd.java @@ -142,6 +142,14 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent return responseObject; } + protected void checkAndFailOnMissingSAMLSignature(Signature signature) { + if (signature == null && SAML2AuthManager.SAMLCheckSignature.value()) { + logger.error("Failing SAML login due to missing signature in the SAML response and signature check is enforced. " + + "Please check and ensure the IDP configuration has signing certificate or relax the saml2.check.signature setting."); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Signature is missing from the SAML Response. Please contact the Administrator"); + } + } + @Override public String authenticate(final String command, final Map params, final HttpSession session, final InetAddress remoteAddress, final String responseType, final StringBuilder auditTrailSb, final HttpServletRequest req, final HttpServletResponse resp) throws ServerApiException { try { @@ -223,6 +231,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent session.setAttribute(SAMLPluginConstants.SAML_IDPID, issuer.getValue()); Signature sig = processedSAMLResponse.getSignature(); + checkAndFailOnMissingSAMLSignature(sig); if (idpMetadata.getSigningCertificate() != null && sig != null) { BasicX509Credential credential = new BasicX509Credential(); credential.setEntityCertificate(idpMetadata.getSigningCertificate()); @@ -236,9 +245,8 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent params, responseType)); } } - if (username == null) { - username = SAMLUtils.getValueFromAssertions(processedSAMLResponse.getAssertions(), SAML2AuthManager.SAMLUserAttributeName.value()); - } + + username = SAMLUtils.getValueFromAssertions(processedSAMLResponse.getAssertions(), SAML2AuthManager.SAMLUserAttributeName.value()); for (Assertion assertion: processedSAMLResponse.getAssertions()) { if (assertion!= null && assertion.getSubject() != null && assertion.getSubject().getNameID() != null) { @@ -270,6 +278,7 @@ public class SAML2LoginAPIAuthenticatorCmd extends BaseCmd implements APIAuthent continue; } Signature encSig = assertion.getSignature(); + checkAndFailOnMissingSAMLSignature(encSig); if (idpMetadata.getSigningCertificate() != null && encSig != null) { BasicX509Credential sigCredential = new BasicX509Credential(); sigCredential.setEntityCertificate(idpMetadata.getSigningCertificate()); diff --git a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManager.java b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManager.java index e52a7e32695..a5dae36581c 100644 --- a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManager.java +++ b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManager.java @@ -25,51 +25,54 @@ import java.util.Collection; public interface SAML2AuthManager extends PluggableAPIAuthenticator, PluggableService { - public static final ConfigKey SAMLIsPluginEnabled = new ConfigKey("Advanced", Boolean.class, "saml2.enabled", "false", + ConfigKey SAMLIsPluginEnabled = new ConfigKey("Advanced", Boolean.class, "saml2.enabled", "false", "Indicates whether SAML SSO plugin is enabled or not", true); - public static final ConfigKey SAMLServiceProviderID = new ConfigKey("Advanced", String.class, "saml2.sp.id", "org.apache.cloudstack", + ConfigKey SAMLServiceProviderID = new ConfigKey("Advanced", String.class, "saml2.sp.id", "org.apache.cloudstack", "SAML2 Service Provider Identifier String", true); - public static final ConfigKey SAMLServiceProviderContactPersonName = new ConfigKey("Advanced", String.class, "saml2.sp.contact.person", "CloudStack Developers", + ConfigKey SAMLServiceProviderContactPersonName = new ConfigKey("Advanced", String.class, "saml2.sp.contact.person", "CloudStack Developers", "SAML2 Service Provider Contact Person Name", true); - public static final ConfigKey SAMLServiceProviderContactEmail = new ConfigKey("Advanced", String.class, "saml2.sp.contact.email", "dev@cloudstack.apache.org", + ConfigKey SAMLServiceProviderContactEmail = new ConfigKey("Advanced", String.class, "saml2.sp.contact.email", "dev@cloudstack.apache.org", "SAML2 Service Provider Contact Email Address", true); - public static final ConfigKey SAMLServiceProviderOrgName = new ConfigKey("Advanced", String.class, "saml2.sp.org.name", "Apache CloudStack", + ConfigKey SAMLServiceProviderOrgName = new ConfigKey("Advanced", String.class, "saml2.sp.org.name", "Apache CloudStack", "SAML2 Service Provider Organization Name", true); - public static final ConfigKey SAMLServiceProviderOrgUrl = new ConfigKey("Advanced", String.class, "saml2.sp.org.url", "http://cloudstack.apache.org", + ConfigKey SAMLServiceProviderOrgUrl = new ConfigKey("Advanced", String.class, "saml2.sp.org.url", "http://cloudstack.apache.org", "SAML2 Service Provider Organization URL", true); - public static final ConfigKey SAMLServiceProviderSingleSignOnURL = new ConfigKey("Advanced", String.class, "saml2.sp.sso.url", "http://localhost:8080/client/api?command=samlSso", + ConfigKey SAMLServiceProviderSingleSignOnURL = new ConfigKey("Advanced", String.class, "saml2.sp.sso.url", "http://localhost:8080/client/api?command=samlSso", "SAML2 CloudStack Service Provider Single Sign On URL", true); - public static final ConfigKey SAMLServiceProviderSingleLogOutURL = new ConfigKey("Advanced", String.class, "saml2.sp.slo.url", "http://localhost:8080/client/", + ConfigKey SAMLServiceProviderSingleLogOutURL = new ConfigKey("Advanced", String.class, "saml2.sp.slo.url", "http://localhost:8080/client/", "SAML2 CloudStack Service Provider Single Log Out URL", true); - public static final ConfigKey SAMLCloudStackRedirectionUrl = new ConfigKey("Advanced", String.class, "saml2.redirect.url", "http://localhost:8080/client", + ConfigKey SAMLCloudStackRedirectionUrl = new ConfigKey("Advanced", String.class, "saml2.redirect.url", "http://localhost:8080/client", "The CloudStack UI url the SSO should redirected to when successful", true); - public static final ConfigKey SAMLUserAttributeName = new ConfigKey("Advanced", String.class, "saml2.user.attribute", "uid", + ConfigKey SAMLUserAttributeName = new ConfigKey("Advanced", String.class, "saml2.user.attribute", "uid", "Attribute name to be looked for in SAML response that will contain the username", true); - public static final ConfigKey SAMLIdentityProviderMetadataURL = new ConfigKey("Advanced", String.class, "saml2.idp.metadata.url", "https://openidp.feide.no/simplesaml/saml2/idp/metadata.php", + ConfigKey SAMLIdentityProviderMetadataURL = new ConfigKey("Advanced", String.class, "saml2.idp.metadata.url", "https://openidp.feide.no/simplesaml/saml2/idp/metadata.php", "SAML2 Identity Provider Metadata XML Url", true); - public static final ConfigKey SAMLDefaultIdentityProviderId = new ConfigKey("Advanced", String.class, "saml2.default.idpid", "https://openidp.feide.no", + ConfigKey SAMLDefaultIdentityProviderId = new ConfigKey("Advanced", String.class, "saml2.default.idpid", "https://openidp.feide.no", "The default IdP entity ID to use only in case of multiple IdPs", true); - public static final ConfigKey SAMLSignatureAlgorithm = new ConfigKey<>(String.class, "saml2.sigalg", "Advanced", "SHA1", + ConfigKey SAMLSignatureAlgorithm = new ConfigKey<>(String.class, "saml2.sigalg", "Advanced", "SHA1", "The algorithm to use to when signing a SAML request. Default is SHA1, allowed algorithms: SHA1, SHA256, SHA384, SHA512", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.Select, "SHA1,SHA256,SHA384,SHA512"); - public static final ConfigKey SAMLAppendDomainSuffix = new ConfigKey("Advanced", Boolean.class, "saml2.append.idpdomain", "false", + ConfigKey SAMLAppendDomainSuffix = new ConfigKey("Advanced", Boolean.class, "saml2.append.idpdomain", "false", "If enabled, create account/user dialog with SAML SSO enabled will append the IdP domain to the user or account name in the UI dialog", true); - public static final ConfigKey SAMLTimeout = new ConfigKey("Advanced", Integer.class, "saml2.timeout", "1800", + ConfigKey SAMLTimeout = new ConfigKey("Advanced", Integer.class, "saml2.timeout", "1800", "SAML2 IDP Metadata refresh interval in seconds, minimum value is set to 300", true); + ConfigKey SAMLCheckSignature = new ConfigKey("Advanced", Boolean.class, "saml2.check.signature", "false", + "Whether SAML2 signature must be checked, when enforced and when the SAML response does not have a signature would lead to login exception", true); + public SAMLProviderMetadata getSPMetadata(); public SAMLProviderMetadata getIdPMetadata(String entityId); public Collection getAllIdPMetadata(); diff --git a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java index 0e8790d6558..c4bd696003b 100644 --- a/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java +++ b/plugins/user-authenticators/saml2/src/main/java/org/apache/cloudstack/saml/SAML2AuthManagerImpl.java @@ -533,6 +533,6 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage SAMLServiceProviderSingleSignOnURL, SAMLServiceProviderSingleLogOutURL, SAMLCloudStackRedirectionUrl, SAMLUserAttributeName, SAMLIdentityProviderMetadataURL, SAMLDefaultIdentityProviderId, - SAMLSignatureAlgorithm, SAMLAppendDomainSuffix, SAMLTimeout}; + SAMLSignatureAlgorithm, SAMLAppendDomainSuffix, SAMLTimeout, SAMLCheckSignature}; } } diff --git a/plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java b/plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java index 39c8c231bf0..ebdc5be8fe9 100644 --- a/plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java +++ b/plugins/user-authenticators/saml2/src/test/java/org/apache/cloudstack/api/command/SAML2LoginAPIAuthenticatorCmdTest.java @@ -271,6 +271,30 @@ public class SAML2LoginAPIAuthenticatorCmdTest { verifyTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(false, hasThrownServerApiException, 0, 0); } + private void overrideDefaultConfigValue(final ConfigKey configKey, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException { + Field f = ConfigKey.class.getDeclaredField(name); + f.setAccessible(true); + f.set(configKey, o); + } + + @Test + public void testFailOnSAMLSignatureCheckWhenFalse() throws NoSuchFieldException, IllegalAccessException { + overrideDefaultConfigValue(SAML2AuthManager.SAMLCheckSignature, "_defaultValue", "false"); + SAML2LoginAPIAuthenticatorCmd cmd = new SAML2LoginAPIAuthenticatorCmd(); + try { + cmd.checkAndFailOnMissingSAMLSignature(null); + } catch(Exception e) { + Assert.fail("This shouldn't throw any exception"); + } + } + + @Test(expected = ServerApiException.class) + public void testFailOnSAMLSignatureCheckWhenTrue() throws NoSuchFieldException, IllegalAccessException { + overrideDefaultConfigValue(SAML2AuthManager.SAMLCheckSignature, "_defaultValue", "true"); + SAML2LoginAPIAuthenticatorCmd cmd = new SAML2LoginAPIAuthenticatorCmd(); + cmd.checkAndFailOnMissingSAMLSignature(null); + } + private UserAccountVO configureTestWhenFailToAuthenticateThrowExceptionOrRedirectToUrl(String entity, String configurationValue, Boolean isUserAuthorized) throws IOException { Mockito.when(samlAuthManager.isUserAuthorized(nullable(Long.class), nullable(String.class))).thenReturn(isUserAuthorized); diff --git a/pom.xml b/pom.xml index 5cd22870916..24247f50050 100644 --- a/pom.xml +++ b/pom.xml @@ -171,7 +171,7 @@ 10.1 2.6.6 0.6.0 - 0.3.0 + 0.5.1 0.10.2 3.4.4_1 4.0.1 diff --git a/server/src/main/java/com/cloud/event/ActionEventInterceptor.java b/server/src/main/java/com/cloud/event/ActionEventInterceptor.java index 30ff61b3e7f..ee025c825f5 100644 --- a/server/src/main/java/com/cloud/event/ActionEventInterceptor.java +++ b/server/src/main/java/com/cloud/event/ActionEventInterceptor.java @@ -70,7 +70,7 @@ public class ActionEventInterceptor implements ComponentMethodInterceptor, Metho if (async) { CallContext ctx = CallContext.current(); - String eventDescription = getEventDescription(actionEvent, ctx); + String eventDescription = getEventDescription(actionEvent, ctx, true); Long eventResourceId = getEventResourceId(actionEvent, ctx); String eventResourceType = getEventResourceType(actionEvent, ctx); String eventType = getEventType(actionEvent, ctx); @@ -183,19 +183,24 @@ public class ActionEventInterceptor implements ComponentMethodInterceptor, Metho return type == null ? actionEvent.eventType() : type; } - protected String getEventDescription(ActionEvent actionEvent, CallContext ctx) { + protected String getEventDescription(ActionEvent actionEvent, CallContext ctx, boolean capitalizeFirstLetter) { String eventDescription = ctx.getEventDescription(); if (eventDescription == null) { eventDescription = actionEvent.eventDescription(); } - if (ctx.getEventDetails() != null) { eventDescription += ". " + ctx.getEventDetails(); } - + if (capitalizeFirstLetter && StringUtils.isNotBlank(eventDescription)) { + eventDescription = eventDescription.substring(0, 1).toUpperCase() + eventDescription.substring(1); + } return eventDescription; } + protected String getEventDescription(ActionEvent actionEvent, CallContext ctx) { + return getEventDescription(actionEvent, ctx, false); + } + protected Long getEventResourceId(ActionEvent actionEvent, CallContext ctx) { Long resourceId = ctx.getEventResourceId(); if (resourceId != null) { diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index e618c17d3cf..0a55f75bbbd 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -205,6 +205,7 @@ import com.cloud.projects.Project; import com.cloud.projects.ProjectManager; import com.cloud.server.ResourceTag; import com.cloud.server.ResourceTag.ResourceObjectType; +import com.cloud.service.ServiceOfferingVO; import com.cloud.tags.ResourceTagVO; import com.cloud.tags.dao.ResourceTagDao; import com.cloud.user.Account; @@ -260,7 +261,6 @@ import com.cloud.vm.dao.NicSecondaryIpVO; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; import com.googlecode.ipv6.IPv6Address; -import com.cloud.service.ServiceOfferingVO; /** * NetworkServiceImpl implements NetworkService. @@ -1756,6 +1756,18 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } } + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_NETWORK_CREATE, eventDescription = "creating network") + public Network createGuestNetwork(long networkOfferingId, String name, String displayText, Account owner, + PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType) throws + InsufficientCapacityException, ConcurrentOperationException, ResourceAllocationException { + return _networkMgr.createGuestNetwork(networkOfferingId, name, displayText, + null, null, null, false, null, owner, null, physicalNetwork, zoneId, + aclType, null, null, null, null, true, null, + null, null, null, null, null, null, null, null, null); + } + void checkAndSetRouterSourceNatIp(Account owner, CreateNetworkCmd cmd, Network network) throws InsufficientAddressCapacityException, ResourceAllocationException { String sourceNatIp = cmd.getSourceNatIP(); if (sourceNatIp == null) { diff --git a/server/src/main/java/com/cloud/server/StatsCollector.java b/server/src/main/java/com/cloud/server/StatsCollector.java index f9ad0f51966..2c8b79500ba 100644 --- a/server/src/main/java/com/cloud/server/StatsCollector.java +++ b/server/src/main/java/com/cloud/server/StatsCollector.java @@ -276,12 +276,9 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc private static final ConfigKey statsOutputUri = new ConfigKey<>("Advanced", String.class, "stats.output.uri", "", "URI to send StatsCollector statistics to. The collector is defined on the URI scheme. Example: graphite://graphite-hostaddress:port or influxdb://influxdb-hostaddress/dbname. Note that the port is optional, if not added the default port for the respective collector (graphite or influxdb) will be used. Additionally, the database name '/dbname' is also optional; default db name is 'cloudstack'. You must create and configure the database if using influxdb.", true); - protected static ConfigKey vmStatsIncrementMetrics = new ConfigKey<>("Advanced", Boolean.class, "vm.stats.increment.metrics", "true", + protected static ConfigKey vmStatsIncrementMetrics = new ConfigKey<>("Advanced", Boolean.class, "vm.stats.increment.metrics", "false", "When set to 'true', VM metrics(NetworkReadKBs, NetworkWriteKBs, DiskWriteKBs, DiskReadKBs, DiskReadIOs and DiskWriteIOs) that are collected from the hypervisor are summed before being returned." + "On the other hand, when set to 'false', the VM metrics API will just display the latest metrics collected.", true); - private static final ConfigKey VM_STATS_INCREMENT_METRICS_IN_MEMORY = new ConfigKey<>("Advanced", Boolean.class, "vm.stats.increment.metrics.in.memory", "true", - "When set to 'true', VM metrics(NetworkReadKBs, NetworkWriteKBs, DiskWriteKBs, DiskReadKBs, DiskReadIOs and DiskWriteIOs) that are collected from the hypervisor are summed and stored in memory. " - + "On the other hand, when set to 'false', the VM metrics API will just display the latest metrics collected.", true); protected static ConfigKey vmStatsMaxRetentionTime = new ConfigKey<>("Advanced", Integer.class, "vm.stats.max.retention.time", "720", "The maximum time (in minutes) for keeping VM stats records in the database. The VM stats cleanup process will be disabled if this is set to 0 or less than 0.", true); @@ -2129,7 +2126,6 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc public ConfigKey[] getConfigKeys() { return new ConfigKey[] {vmDiskStatsInterval, vmDiskStatsIntervalMin, vmNetworkStatsInterval, vmNetworkStatsIntervalMin, StatsTimeout, statsOutputUri, vmStatsIncrementMetrics, vmStatsMaxRetentionTime, vmStatsCollectUserVMOnly, vmDiskStatsRetentionEnabled, vmDiskStatsMaxRetentionTime, - VM_STATS_INCREMENT_METRICS_IN_MEMORY, MANAGEMENT_SERVER_STATUS_COLLECTION_INTERVAL, DATABASE_SERVER_STATUS_COLLECTION_INTERVAL, DATABASE_SERVER_LOAD_HISTORY_RETENTION_NUMBER}; diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 72e9d85e25a..0963fabf233 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -54,7 +54,7 @@ import javax.naming.ConfigurationException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.ParserConfigurationException; -import com.cloud.kubernetes.cluster.KubernetesClusterHelper; +import com.cloud.kubernetes.cluster.KubernetesServiceHelper; import com.cloud.network.dao.NsxProviderDao; import com.cloud.network.element.NsxProviderVO; import com.cloud.user.AccountVO; @@ -611,7 +611,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir private boolean _dailyOrHourly = false; private int capacityReleaseInterval; private ExecutorService _vmIpFetchThreadExecutor; - private List kubernetesClusterHelpers; + private List kubernetesClusterHelpers; private String _instance; @@ -625,11 +625,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir private static final int NUM_OF_2K_BLOCKS = 512; private static final int MAX_HTTP_POST_LENGTH = NUM_OF_2K_BLOCKS * MAX_USER_DATA_LENGTH_BYTES; - public List getKubernetesClusterHelpers() { + public List getKubernetesClusterHelpers() { return kubernetesClusterHelpers; } - public void setKubernetesClusterHelpers(final List kubernetesClusterHelpers) { + public void setKubernetesClusterHelpers(final List kubernetesClusterHelpers) { this.kubernetesClusterHelpers = kubernetesClusterHelpers; } @@ -3261,6 +3261,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return startVirtualMachine(cmd.getId(), cmd.getPodId(), cmd.getClusterId(), cmd.getHostId(), additonalParams, cmd.getDeploymentPlanner()).first(); } + @Override + @ActionEvent(eventType = EventTypes.EVENT_VM_START, eventDescription = "starting Vm", async = true) + public void startVirtualMachine(UserVm vm) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException { + _itMgr.advanceStart(vm.getUuid(), null, null); + } + @Override @ActionEvent(eventType = EventTypes.EVENT_VM_REBOOT, eventDescription = "rebooting Vm", async = true) public UserVm rebootVirtualMachine(RebootVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ResourceAllocationException { @@ -3317,9 +3323,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir protected void checkPluginsIfVmCanBeDestroyed(UserVm vm) { try { - KubernetesClusterHelper kubernetesClusterHelper = - ComponentContext.getDelegateComponentOfType(KubernetesClusterHelper.class); - kubernetesClusterHelper.checkVmCanBeDestroyed(vm); + KubernetesServiceHelper kubernetesServiceHelper = + ComponentContext.getDelegateComponentOfType(KubernetesServiceHelper.class); + kubernetesServiceHelper.checkVmCanBeDestroyed(vm); } catch (NoSuchBeanDefinitionException ignored) { logger.debug("No KubernetesClusterHelper bean found"); } @@ -5508,7 +5514,12 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir final ServiceOfferingVO offering = serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId()); Pair cpuCapabilityAndCapacity = _capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(destinationHost, offering, false); if (!cpuCapabilityAndCapacity.first() || !cpuCapabilityAndCapacity.second()) { - String errorMsg = "Cannot deploy the VM to specified host " + hostId + "; host has cpu capability? " + cpuCapabilityAndCapacity.first() + ", host has capacity? " + cpuCapabilityAndCapacity.second(); + String errorMsg; + if (!cpuCapabilityAndCapacity.first()) { + errorMsg = String.format("Cannot deploy the VM to specified host %d, requested CPU and speed is more than the host capability", hostId); + } else { + errorMsg = String.format("Cannot deploy the VM to specified host %d, host does not have enough free CPU or RAM, please check the logs", hostId); + } logger.info(errorMsg); if (!AllowDeployVmIfGivenHostFails.value()) { throw new InvalidParameterValueException(errorMsg); diff --git a/server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java b/server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java index 6975ecbef66..a5f7a1b8002 100644 --- a/server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/annotation/AnnotationManagerImpl.java @@ -64,7 +64,7 @@ import com.cloud.event.EventTypes; import com.cloud.exception.PermissionDeniedException; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; -import com.cloud.kubernetes.cluster.KubernetesClusterHelper; +import com.cloud.kubernetes.cluster.KubernetesServiceHelper; import com.cloud.network.as.dao.AutoScaleVmGroupDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.NetworkDao; @@ -163,7 +163,7 @@ public final class AnnotationManagerImpl extends ManagerBase implements Annotati EntityManager entityManager; private static final List adminRoles = Collections.singletonList(RoleType.Admin); - private List kubernetesClusterHelpers; + private List kubernetesServiceHelpers; public static final Map s_typeMap = new HashMap<>(); static { @@ -198,12 +198,12 @@ public final class AnnotationManagerImpl extends ManagerBase implements Annotati s_typeMap.put(EntityType.OBJECT_STORAGE, ApiCommandResourceType.ObjectStore); } - public List getKubernetesClusterHelpers() { - return kubernetesClusterHelpers; + public List getKubernetesServiceHelpers() { + return kubernetesServiceHelpers; } - public void setKubernetesClusterHelpers(final List kubernetesClusterHelpers) { - this.kubernetesClusterHelpers = kubernetesClusterHelpers; + public void setKubernetesServiceHelpers(final List kubernetesServiceHelpers) { + this.kubernetesServiceHelpers = kubernetesServiceHelpers; } @Override @@ -533,7 +533,7 @@ public final class AnnotationManagerImpl extends ManagerBase implements Annotati case ISO: return templateDao.findByUuid(entityUuid); case KUBERNETES_CLUSTER: - return kubernetesClusterHelpers.get(0).findByUuid(entityUuid); + return kubernetesServiceHelpers.get(0).findByUuid(entityUuid); case AUTOSCALE_VM_GROUP: return autoScaleVmGroupDao.findByUuid(entityUuid); case MANAGEMENT_SERVER: diff --git a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java index 2a2427c9795..73144ac2966 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImpl.java @@ -2479,11 +2479,7 @@ public class UnmanagedVMsManagerImpl implements UnmanagedVMsManager { throw new InvalidParameterValueException("Please specify a valid zone."); } final String hypervisorType = cmd.getHypervisor(); - if (Hypervisor.HypervisorType.KVM.toString().equalsIgnoreCase(hypervisorType)) { - if (StringUtils.isBlank(cmd.getUsername())) { - throw new InvalidParameterValueException("Username need to be provided."); - } - } else { + if (!Hypervisor.HypervisorType.KVM.toString().equalsIgnoreCase(hypervisorType)) { throw new InvalidParameterValueException(String.format("VM Import is currently not supported for hypervisor: %s", hypervisorType)); } 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 30b1a681968..2a33d0d4a97 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 @@ -322,7 +322,7 @@ - + diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java index 106fc7fa543..7007ad6b33f 100644 --- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java @@ -222,6 +222,13 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches return null; } + @Override + public Network createGuestNetwork(long networkOfferingId, String name, String displayText, Account owner, + PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType) throws InsufficientCapacityException, + ConcurrentOperationException, ResourceAllocationException { + return null; + } + /* (non-Javadoc) * @see com.cloud.network.NetworkService#searchForNetworks(com.cloud.api.commands.ListNetworksCmd) */ diff --git a/test/integration/smoke/test_register_userdata.py b/test/integration/smoke/test_register_userdata.py index c89d08e63e8..f19b5abc9cf 100644 --- a/test/integration/smoke/test_register_userdata.py +++ b/test/integration/smoke/test_register_userdata.py @@ -28,7 +28,7 @@ from marvin.lib.base import (ServiceOffering, NATRule, Template) from marvin.lib.common import get_test_template, get_zone, list_virtual_machines -from marvin.lib.utils import (validateList, cleanup_resources) +from marvin.lib.utils import validateList from nose.plugins.attrib import attr from marvin.codes import PASS,FAIL import base64 @@ -87,6 +87,7 @@ class TestRegisteredUserdata(cloudstackTestCase): # Get Zone, Domain and Default Built-in template self.domain = get_domain(self.apiclient) self.zone = get_zone(self.apiclient, self.testClient.getZoneForTests()) + self.hypervisor = self.testClient.getHypervisorInfo() #create a user account self.account = Account.create( @@ -96,7 +97,7 @@ class TestRegisteredUserdata(cloudstackTestCase): ) self.testdata["mode"] = self.zone.networktype - self.template = get_template(self.apiclient, self.zone.id, self.testdata["ostype"]) + self.template = get_test_template(self.apiclient, self.zone.id, self.hypervisor) #create a service offering small_service_offering = self.testdata["service_offerings"]["small"] @@ -115,25 +116,21 @@ class TestRegisteredUserdata(cloudstackTestCase): self.testdata["network"], networkofferingid=self.no_isolate.id, zoneid=self.zone.id, - accountid="admin", - domainid=1 + accountid=self.account.name, + domainid=self.account.domainid ) #build cleanup list self.cleanup = [ + self.account, + self.no_isolate, self.service_offering, self.isolated_network, - self.no_isolate, - self.account, ] def tearDown(self): - try: - cleanup_resources(self.apiclient, self.cleanup) - except Exception as e: - self.debug("Warning! Exception in tearDown: %s" % e) - + super(TestRegisteredUserdata, self).tearDown() @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False) def test_CRUD_operations_userdata(self): @@ -192,22 +189,21 @@ class TestRegisteredUserdata(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], zoneid=self.zone.id, - accountid="admin", - domainid=1, + accountid=self.account.name, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, templateid=self.template.id, networkids=[self.isolated_network.id], userdataid=self.userdata2.userdata.id ) self.cleanup.append(self.virtual_machine) - self.cleanup.append(self.userdata2) networkid = self.virtual_machine.nic[0].networkid src_nat_list = PublicIPAddress.list( self.apiclient, associatednetworkid=networkid, - account="admin", - domainid=1, + account=self.account.name, + domainid=self.account.domainid, listall=True, issourcenat=True, ) @@ -320,8 +316,8 @@ class TestRegisteredUserdata(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], zoneid=self.zone.id, - accountid="admin", - domainid=1, + accountid=self.account.name, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, templateid=self.template.id, networkids=[self.isolated_network.id], @@ -329,14 +325,13 @@ class TestRegisteredUserdata(cloudstackTestCase): userdatadetails=[{"key1": "value1"}] ) self.cleanup.append(self.virtual_machine) - self.cleanup.append(self.userdata2) networkid = self.virtual_machine.nic[0].networkid src_nat_list = PublicIPAddress.list( self.apiclient, associatednetworkid=networkid, - account="admin", - domainid=1, + account=self.account.name, + domainid=self.account.domainid, listall=True, issourcenat=True, ) @@ -493,15 +488,14 @@ class TestRegisteredUserdata(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], zoneid=self.zone.id, - accountid="admin", - domainid=1, + accountid=self.account.name, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, templateid=self.template.id, networkids=[self.isolated_network.id], userdataid=self.apiUserdata.userdata.id ) self.cleanup.append(self.virtual_machine) - self.cleanup.append(self.apiUserdata) self.template = Template.linkUserDataToTemplate( self.apiclient, @@ -512,8 +506,8 @@ class TestRegisteredUserdata(cloudstackTestCase): src_nat_list = PublicIPAddress.list( self.apiclient, associatednetworkid=networkid, - account="admin", - domainid=1, + account=self.account.name, + domainid=self.account.domainid, listall=True, issourcenat=True, ) @@ -623,16 +617,14 @@ class TestRegisteredUserdata(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], zoneid=self.zone.id, - accountid="admin", - domainid=1, + accountid=self.account.name, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, templateid=self.template.id, networkids=[self.isolated_network.id], userdataid=self.apiUserdata.userdata.id ) self.cleanup.append(self.virtual_machine) - self.cleanup.append(self.apiUserdata) - self.cleanup.append(self.templateUserdata) self.template = Template.linkUserDataToTemplate( self.apiclient, @@ -643,8 +635,8 @@ class TestRegisteredUserdata(cloudstackTestCase): src_nat_list = PublicIPAddress.list( self.apiclient, associatednetworkid=networkid, - account="admin", - domainid=1, + account=self.account.name, + domainid=self.account.domainid, listall=True, issourcenat=True, ) @@ -770,8 +762,8 @@ class TestRegisteredUserdata(cloudstackTestCase): self.apiclient, self.services["virtual_machine"], zoneid=self.zone.id, - accountid="admin", - domainid=1, + accountid=self.account.name, + domainid=self.account.domainid, serviceofferingid=self.service_offering.id, templateid=self.template.id, networkids=[self.isolated_network.id], @@ -781,9 +773,6 @@ class TestRegisteredUserdata(cloudstackTestCase): self.debug("Deploy VM with userdata passed during deployment failed as expected because template userdata override policy is deny. Exception here is : %s" % e.exception) - self.cleanup.append(self.apiUserdata) - self.cleanup.append(self.templateUserdata) - self.template = Template.linkUserDataToTemplate( self.apiclient, templateid=self.template.id @@ -809,7 +798,6 @@ class TestRegisteredUserdata(cloudstackTestCase): account=self.account.name, domainid=self.account.domainid ) - self.cleanup.append(self.userdata) list_userdata = UserData.list(self.apiclient, id=self.userdata.userdata.id, listall=True) self.assertNotEqual( @@ -853,4 +841,3 @@ class TestRegisteredUserdata(cloudstackTestCase): self.userapiclient, id=self.userdata.userdata.id ) - self.cleanup.remove(self.userdata) diff --git a/tools/marvin/mvn-setup.py b/tools/marvin/mvn-setup.py index 61bd2219238..cabcf0dc659 100755 --- a/tools/marvin/mvn-setup.py +++ b/tools/marvin/mvn-setup.py @@ -34,6 +34,8 @@ def replaceVersion(fname, version): with open(fname, 'r') as f: content = f.read() needle = '\nVERSION\s*=\s*[\'"][^\'"]*[\'"]' + # Ensure the version is PEP440 compliant + version = version.replace('-', '+', 1) replacement = '\nVERSION = "%s"' % version content = re.sub(needle, replacement, content, 1) with open(fname, 'w') as f: diff --git a/ui/src/components/widgets/ResourceLabel.vue b/ui/src/components/widgets/ResourceLabel.vue index 6e44ce54a5d..0a10843ff86 100644 --- a/ui/src/components/widgets/ResourceLabel.vue +++ b/ui/src/components/widgets/ResourceLabel.vue @@ -18,7 +18,13 @@ - + - + - + - + - + - + - + - + - +