Merge branch '4.19'

This commit is contained in:
Daan Hoogland 2024-06-14 10:30:10 +02:00
commit cb9b3134f7
66 changed files with 1119 additions and 329 deletions

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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<List<? extends Network>, Integer> searchForNetworks(ListNetworksCmd cmd);
boolean deleteNetwork(long networkId, boolean forced);

View File

@ -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

View File

@ -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<ApiCommandResourceType, Class<?>> 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);
}
}

View File

@ -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'.")

View File

@ -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<String> viewDetails;
@Parameter(name = ApiConstants.TEMPLATE_ID, type = CommandType.UUID, entityType = TemplateResponse.class, description = "list vms by template")

View File

@ -125,9 +125,8 @@ public interface QueryService {
static final ConfigKey<Boolean> 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<Boolean> 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<Boolean> 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<UserResponse> searchForUsers(ListUsersCmd cmd) throws PermissionDeniedException;

View File

@ -339,7 +339,7 @@
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
</bean>
<bean id="kubernetesClusterHelperRegistry"
<bean id="kubernetesServiceHelperRegistry"
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
</bean>

View File

@ -25,8 +25,8 @@
>
<bean class="org.apache.cloudstack.spring.lifecycle.registry.RegistryLifecycle">
<property name="registry" ref="kubernetesClusterHelperRegistry" />
<property name="typeClass" value="com.cloud.kubernetes.cluster.KubernetesClusterHelper" />
<property name="registry" ref="kubernetesServiceHelperRegistry" />
<property name="typeClass" value="com.cloud.kubernetes.cluster.KubernetesServiceHelper" />
</bean>
</beans>

View File

@ -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<VolumeApiResult> future = volService.expungeVolumeAsync(volFactory.getVolume(existingVolume.getId()));
try {
future.get();

View File

@ -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';

View File

@ -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`)

View File

@ -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<String, String>(null, null)).when(convertInstanceCommandWrapper).getNfsStoragePoolHostAndPath(destinationPool);
List<UnmanagedInstanceTO.Disk> unmanagedInstanceDisks = convertInstanceCommandWrapper.getUnmanagedInstanceDisks(disks, parser);
Assert.assertEquals(1, unmanagedInstanceDisks.size());

View File

@ -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<KubernetesClusterVO> kubernetesClusters = kubernetesClusterDao.search(sc, searchFilter);
for (KubernetesClusterVO cluster : kubernetesClusters) {
Pair<List<KubernetesClusterVO>, Integer> kubernetesClustersAndCount = kubernetesClusterDao.searchAndCount(sc, searchFilter);
for (KubernetesClusterVO cluster : kubernetesClustersAndCount.first()) {
KubernetesClusterResponse clusterResponse = createKubernetesClusterResponse(cluster.getId());
responsesList.add(clusterResponse);
}
ListResponse<KubernetesClusterResponse> response = new ListResponse<KubernetesClusterResponse>();
response.setResponses(responsesList);
ListResponse<KubernetesClusterResponse> 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");

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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<UserVm> 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()) {

View File

@ -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();
}
}
}

View File

@ -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<UserVm> 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<Long> 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();
}
}

View File

@ -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();

View File

@ -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) {

View File

@ -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<KubernetesSupportedVersionResponse> createKubernetesSupportedVersionListResponse(List<KubernetesSupportedVersionVO> versions) {
private ListResponse<KubernetesSupportedVersionResponse> createKubernetesSupportedVersionListResponse(
List<KubernetesSupportedVersionVO> versions, Integer count) {
List<KubernetesSupportedVersionResponse> responseList = new ArrayList<>();
for (KubernetesSupportedVersionVO version : versions) {
responseList.add(createKubernetesSupportedVersionResponse(version));
}
ListResponse<KubernetesSupportedVersionResponse> 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 <KubernetesSupportedVersionVO> versions = kubernetesSupportedVersionDao.search(sc, searchFilter);
versions = filterKubernetesSupportedVersions(versions, minimumSemanticVersion);
Pair<List<KubernetesSupportedVersionVO>, Integer> versionsAndCount =
kubernetesSupportedVersionDao.searchAndCount(sc, searchFilter);
List<KubernetesSupportedVersionVO> 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");

View File

@ -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///////////////////
/////////////////////////////////////////////////////

View File

@ -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///////////////////
/////////////////////////////////////////////////////

View File

@ -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();
}
/////////////////////////////////////////////////////

View File

@ -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);

View File

@ -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";

View File

@ -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 {

View File

@ -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///////////////////
/////////////////////////////////////////////////////

View File

@ -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) {

View File

@ -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///////////////////
/////////////////////////////////////////////////////

View File

@ -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///////////////////
/////////////////////////////////////////////////////

View File

@ -34,8 +34,8 @@
<bean id="kubernetesClusterVmMapDaoImpl" class="com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDaoImpl" />
<bean id="kubernetesClusterManagerImpl" class="com.cloud.kubernetes.cluster.KubernetesClusterManagerImpl" />
<bean id="kubernetesClusterHelper" class="com.cloud.kubernetes.cluster.KubernetesClusterHelperImpl" >
<property name="name" value="KubernetesClusterHelper" />
<bean id="kubernetesServiceHelper" class="com.cloud.kubernetes.cluster.KubernetesServiceHelperImpl" >
<property name="name" value="KubernetesServiceHelper" />
</bean>
</beans>

View File

@ -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);
}
}

View File

@ -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<KubernetesSupportedVersionResponse> 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<KubernetesSupportedVersionResponse> 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<ComponentContext> mockedComponentContext = Mockito.mockStatic(ComponentContext.class)) {
CallContext callContext = Mockito.mock(CallContext.class);
try (MockedStatic<ComponentContext> mockedComponentContext = Mockito.mockStatic(ComponentContext.class);
MockedStatic<CallContext> 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));

View File

@ -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<RemoteHostEndPoint> 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<RemoteHostEndPoint> 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());
}

View File

@ -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
);
}

View File

@ -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<VolumeObjectTO> 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<VolumeObjectTO> 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<VolumeObjectTO> 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<VolumeObjectTO> 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<VolumeObjectTO> 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<String> 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<VolumeObjectTO> 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);
}
}
}

View File

@ -29,6 +29,8 @@
<bean id="linstorPrimaryDataStoreProviderImpl"
class="org.apache.cloudstack.storage.datastore.provider.LinstorPrimaryDatastoreProviderImpl" />
<bean id="linstorSnapshotStrategy"
class="org.apache.cloudstack.storage.snapshot.LinstorVMSnapshotStrategy" />
<bean id="linstorConfigManager"
class="org.apache.cloudstack.storage.datastore.util.LinstorConfigurationManager" />
</beans>

View File

@ -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<String, Object[]> 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());

View File

@ -25,51 +25,54 @@ import java.util.Collection;
public interface SAML2AuthManager extends PluggableAPIAuthenticator, PluggableService {
public static final ConfigKey<Boolean> SAMLIsPluginEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class, "saml2.enabled", "false",
ConfigKey<Boolean> SAMLIsPluginEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class, "saml2.enabled", "false",
"Indicates whether SAML SSO plugin is enabled or not", true);
public static final ConfigKey<String> SAMLServiceProviderID = new ConfigKey<String>("Advanced", String.class, "saml2.sp.id", "org.apache.cloudstack",
ConfigKey<String> SAMLServiceProviderID = new ConfigKey<String>("Advanced", String.class, "saml2.sp.id", "org.apache.cloudstack",
"SAML2 Service Provider Identifier String", true);
public static final ConfigKey<String> SAMLServiceProviderContactPersonName = new ConfigKey<String>("Advanced", String.class, "saml2.sp.contact.person", "CloudStack Developers",
ConfigKey<String> SAMLServiceProviderContactPersonName = new ConfigKey<String>("Advanced", String.class, "saml2.sp.contact.person", "CloudStack Developers",
"SAML2 Service Provider Contact Person Name", true);
public static final ConfigKey<String> SAMLServiceProviderContactEmail = new ConfigKey<String>("Advanced", String.class, "saml2.sp.contact.email", "dev@cloudstack.apache.org",
ConfigKey<String> SAMLServiceProviderContactEmail = new ConfigKey<String>("Advanced", String.class, "saml2.sp.contact.email", "dev@cloudstack.apache.org",
"SAML2 Service Provider Contact Email Address", true);
public static final ConfigKey<String> SAMLServiceProviderOrgName = new ConfigKey<String>("Advanced", String.class, "saml2.sp.org.name", "Apache CloudStack",
ConfigKey<String> SAMLServiceProviderOrgName = new ConfigKey<String>("Advanced", String.class, "saml2.sp.org.name", "Apache CloudStack",
"SAML2 Service Provider Organization Name", true);
public static final ConfigKey<String> SAMLServiceProviderOrgUrl = new ConfigKey<String>("Advanced", String.class, "saml2.sp.org.url", "http://cloudstack.apache.org",
ConfigKey<String> SAMLServiceProviderOrgUrl = new ConfigKey<String>("Advanced", String.class, "saml2.sp.org.url", "http://cloudstack.apache.org",
"SAML2 Service Provider Organization URL", true);
public static final ConfigKey<String> SAMLServiceProviderSingleSignOnURL = new ConfigKey<String>("Advanced", String.class, "saml2.sp.sso.url", "http://localhost:8080/client/api?command=samlSso",
ConfigKey<String> SAMLServiceProviderSingleSignOnURL = new ConfigKey<String>("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<String> SAMLServiceProviderSingleLogOutURL = new ConfigKey<String>("Advanced", String.class, "saml2.sp.slo.url", "http://localhost:8080/client/",
ConfigKey<String> SAMLServiceProviderSingleLogOutURL = new ConfigKey<String>("Advanced", String.class, "saml2.sp.slo.url", "http://localhost:8080/client/",
"SAML2 CloudStack Service Provider Single Log Out URL", true);
public static final ConfigKey<String> SAMLCloudStackRedirectionUrl = new ConfigKey<String>("Advanced", String.class, "saml2.redirect.url", "http://localhost:8080/client",
ConfigKey<String> SAMLCloudStackRedirectionUrl = new ConfigKey<String>("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<String> SAMLUserAttributeName = new ConfigKey<String>("Advanced", String.class, "saml2.user.attribute", "uid",
ConfigKey<String> SAMLUserAttributeName = new ConfigKey<String>("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<String> SAMLIdentityProviderMetadataURL = new ConfigKey<String>("Advanced", String.class, "saml2.idp.metadata.url", "https://openidp.feide.no/simplesaml/saml2/idp/metadata.php",
ConfigKey<String> SAMLIdentityProviderMetadataURL = new ConfigKey<String>("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<String> SAMLDefaultIdentityProviderId = new ConfigKey<String>("Advanced", String.class, "saml2.default.idpid", "https://openidp.feide.no",
ConfigKey<String> SAMLDefaultIdentityProviderId = new ConfigKey<String>("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<String> SAMLSignatureAlgorithm = new ConfigKey<>(String.class, "saml2.sigalg", "Advanced", "SHA1",
ConfigKey<String> 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<Boolean> SAMLAppendDomainSuffix = new ConfigKey<Boolean>("Advanced", Boolean.class, "saml2.append.idpdomain", "false",
ConfigKey<Boolean> SAMLAppendDomainSuffix = new ConfigKey<Boolean>("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<Integer> SAMLTimeout = new ConfigKey<Integer>("Advanced", Integer.class, "saml2.timeout", "1800",
ConfigKey<Integer> SAMLTimeout = new ConfigKey<Integer>("Advanced", Integer.class, "saml2.timeout", "1800",
"SAML2 IDP Metadata refresh interval in seconds, minimum value is set to 300", true);
ConfigKey<Boolean> SAMLCheckSignature = new ConfigKey<Boolean>("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<SAMLProviderMetadata> getAllIdPMetadata();

View File

@ -533,6 +533,6 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
SAMLServiceProviderSingleSignOnURL, SAMLServiceProviderSingleLogOutURL,
SAMLCloudStackRedirectionUrl, SAMLUserAttributeName,
SAMLIdentityProviderMetadataURL, SAMLDefaultIdentityProviderId,
SAMLSignatureAlgorithm, SAMLAppendDomainSuffix, SAMLTimeout};
SAMLSignatureAlgorithm, SAMLAppendDomainSuffix, SAMLTimeout, SAMLCheckSignature};
}
}

View File

@ -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);

View File

@ -171,7 +171,7 @@
<cs.nitro.version>10.1</cs.nitro.version>
<cs.opensaml.version>2.6.6</cs.opensaml.version>
<cs.rados-java.version>0.6.0</cs.rados-java.version>
<cs.java-linstor.version>0.3.0</cs.java-linstor.version>
<cs.java-linstor.version>0.5.1</cs.java-linstor.version>
<cs.reflections.version>0.10.2</cs.reflections.version>
<cs.servicemix.version>3.4.4_1</cs.servicemix.version>
<cs.servlet.version>4.0.1</cs.servlet.version>

View File

@ -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) {

View File

@ -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) {

View File

@ -276,12 +276,9 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc
private static final ConfigKey<String> 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<Boolean> vmStatsIncrementMetrics = new ConfigKey<>("Advanced", Boolean.class, "vm.stats.increment.metrics", "true",
protected static ConfigKey<Boolean> 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<Boolean> 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<Integer> 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};

View File

@ -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<KubernetesClusterHelper> kubernetesClusterHelpers;
private List<KubernetesServiceHelper> 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<KubernetesClusterHelper> getKubernetesClusterHelpers() {
public List<KubernetesServiceHelper> getKubernetesClusterHelpers() {
return kubernetesClusterHelpers;
}
public void setKubernetesClusterHelpers(final List<KubernetesClusterHelper> kubernetesClusterHelpers) {
public void setKubernetesClusterHelpers(final List<KubernetesServiceHelper> 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<Boolean, Boolean> 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);

View File

@ -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<RoleType> adminRoles = Collections.singletonList(RoleType.Admin);
private List<KubernetesClusterHelper> kubernetesClusterHelpers;
private List<KubernetesServiceHelper> kubernetesServiceHelpers;
public static final Map<EntityType, ApiCommandResourceType> 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<KubernetesClusterHelper> getKubernetesClusterHelpers() {
return kubernetesClusterHelpers;
public List<KubernetesServiceHelper> getKubernetesServiceHelpers() {
return kubernetesServiceHelpers;
}
public void setKubernetesClusterHelpers(final List<KubernetesClusterHelper> kubernetesClusterHelpers) {
this.kubernetesClusterHelpers = kubernetesClusterHelpers;
public void setKubernetesServiceHelpers(final List<KubernetesServiceHelper> 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:

View File

@ -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));
}

View File

@ -322,7 +322,7 @@
</bean>
<bean id="annotationService" class="org.apache.cloudstack.annotation.AnnotationManagerImpl">
<property name="kubernetesClusterHelpers" value="#{kubernetesClusterHelperRegistry.registered}" />
<property name="kubernetesServiceHelpers" value="#{kubernetesServiceHelperRegistry.registered}" />
</bean>
<bean id="indirectAgentLBService" class="org.apache.cloudstack.agent.lb.IndirectAgentLBServiceImpl" />

View File

@ -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)
*/

View File

@ -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)

View File

@ -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:

View File

@ -18,7 +18,13 @@
<template>
<div v-if="resourceType && resourceId" >
<a-tooltip v-if="resourceIcon" placement="top" :title="resourceIconTooltip">
<render-icon style="font-size: 16px; margin-right: 5px" :icon="resourceIcon" />
<font-awesome-icon
v-if="resourceIcon && Array.isArray(resourceIcon)"
:icon="resourceIcon"
size="1x"
class="anticon"
:style="[$store.getters.darkMode ? { color: 'rgba(255, 255, 255, 0.65)' } : { color: '#888' }]" />
<render-icon v-else style="font-size: 16px; margin-right: 5px" :icon="resourceIcon" />
</a-tooltip>
<a-tag v-else>{{ resourceType }}</a-tag>
<router-link v-if="resourceRoute && $router.resolve(resourceRoute)" :to="{ path: resourceRoute }">{{ resourceName || resourceId }}</router-link>
@ -67,3 +73,12 @@ export default {
}
}
</script>
<style lang="scss" scoped>
.anticon {
margin-right: 5px;
vertical-align: center;
}
</style>

View File

@ -32,9 +32,9 @@ export default {
getApiToCall: () => store.getters.metrics ? 'listVirtualMachinesMetrics' : 'listVirtualMachines',
resourceType: 'UserVm',
params: () => {
var params = { details: 'servoff,tmpl,iso,nics,backoff' }
var params = { details: 'group,nics,secgrp,tmpl,servoff,diskoff,iso,volume,affgrp' }
if (store.getters.metrics) {
params = { details: 'servoff,tmpl,iso,nics,backoff,stats' }
params = { details: 'all,stats' }
}
params.isvnf = false
return params
@ -542,10 +542,12 @@ export default {
return filters
},
details: ['name', 'description', 'zonename', 'kubernetesversionname', 'autoscalingenabled', 'minsize', 'maxsize', 'size', 'controlnodes', 'cpunumber', 'memory', 'keypair', 'associatednetworkname', 'account', 'domain', 'zonename', 'clustertype', 'created'],
tabs: [{
name: 'k8s',
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/KubernetesServiceTab.vue')))
}],
tabs: [
{
name: 'k8s',
component: shallowRef(defineAsyncComponent(() => import('@/views/compute/KubernetesServiceTab.vue')))
}
],
resourceType: 'KubernetesCluster',
actions: [
{

View File

@ -370,6 +370,18 @@ export default {
searchFilters: ['zoneid', 'minimumsemanticversion'],
columns: ['name', 'state', 'semanticversion', 'isostate', 'mincpunumber', 'minmemory', 'zonename'],
details: ['name', 'semanticversion', 'supportsautoscaling', 'zoneid', 'zonename', 'isoid', 'isoname', 'isostate', 'mincpunumber', 'minmemory', 'supportsha', 'state', 'created'],
tabs: [
{
name: 'details',
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
},
{
name: 'events',
resourceType: 'KubernetesSupportedVersion',
component: shallowRef(defineAsyncComponent(() => import('@/components/view/EventsTab.vue'))),
show: () => { return 'listEvents' in store.getters.apis }
}
],
actions: [
{
api: 'addKubernetesSupportedVersion',

View File

@ -390,6 +390,10 @@ export const resourceTypePlugin = {
return 'publicip'
case 'NetworkAcl':
return 'acllist'
case 'KubernetesCluster':
return 'kubernetes'
case 'KubernetesSupportedVersion':
return 'kubernetesiso'
case 'SystemVm':
case 'PhysicalNetwork':
case 'Backup':

View File

@ -23,13 +23,19 @@ function filterNumber (value) {
}
function stringComparator (a, b) {
return a.toString().localeCompare(b.toString())
return a.toString().localeCompare(b.toString(), undefined, { numeric: true })
}
function numericComparator (a, b) {
return filterNumber(a) < filterNumber(b) ? 1 : -1
}
function metricComparator (ma, mb) {
var a = ('' + ma).replace(/((%)|(Ghz)|(Mhz)|(MiB)|(GiB)|(GB)).*$/g, '')
var b = ('' + mb).replace(/((%)|(Ghz)|(Mhz)|(MiB)|(GiB)|(GB)).*$/g, '')
return parseFloat(a) < parseFloat(b) ? 1 : -1
}
function ipV4AddressCIDRComparator (a, b) {
a = a.split(/[./]/gm)
b = b.split(/[./]/gm)
@ -76,6 +82,10 @@ function isNumeric (obj) {
return !Array.isArray(obj) && !isNaN(filterNumber(obj))
}
function isMetric (value) {
return /^[0-9\\. ]*((%)|(Ghz)|(Mhz)|(MiB)|(GiB)|(GB))/.test(value)
}
/**
* Compare elements, attempting to determine type of element to get the best comparison
*
@ -97,6 +107,9 @@ export function genericCompare (a, b) {
if (isNumeric(a) && isNumeric(b)) {
comparator = numericComparator
}
if (isMetric(a) && isMetric(b)) {
comparator = metricComparator
}
return comparator(a, b)
}

View File

@ -904,6 +904,7 @@ export default {
if (['listVirtualMachinesMetrics'].includes(this.apiName) && this.dataView) {
delete params.details
delete params.isvnf
params.details = 'group,nics,secgrp,tmpl,servoff,diskoff,iso,volume,affgrp'
}
this.loading = true

View File

@ -149,6 +149,9 @@
<a-tab-pane :tab="$t('label.loadbalancing')" key="loadbalancing" v-if="publicIpAddress">
<LoadBalancing :resource="publicIpAddress" :loading="networkLoading" />
</a-tab-pane>
<a-tab-pane :tab="$t('label.events')" key="events" v-if="'listEvents' in $store.getters.apis">
<events-tab :resource="resource" resourceType="KubernetesCluster" :loading="loading" />
</a-tab-pane>
<a-tab-pane :tab="$t('label.annotations')" key="comments" v-if="'listAnnotations' in $store.getters.apis">
<AnnotationsTab
:resource="resource"
@ -169,6 +172,7 @@ import PortForwarding from '@/views/network/PortForwarding'
import LoadBalancing from '@/views/network/LoadBalancing'
import Status from '@/components/widgets/Status'
import AnnotationsTab from '@/components/view/AnnotationsTab'
import EventsTab from '@/components/view/EventsTab'
export default {
name: 'KubernetesServiceTab',
@ -178,7 +182,8 @@ export default {
PortForwarding,
LoadBalancing,
Status,
AnnotationsTab
AnnotationsTab,
EventsTab
},
mixins: [mixinDevice],
inject: ['parentFetchData'],

View File

@ -99,6 +99,14 @@ export default {
type: String,
default: () => ''
},
domainid: {
type: String,
default: ''
},
account: {
type: String,
default: ''
},
selectionEnabled: {
type: Boolean,
default: true
@ -144,7 +152,8 @@ export default {
ipAddressesEnabled: {},
ipAddresses: {},
indexNum: 1,
sendValuesTimer: null
sendValuesTimer: null,
accountNetworkUpdateTimer: null
}
},
computed: {
@ -184,6 +193,14 @@ export default {
},
zoneId () {
this.fetchNetworks()
},
account () {
clearTimeout(this.accountNetworkUpdateTimer)
this.accountNetworkUpdateTimer = setTimeout(() => {
if (this.account) {
this.fetchNetworks()
}
}, 750)
}
},
created () {
@ -196,13 +213,20 @@ export default {
return
}
this.loading = true
api('listNetworks', {
var params = {
zoneid: this.zoneId,
listall: true
}).then(response => {
}
if (this.domainid && this.account) {
params.domainid = this.domainid
params.account = this.account
}
api('listNetworks', params).then(response => {
this.networks = response.listnetworksresponse.network || []
this.orderNetworks()
}).catch(() => {
this.networks = []
}).finally(() => {
this.orderNetworks()
this.loading = false
})
},

View File

@ -51,7 +51,7 @@
</template>
<a-divider style="margin: 6px 0px; border-width: 0px"/>
<a-row :gutter="[10, 10]">
<a-col :span="12">
<a-col :span="12" v-if="'listVirtualMachines' in $store.getters.apis">
<router-link :to="{ path: '/vm' }">
<a-statistic
:title="$t('label.instances')"
@ -63,7 +63,7 @@
</a-statistic>
</router-link>
</a-col>
<a-col :span="12">
<a-col :span="12" v-if="'listKubernetesClusters' in $store.getters.apis">
<router-link :to="{ path: '/kubernetes' }">
<a-statistic
:title="$t('label.kubernetes.cluster')"
@ -75,7 +75,7 @@
</a-statistic>
</router-link>
</a-col>
<a-col :span="12">
<a-col :span="12" v-if="'listVolumes' in $store.getters.apis">
<router-link :to="{ path: '/volume' }">
<a-statistic
:title="$t('label.volumes')"
@ -87,7 +87,7 @@
</a-statistic>
</router-link>
</a-col>
<a-col :span="12">
<a-col :span="12" v-if="'listSnapshots' in $store.getters.apis">
<router-link :to="{ path: '/snapshot' }">
<a-statistic
:title="$t('label.snapshots')"
@ -99,7 +99,7 @@
</a-statistic>
</router-link>
</a-col>
<a-col :span="12">
<a-col :span="12" v-if="'listNetworks' in $store.getters.apis">
<router-link :to="{ path: '/guestnetwork' }">
<a-statistic
:title="$t('label.guest.networks')"
@ -111,7 +111,7 @@
</a-statistic>
</router-link>
</a-col>
<a-col :span="12">
<a-col :span="12" v-if="'listVPCs' in $store.getters.apis">
<router-link :to="{ path: '/vpc' }">
<a-statistic
:title="$t('label.vpcs')"
@ -123,7 +123,7 @@
</a-statistic>
</router-link>
</a-col>
<a-col :span="12">
<a-col :span="12" v-if="'listPublicIpAddresses' in $store.getters.apis">
<router-link :to="{ path: '/publicip' }">
<a-statistic
:title="$t('label.public.ips')"
@ -135,7 +135,7 @@
</a-statistic>
</router-link>
</a-col>
<a-col :span="12">
<a-col :span="12" v-if="'listTemplates' in $store.getters.apis">
<router-link :to="{ path: '/template', query: { templatefilter: 'self', filter: 'self' } }">
<a-statistic
:title="$t('label.templates')"
@ -150,7 +150,7 @@
</a-row>
</chart-card>
</a-col>
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }">
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }" v-if="'listVirtualMachines' in $store.getters.apis">
<chart-card :loading="loading" class="dashboard-card">
<template #title>
<div class="center">
@ -315,7 +315,7 @@
</a-list>
</chart-card>
</a-col>
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }">
<a-col :xs="{ span: 24 }" :lg="{ span: 12 }" :xl="{ span: 8 }" :xxl="{ span: 8 }" v-if="'listEvents' in $store.getters.apis">
<chart-card :loading="loading" class="dashboard-card dashboard-event">
<template #title>
<div class="center">
@ -482,37 +482,60 @@ export default {
}
this.listInstances()
this.listEvents()
this.loading = true
api('listKubernetesClusters', { listall: true, page: 1, pagesize: 1 }).then(json => {
this.loading = false
this.data.kubernetes = json?.listkubernetesclustersresponse?.count
})
api('listVolumes', { listall: true, page: 1, pagesize: 1 }).then(json => {
this.loading = false
this.data.volumes = json?.listvolumesresponse?.count
})
api('listSnapshots', { listall: true, page: 1, pagesize: 1 }).then(json => {
this.loading = false
this.data.snapshots = json?.listsnapshotsresponse?.count
})
api('listNetworks', { listall: true, page: 1, pagesize: 1 }).then(json => {
this.loading = false
this.data.networks = json?.listnetworksresponse?.count
})
api('listVPCs', { listall: true, page: 1, pagesize: 1 }).then(json => {
this.loading = false
this.data.vpcs = json?.listvpcsresponse?.count
})
api('listPublicIpAddresses', { listall: true, page: 1, pagesize: 1 }).then(json => {
this.loading = false
this.data.ips = json?.listpublicipaddressesresponse?.count
})
api('listTemplates', { templatefilter: 'self', listall: true, page: 1, pagesize: 1 }).then(json => {
this.loading = false
this.data.templates = json?.listtemplatesresponse?.count
})
if ('listKubernetesClusters' in this.$store.getters.apis) {
this.loading = true
api('listKubernetesClusters', { listall: true, page: 1, pagesize: 1 }).then(json => {
this.loading = false
this.data.kubernetes = json?.listkubernetesclustersresponse?.count
})
}
if ('listVolumes' in this.$store.getters.apis) {
this.loading = true
api('listVolumes', { listall: true, page: 1, pagesize: 1 }).then(json => {
this.loading = false
this.data.volumes = json?.listvolumesresponse?.count
})
}
if ('listSnapshots' in this.$store.getters.apis) {
this.loading = true
api('listSnapshots', { listall: true, page: 1, pagesize: 1 }).then(json => {
this.loading = false
this.data.snapshots = json?.listsnapshotsresponse?.count
})
}
if ('listNetworks' in this.$store.getters.apis) {
this.loading = true
api('listNetworks', { listall: true, page: 1, pagesize: 1 }).then(json => {
this.loading = false
this.data.networks = json?.listnetworksresponse?.count
})
}
if ('listVPCs' in this.$store.getters.apis) {
this.loading = true
api('listVPCs', { listall: true, page: 1, pagesize: 1 }).then(json => {
this.loading = false
this.data.vpcs = json?.listvpcsresponse?.count
})
}
if ('listPublicIpAddresses' in this.$store.getters.apis) {
this.loading = true
api('listPublicIpAddresses', { listall: true, page: 1, pagesize: 1 }).then(json => {
this.loading = false
this.data.ips = json?.listpublicipaddressesresponse?.count
})
}
if ('listTemplates' in this.$store.getters.apis) {
this.loading = true
api('listTemplates', { templatefilter: 'self', listall: true, page: 1, pagesize: 1 }).then(json => {
this.loading = false
this.data.templates = json?.listtemplatesresponse?.count
})
}
},
listInstances (zone) {
listInstances () {
if (!('listVirtualMachines' in this.$store.getters.apis)) {
return
}
this.loading = true
api('listVirtualMachines', { listall: true, details: 'min', page: 1, pagesize: 1 }).then(json => {
this.loading = false
@ -528,6 +551,9 @@ export default {
})
},
listEvents () {
if (!('listEvents' in this.$store.getters.apis)) {
return
}
const params = {
page: 1,
pagesize: 8,

View File

@ -162,7 +162,7 @@
</a-form-item>
<div
v-if="form.protocol === 'nfs' || form.protocol === 'SMB' || form.protocol === 'iscsi' || form.protocol === 'vmfs'|| form.protocol === 'Gluster' || form.protocol === 'Linstor' ||
(form.protocol === 'PreSetup' && hypervisorType === 'VMware') || form.protocol === 'datastorecluster'">
(form.protocol === 'PreSetup' && hypervisorType === 'VMware') || form.protocol === 'datastorecluster' || form.provider === 'Linstor'">
<a-form-item name="server" ref="server">
<template #label>
<tooltip-label :title="$t('label.server')" :tooltip="$t('message.server.description')"/>
@ -376,7 +376,7 @@
<a-input v-model:value="form.volume" :placeholder="$t('label.volume')"/>
</a-form-item>
</div>
<div v-if="form.protocol === 'Linstor'">
<div v-if="form.protocol === 'Linstor' || form.provider === 'Linstor'">
<a-form-item name="capacityIops" ref="capacityIops">
<template #label>
<tooltip-label :title="$t('label.capacityiops')" :tooltip="apiParams.capacityiops.description"/>
@ -852,13 +852,7 @@ export default {
var lun = values.lun
url = this.iscsiURL(server, iqn, lun)
} else if (values.protocol === 'Linstor') {
url = this.linstorURL(server)
params.provider = 'Linstor'
values.managed = false
params['details[0].resourceGroup'] = values.resourcegroup
if (values.capacityIops && values.capacityIops.length > 0) {
params.capacityIops = values.capacityIops.split(',').join('')
}
} else if (values.protocol === 'Filesystem') {
url = this.filesystemURL(values.host, path)
} else if (values.provider === 'Primera') {
@ -870,6 +864,16 @@ export default {
params['details[0].api_password'] = values.flashArrayPassword
url = values.flashArrayURL
}
if (values.provider === 'Linstor') {
url = this.linstorURL(server)
values.managed = false
params['details[0].resourceGroup'] = values.resourcegroup
if (values.capacityIops && values.capacityIops.length > 0) {
params.capacityIops = values.capacityIops.split(',').join('')
}
}
params.url = url
if (values.provider !== 'DefaultPrimary' && values.provider !== 'PowerFlex') {
if (values.managed) {

View File

@ -297,6 +297,8 @@
<multi-network-selection
:items="nics"
:zoneId="cluster.zoneid"
:domainid="form.domainid"
:account="form.account"
:selectionEnabled="false"
:filterUnimplementedNetworks="true"
:hypervisor="this.cluster.hypervisortype"
@ -693,7 +695,9 @@ export default {
this.form = reactive({
rootdiskid: 0,
migrateallowed: this.switches.migrateAllowed,
forced: this.switches.forced
forced: this.switches.forced,
domainid: null,
account: null
})
this.rules = reactive({
displayname: [{ required: true, message: this.$t('message.error.input.value') }],