Merge remote-tracking branch 'origin/4.15'

This commit is contained in:
Rohit Yadav 2021-05-04 19:37:45 +05:30
commit 1abd10199c
31 changed files with 280 additions and 86 deletions

View File

@ -72,7 +72,7 @@ public class RegisterTemplateCmd extends BaseCmd implements UserCmd {
private String format; private String format;
@Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, required = true, description = "the target hypervisor for the template") @Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, required = true, description = "the target hypervisor for the template")
private String hypervisor; protected String hypervisor;
@Parameter(name = ApiConstants.IS_FEATURED, type = CommandType.BOOLEAN, description = "true if this template is a featured template, false otherwise") @Parameter(name = ApiConstants.IS_FEATURED, type = CommandType.BOOLEAN, description = "true if this template is a featured template, false otherwise")
private Boolean featured; private Boolean featured;
@ -162,6 +162,11 @@ public class RegisterTemplateCmd extends BaseCmd implements UserCmd {
description = "true if template should bypass Secondary Storage and be downloaded to Primary Storage on deployment") description = "true if template should bypass Secondary Storage and be downloaded to Primary Storage on deployment")
private Boolean directDownload; private Boolean directDownload;
@Parameter(name=ApiConstants.DEPLOY_AS_IS,
type = CommandType.BOOLEAN,
description = "(VMware only) true if VM deployments should preserve all the configurations defined for this template", since = "4.15.1")
protected Boolean deployAsIs;
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
/////////////////// Accessors /////////////////////// /////////////////// Accessors ///////////////////////
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
@ -274,8 +279,9 @@ public class RegisterTemplateCmd extends BaseCmd implements UserCmd {
return directDownload == null ? false : directDownload; return directDownload == null ? false : directDownload;
} }
public Boolean isDeployAsIs() { public boolean isDeployAsIs() {
return hypervisor != null && hypervisor.equalsIgnoreCase(Hypervisor.HypervisorType.VMware.toString()); return Hypervisor.HypervisorType.VMware.toString().equalsIgnoreCase(hypervisor) &&
Boolean.TRUE.equals(deployAsIs);
} }
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
@ -341,7 +347,7 @@ public class RegisterTemplateCmd extends BaseCmd implements UserCmd {
"Parameter directdownload is only allowed for KVM templates"); "Parameter directdownload is only allowed for KVM templates");
} }
if (!getHypervisor().equalsIgnoreCase(Hypervisor.HypervisorType.VMware.toString()) && osTypeId == null) { if (!isDeployAsIs() && osTypeId == null) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please provide a guest OS type"); throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please provide a guest OS type");
} }
} }

View File

@ -20,6 +20,7 @@
package org.apache.cloudstack.api.command.user.template; package org.apache.cloudstack.api.command.user.template;
import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceAllocationException;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.template.TemplateApiService; import com.cloud.template.TemplateApiService;
import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.ServerApiException;
@ -32,7 +33,7 @@ import org.mockito.runners.MockitoJUnitRunner;
import java.util.ArrayList; import java.util.ArrayList;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class RegisterTemplateCmdTest{ public class RegisterTemplateCmdTest {
@InjectMocks @InjectMocks
private RegisterTemplateCmd registerTemplateCmd; private RegisterTemplateCmd registerTemplateCmd;
@ -108,4 +109,34 @@ public class RegisterTemplateCmdTest{
registerTemplateCmd.zoneId = 1L; registerTemplateCmd.zoneId = 1L;
Assert.assertEquals((Long)1L,registerTemplateCmd.getZoneIds().get(0)); Assert.assertEquals((Long)1L,registerTemplateCmd.getZoneIds().get(0));
} }
private void testIsDeployAsIsBase(Hypervisor.HypervisorType hypervisorType, Boolean deployAsIsParameter, boolean expectedResult) {
registerTemplateCmd = new RegisterTemplateCmd();
registerTemplateCmd.hypervisor = hypervisorType.name();
registerTemplateCmd.deployAsIs = deployAsIsParameter;
boolean isDeployAsIs = registerTemplateCmd.isDeployAsIs();
Assert.assertEquals(expectedResult, isDeployAsIs);
}
@Test
public void testIsDeployAsIsVmwareNullAsIs() {
testIsDeployAsIsBase(Hypervisor.HypervisorType.VMware, null, false);
}
@Test
public void testIsDeployAsIsVmwareNotAsIs() {
testIsDeployAsIsBase(Hypervisor.HypervisorType.VMware, false, false);
}
@Test
public void testIsDeployAsIsVmwareAsIs() {
testIsDeployAsIsBase(Hypervisor.HypervisorType.VMware, true, true);
}
@Test
public void testIsDeployAsIsNonVmware() {
testIsDeployAsIsBase(Hypervisor.HypervisorType.KVM, true, false);
testIsDeployAsIsBase(Hypervisor.HypervisorType.XenServer, true, false);
testIsDeployAsIsBase(Hypervisor.HypervisorType.Any, true, false);
}
} }

View File

@ -262,5 +262,11 @@ public interface VirtualMachineManager extends Manager {
UserVm restoreVirtualMachine(long vmId, Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException; UserVm restoreVirtualMachine(long vmId, Long newTemplateId) throws ResourceUnavailableException, InsufficientCapacityException;
/**
* Returns true if the VM's Root volume is allocated at a local storage pool
*/
boolean isRootVolumeOnLocalStorage(long vmId);
Pair<Long, Long> findClusterAndHostIdForVm(long vmId); Pair<Long, Long> findClusterAndHostIdForVm(long vmId);
} }

View File

@ -3828,22 +3828,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
final ServiceOfferingVO currentServiceOffering = _offeringDao.findByIdIncludingRemoved(vmInstance.getId(), vmInstance.getServiceOfferingId()); final ServiceOfferingVO currentServiceOffering = _offeringDao.findByIdIncludingRemoved(vmInstance.getId(), vmInstance.getServiceOfferingId());
// Check that the service offering being upgraded to has the same Guest IP type as the VM's current service offering checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstance, newServiceOffering);
// NOTE: With the new network refactoring in 2.2, we shouldn't need the check for same guest IP type anymore.
/*
* if (!currentServiceOffering.getGuestIpType().equals(newServiceOffering.getGuestIpType())) { String errorMsg =
* "The service offering being upgraded to has a guest IP type: " + newServiceOffering.getGuestIpType(); errorMsg +=
* ". Please select a service offering with the same guest IP type as the VM's current service offering (" +
* currentServiceOffering.getGuestIpType() + ")."; throw new InvalidParameterValueException(errorMsg); }
*/
// Check that the service offering being upgraded to has the same storage pool preference as the VM's current service
// offering
if (currentServiceOffering.isUseLocalStorage() != newServiceOffering.isUseLocalStorage()) {
throw new InvalidParameterValueException("Unable to upgrade virtual machine " + vmInstance.toString() +
", cannot switch between local storage and shared storage service offerings. Current offering " + "useLocalStorage=" +
currentServiceOffering.isUseLocalStorage() + ", target offering useLocalStorage=" + newServiceOffering.isUseLocalStorage());
}
// if vm is a system vm, check if it is a system service offering, if yes return with error as it cannot be used for user vms // if vm is a system vm, check if it is a system service offering, if yes return with error as it cannot be used for user vms
if (currentServiceOffering.isSystemUse() != newServiceOffering.isSystemUse()) { if (currentServiceOffering.isSystemUse() != newServiceOffering.isSystemUse()) {
@ -3865,6 +3850,39 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
} }
} }
/**
* Throws an InvalidParameterValueException in case the new service offerings does not match the storage scope (e.g. local or shared).
*/
protected void checkIfNewOfferingStorageScopeMatchesStoragePool(VirtualMachine vmInstance, ServiceOffering newServiceOffering) {
boolean isRootVolumeOnLocalStorage = isRootVolumeOnLocalStorage(vmInstance.getId());
if (newServiceOffering.isUseLocalStorage() && !isRootVolumeOnLocalStorage) {
String message = String .format("Unable to upgrade virtual machine %s, target offering use local storage but the storage pool where "
+ "the volume is allocated is a shared storage.", vmInstance.toString());
throw new InvalidParameterValueException(message);
}
if (!newServiceOffering.isUseLocalStorage() && isRootVolumeOnLocalStorage) {
String message = String.format("Unable to upgrade virtual machine %s, target offering use shared storage but the storage pool where "
+ "the volume is allocated is a local storage.", vmInstance.toString());
throw new InvalidParameterValueException(message);
}
}
public boolean isRootVolumeOnLocalStorage(long vmId) {
ScopeType poolScope = ScopeType.ZONE;
List<VolumeVO> volumes = _volsDao.findByInstanceAndType(vmId, Type.ROOT);
if(CollectionUtils.isNotEmpty(volumes)) {
VolumeVO rootDisk = volumes.get(0);
Long poolId = rootDisk.getPoolId();
if (poolId != null) {
StoragePoolVO storagePoolVO = _storagePoolDao.findById(poolId);
poolScope = storagePoolVO.getScope();
}
}
return ScopeType.HOST == poolScope;
}
@Override @Override
public boolean upgradeVmDb(final long vmId, final ServiceOffering newServiceOffering, ServiceOffering currentServiceOffering) { public boolean upgradeVmDb(final long vmId, final ServiceOffering newServiceOffering, ServiceOffering currentServiceOffering) {

View File

@ -31,6 +31,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
@ -624,4 +625,59 @@ public class VirtualMachineManagerImplTest {
assertTrue(VirtualMachineManagerImpl.matches(tags,three)); assertTrue(VirtualMachineManagerImpl.matches(tags,three));
assertTrue(VirtualMachineManagerImpl.matches(others,three)); assertTrue(VirtualMachineManagerImpl.matches(others,three));
} }
@Test
public void isRootVolumeOnLocalStorageTestOnLocal() {
prepareAndTestIsRootVolumeOnLocalStorage(ScopeType.HOST, true);
}
@Test
public void isRootVolumeOnLocalStorageTestCluster() {
prepareAndTestIsRootVolumeOnLocalStorage(ScopeType.CLUSTER, false);
}
@Test
public void isRootVolumeOnLocalStorageTestZone() {
prepareAndTestIsRootVolumeOnLocalStorage(ScopeType.ZONE, false);
}
private void prepareAndTestIsRootVolumeOnLocalStorage(ScopeType scope, boolean expected) {
StoragePoolVO storagePoolVoMock = Mockito.mock(StoragePoolVO.class);
Mockito.doReturn(storagePoolVoMock).when(storagePoolDaoMock).findById(Mockito.anyLong());
Mockito.doReturn(scope).when(storagePoolVoMock).getScope();
List<VolumeVO> mockedVolumes = new ArrayList<>();
mockedVolumes.add(volumeVoMock);
Mockito.doReturn(mockedVolumes).when(volumeDaoMock).findByInstanceAndType(Mockito.anyLong(), Mockito.any());
boolean result = virtualMachineManagerImpl.isRootVolumeOnLocalStorage(0l);
Assert.assertEquals(expected, result);
}
@Test
public void checkIfNewOfferingStorageScopeMatchesStoragePoolTestLocalLocal() {
prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(true, true);
}
@Test
public void checkIfNewOfferingStorageScopeMatchesStoragePoolTestSharedShared() {
prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(false, false);
}
@Test (expected = InvalidParameterValueException.class)
public void checkIfNewOfferingStorageScopeMatchesStoragePoolTestLocalShared() {
prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(true, false);
}
@Test (expected = InvalidParameterValueException.class)
public void checkIfNewOfferingStorageScopeMatchesStoragePoolTestSharedLocal() {
prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(false, true);
}
private void prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(boolean isRootOnLocal, boolean isOfferingUsingLocal) {
Mockito.doReturn(isRootOnLocal).when(virtualMachineManagerImpl).isRootVolumeOnLocalStorage(Mockito.anyLong());
Mockito.doReturn("vmInstanceMockedToString").when(vmInstanceMock).toString();
Mockito.doReturn(isOfferingUsingLocal).when(serviceOfferingMock).isUseLocalStorage();
virtualMachineManagerImpl.checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstanceMock, serviceOfferingMock);
}
} }

View File

@ -200,7 +200,7 @@ public class VMInstanceVO implements VirtualMachine, FiniteStateObject<State, Vi
@Column(name = "backup_external_id") @Column(name = "backup_external_id")
protected String backupExternalId; protected String backupExternalId;
@Column(name = "backup_volumes") @Column(name = "backup_volumes", length = 65535)
protected String backupVolumes; protected String backupVolumes;
public VMInstanceVO(long id, long serviceOfferingId, String name, String instanceName, Type type, Long vmTemplateId, HypervisorType hypervisorType, long guestOSId, public VMInstanceVO(long id, long serviceOfferingId, String name, String instanceName, Type type, Long vmTemplateId, HypervisorType hypervisorType, long guestOSId,

View File

@ -80,6 +80,7 @@ Requires: iptables-services
Requires: qemu-img Requires: qemu-img
Requires: python3-pip Requires: python3-pip
Requires: python3-setuptools Requires: python3-setuptools
Requires: libgcrypt > 1.8.3
Group: System Environment/Libraries Group: System Environment/Libraries
%description management %description management
The CloudStack management server is the central point of coordination, The CloudStack management server is the central point of coordination,
@ -109,6 +110,7 @@ Requires: perl
Requires: python3-libvirt Requires: python3-libvirt
Requires: qemu-img Requires: qemu-img
Requires: qemu-kvm Requires: qemu-kvm
Requires: libgcrypt > 1.8.3
Provides: cloud-agent Provides: cloud-agent
Group: System Environment/Libraries Group: System Environment/Libraries
%description agent %description agent

View File

@ -32,6 +32,7 @@ import java.util.stream.Stream;
import javax.inject.Inject; import javax.inject.Inject;
import com.cloud.storage.dao.VMTemplateDetailsDao; import com.cloud.storage.dao.VMTemplateDetailsDao;
import com.cloud.vm.VirtualMachineManager;
import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO; import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO;
import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.affinity.AffinityGroupResponse;
@ -424,6 +425,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
@Inject @Inject
private UserDao userDao; private UserDao userDao;
@Inject
private VirtualMachineManager virtualMachineManager;
/* /*
* (non-Javadoc) * (non-Javadoc)
* *
@ -2962,8 +2966,10 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
sc.addAnd("id", SearchCriteria.Op.NEQ, currentVmOffering.getId()); sc.addAnd("id", SearchCriteria.Op.NEQ, currentVmOffering.getId());
} }
// 1. Only return offerings with the same storage type boolean isRootVolumeUsingLocalStorage = virtualMachineManager.isRootVolumeOnLocalStorage(vmId);
sc.addAnd("useLocalStorage", SearchCriteria.Op.EQ, currentVmOffering.isUseLocalStorage());
// 1. Only return offerings with the same storage type than the storage pool where the VM's root volume is allocated
sc.addAnd("useLocalStorage", SearchCriteria.Op.EQ, isRootVolumeUsingLocalStorage);
// 2.In case vm is running return only offerings greater than equal to current offering compute. // 2.In case vm is running return only offerings greater than equal to current offering compute.
if (vmInstance.getState() == VirtualMachine.State.Running) { if (vmInstance.getState() == VirtualMachine.State.Running) {

View File

@ -291,7 +291,7 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat
} }
Map details = cmd.getDetails(); Map details = cmd.getDetails();
if (hypervisorType == HypervisorType.VMware) { if (cmd.isDeployAsIs()) {
if (MapUtils.isNotEmpty(details)) { if (MapUtils.isNotEmpty(details)) {
if (details.containsKey(VmDetailConstants.ROOT_DISK_CONTROLLER)) { if (details.containsKey(VmDetailConstants.ROOT_DISK_CONTROLLER)) {
s_logger.info("Ignoring the rootDiskController detail provided, as we honour what is defined in the template"); s_logger.info("Ignoring the rootDiskController detail provided, as we honour what is defined in the template");

View File

@ -260,8 +260,8 @@ class TestKubernetesCluster(cloudstackTestCase):
if validateList(templates)[0] != PASS: if validateList(templates)[0] != PASS:
details = None details = None
if hypervisor in ["vmware"]: if hypervisor in ["vmware"] and "details" in cks_template:
details = [{"keyboard": "us"}] details = cks_template["details"]
template = Template.register(cls.apiclient, cks_template, zoneid=cls.zone.id, hypervisor=hypervisor.lower(), randomize_name=False, details=details) template = Template.register(cls.apiclient, cks_template, zoneid=cls.zone.id, hypervisor=hypervisor.lower(), randomize_name=False, details=details)
template.download(cls.apiclient) template.download(cls.apiclient)
return template, False return template, False

View File

@ -59,6 +59,7 @@ class TestVMWareStoragePolicies(cloudstackTestCase):
cls.apiclient, cls.apiclient,
cls.zone.id, cls.zone.id,
cls.hypervisor, cls.hypervisor,
deploy_as_is=cls.hypervisor.lower() == "vmware"
) )
cls._cleanup.append(cls.network_offering) cls._cleanup.append(cls.network_offering)
return return

View File

@ -84,6 +84,7 @@ class TestCreateTemplateWithChecksum(cloudstackTestCase):
self.test_template.displaytext = 'test sha-1' self.test_template.displaytext = 'test sha-1'
self.test_template.url = "http://dl.openvm.eu/cloudstack/macchinina/x86_64/macchinina-vmware.ova" self.test_template.url = "http://dl.openvm.eu/cloudstack/macchinina/x86_64/macchinina-vmware.ova"
self.test_template.format = "OVA" self.test_template.format = "OVA"
self.test_template.ostypeid = self.getOsType("Other Linux (64-bit)")
self.md5 = "27f3c56a8c7ec7b2f3ff2199f7078006" self.md5 = "27f3c56a8c7ec7b2f3ff2199f7078006"
self.sha256 = "a7b04c1eb507f3f5de844bda352df1ea5e20335b465409493ca6ae07dfd0a158" self.sha256 = "a7b04c1eb507f3f5de844bda352df1ea5e20335b465409493ca6ae07dfd0a158"

View File

@ -308,7 +308,8 @@ class TestVolumes(cloudstackTestCase):
cls.apiclient, cls.apiclient,
cls.zone.id, cls.zone.id,
cls.services["ostype"], cls.services["ostype"],
cls.hypervisor cls.hypervisor,
deploy_as_is=cls.hypervisor.lower() == "vmware"
) )
if cls.template == FAILED: if cls.template == FAILED:
assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"]

View File

@ -1405,24 +1405,22 @@ class Template:
elif "hypervisor" in services: elif "hypervisor" in services:
cmd.hypervisor = services["hypervisor"] cmd.hypervisor = services["hypervisor"]
if cmd.hypervisor.lower() not in ["vmware"]: if "ostypeid" in services:
# Since version 4.15 VMware templates honour the guest OS defined in the template cmd.ostypeid = services["ostypeid"]
if "ostypeid" in services: elif "ostype" in services:
cmd.ostypeid = services["ostypeid"] # Find OSTypeId from Os type
elif "ostype" in services: sub_cmd = listOsTypes.listOsTypesCmd()
# Find OSTypeId from Os type sub_cmd.description = services["ostype"]
sub_cmd = listOsTypes.listOsTypesCmd() ostypes = apiclient.listOsTypes(sub_cmd)
sub_cmd.description = services["ostype"]
ostypes = apiclient.listOsTypes(sub_cmd)
if not isinstance(ostypes, list): if not isinstance(ostypes, list):
raise Exception(
"Unable to find Ostype id with desc: %s" %
services["ostype"])
cmd.ostypeid = ostypes[0].id
else:
raise Exception( raise Exception(
"Unable to find Ostype is required for registering template") "Unable to find Ostype id with desc: %s" %
services["ostype"])
cmd.ostypeid = ostypes[0].id
else:
raise Exception(
"Unable to find Ostype is required for registering template")
cmd.url = services["url"] cmd.url = services["url"]
@ -1440,6 +1438,7 @@ class Template:
cmd.isdynamicallyscalable = services["isdynamicallyscalable"] if "isdynamicallyscalable" in services else False cmd.isdynamicallyscalable = services["isdynamicallyscalable"] if "isdynamicallyscalable" in services else False
cmd.passwordenabled = services[ cmd.passwordenabled = services[
"passwordenabled"] if "passwordenabled" in services else False "passwordenabled"] if "passwordenabled" in services else False
cmd.deployasis = services["deployasis"] if "deployasis" in services else False
if account: if account:
cmd.account = account cmd.account = account

View File

@ -348,7 +348,7 @@ def get_template(
return list_templatesout[0] return list_templatesout[0]
def get_test_template(apiclient, zone_id=None, hypervisor=None, test_templates=None): def get_test_template(apiclient, zone_id=None, hypervisor=None, test_templates=None, deploy_as_is=False):
""" """
@Name : get_test_template @Name : get_test_template
@Desc : Retrieves the test template used to running tests. When the template @Desc : Retrieves the test template used to running tests. When the template
@ -373,6 +373,8 @@ def get_test_template(apiclient, zone_id=None, hypervisor=None, test_templates=N
return FAILED return FAILED
test_template = test_templates[hypervisor] test_template = test_templates[hypervisor]
if deploy_as_is:
test_template['deployasis'] = True
cmd = listTemplates.listTemplatesCmd() cmd = listTemplates.listTemplatesCmd()
cmd.name = test_template['name'] cmd.name = test_template['name']
@ -513,7 +515,7 @@ def get_windows_template(
return FAILED return FAILED
def get_suitable_test_template(apiclient, zoneid, ostypeid, hypervisor): def get_suitable_test_template(apiclient, zoneid, ostypeid, hypervisor, deploy_as_is=False):
''' '''
@Name : get_suitable_test_template @Name : get_suitable_test_template
@Desc : Retrieves the test template information based upon inputs provided @Desc : Retrieves the test template information based upon inputs provided
@ -525,11 +527,12 @@ def get_suitable_test_template(apiclient, zoneid, ostypeid, hypervisor):
template Information matching the inputs template Information matching the inputs
''' '''
template = FAILED template = FAILED
if hypervisor.lower() in ["xenserver"]: if hypervisor.lower() in ["xenserver"] or (hypervisor.lower() in ["vmware"] and deploy_as_is):
template = get_test_template( template = get_test_template(
apiclient, apiclient,
zoneid, zoneid,
hypervisor) hypervisor,
deploy_as_is=deploy_as_is)
if template == FAILED: if template == FAILED:
template = get_template( template = get_template(
apiclient, apiclient,

View File

@ -718,7 +718,7 @@
"label.demote.project.owner": "Demote account to Regular role", "label.demote.project.owner": "Demote account to Regular role",
"label.demote.project.owner.user": "Demote user to Regular role", "label.demote.project.owner.user": "Demote user to Regular role",
"label.deny": "Deny", "label.deny": "Deny",
"label.deployasis":"Deploy As-Is", "label.deployasis":"Read VM settings from OVA",
"label.deploymentplanner": "Deployment planner", "label.deploymentplanner": "Deployment planner",
"label.description": "Description", "label.description": "Description",
"label.destcidr": "Destination CIDR", "label.destcidr": "Destination CIDR",

View File

@ -23,7 +23,7 @@
<a-form-item class="pair-select-container" :label="selectLabel" v-if="this.checked"> <a-form-item class="pair-select-container" :label="selectLabel" v-if="this.checked">
<a-select <a-select
v-decorator="[selectDecorator, { v-decorator="[selectDecorator, {
initialValue: this.getSelectInitialValue() initialValue: selectedOption ? selectedOption : this.getSelectInitialValue()
}]" }]"
showSearch showSearch
optionFilterProp="children" optionFilterProp="children"
@ -80,23 +80,16 @@ export default {
return array !== null && array !== undefined && Array.isArray(array) && array.length > 0 return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
}, },
getSelectInitialValue () { getSelectInitialValue () {
if (this.arrayHasItems(this.selectOptions)) { const provider = this.selectOptions?.filter(x => x.enabled)?.[0]?.name || ''
for (var i = 0; i < this.selectOptions.length; i++) { this.handleSelectChange(provider)
if (this.selectOptions[i].enabled !== false) { return provider
return this.selectOptions[i].name
}
}
}
return ''
}, },
handleCheckChange (e) { handleCheckChange (e) {
this.checked = e.target.checked this.checked = e.target.checked
if (this.checked && this.arrayHasItems(this.selectOptions)) {
this.selectedOption = this.selectOptions[0].name
}
this.$emit('handle-checkpair-change', this.resourceKey, this.checked, '') this.$emit('handle-checkpair-change', this.resourceKey, this.checked, '')
}, },
handleSelectChange (val) { handleSelectChange (val) {
this.selectedOption = val
this.$emit('handle-checkpair-change', this.resourceKey, this.checked, val) this.$emit('handle-checkpair-change', this.resourceKey, this.checked, val)
} }
} }

View File

@ -96,7 +96,7 @@ export default {
filter: '' filter: ''
} }
}, },
beforeMount () { created () {
switch (this.$route.meta.name) { switch (this.$route.meta.name) {
case 'account': case 'account':
this.scopeKey = 'accountid' this.scopeKey = 'accountid'
@ -119,8 +119,6 @@ export default {
default: default:
this.scopeKey = '' this.scopeKey = ''
} }
},
created () {
this.fetchData() this.fetchData()
}, },
watch: { watch: {

View File

@ -317,7 +317,7 @@ export default {
label: 'label.action.reset.password', label: 'label.action.reset.password',
message: 'message.action.instance.reset.password', message: 'message.action.instance.reset.password',
dataView: true, dataView: true,
show: (record) => { return ['Running', 'Stopped'].includes(record.state) && record.passwordenabled }, show: (record) => { return ['Stopped'].includes(record.state) && record.passwordenabled },
response: (result) => { return result.virtualmachine && result.virtualmachine.password ? `The password of VM <b>${result.virtualmachine.displayname}</b> is <b>${result.virtualmachine.password}</b>` : null } response: (result) => { return result.virtualmachine && result.virtualmachine.password ? `The password of VM <b>${result.virtualmachine.displayname}</b> is <b>${result.virtualmachine.password}</b>` : null }
}, },
{ {

View File

@ -249,6 +249,7 @@ export default {
} }
} }
const resourceName = params.displayname || params.displaytext || params.name || this.resource.name
let hasJobId = false let hasJobId = false
api(this.action.api, params).then(json => { api(this.action.api, params).then(json => {
for (const obj in json) { for (const obj in json) {
@ -270,6 +271,18 @@ export default {
} }
} }
if (!hasJobId) { if (!hasJobId) {
var message = this.action.successMessage ? this.$t(this.action.successMessage) : this.$t(this.action.label) +
(resourceName ? ' - ' + resourceName : '')
var duration = 2
if (this.action.additionalMessage) {
message = message + ' - ' + this.$t(this.action.successMessage)
duration = 5
}
this.$message.success({
content: message,
key: this.action.label + resourceName,
duration: duration
})
this.parentUpdActionData(json) this.parentUpdActionData(json)
this.parentFetchData() this.parentFetchData()
} }

View File

@ -208,8 +208,18 @@
:default-checked="xenServerProvider" /> :default-checked="xenServerProvider" />
</a-form-item> </a-form-item>
</a-row> </a-row>
<a-form-item :label="$t('label.deployasis')" v-if="hyperVMWShow">
<a-switch
v-decorator="['deployasis', {
initialValue: false,
}]"
:checked="deployasis"
@change="val => deployasis = val"/>
</a-form-item>
<a-row :gutter="12" v-if="hyperKVMShow || hyperVMWShow"> <a-row :gutter="12" v-if="hyperKVMShow || hyperVMWShow">
<a-col :md="24" :lg="24" v-if="hyperKVMShow"> <a-col :md="24" :lg="24" v-if="hyperKVMShow || (hyperVMWShow && !deployasis)">
<a-form-item :label="$t('label.rootdiskcontrollertype')"> <a-form-item :label="$t('label.rootdiskcontrollertype')">
<a-select <a-select
v-decorator="['rootDiskControllerType', { v-decorator="['rootDiskControllerType', {
@ -230,7 +240,7 @@
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :md="24" :lg="24"> <a-col :md="24" :lg="24">
<a-form-item v-if="hyperVMWShow" :label="$t('label.keyboardtype')"> <a-form-item v-if="hyperVMWShow && !deployasis" :label="$t('label.keyboardtype')">
<a-select <a-select
v-decorator="['keyboardType', { v-decorator="['keyboardType', {
rules: [ rules: [
@ -248,7 +258,7 @@
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
<a-row :gutter="12" v-if="!hyperVMWShow"> <a-row :gutter="12" v-if="!hyperVMWShow || (hyperVMWShow && !deployasis)">
<a-col :md="24" :lg="24"> <a-col :md="24" :lg="24">
<a-form-item :label="$t('label.ostypeid')"> <a-form-item :label="$t('label.ostypeid')">
<a-select <a-select
@ -378,6 +388,7 @@ export default {
hyperKVMShow: false, hyperKVMShow: false,
hyperXenServerShow: false, hyperXenServerShow: false,
hyperVMWShow: false, hyperVMWShow: false,
deployasis: false,
zoneError: '', zoneError: '',
zoneErrorMessage: '', zoneErrorMessage: '',
loading: false, loading: false,
@ -763,6 +774,7 @@ export default {
this.hyperXenServerShow = false this.hyperXenServerShow = false
this.hyperVMWShow = false this.hyperVMWShow = false
this.hyperKVMShow = false this.hyperKVMShow = false
this.deployasis = false
this.allowDirectDownload = false this.allowDirectDownload = false
this.resetSelect() this.resetSelect()
@ -794,10 +806,6 @@ export default {
continue continue
} }
params[key] = input.join() params[key] = input.join()
} else if (key === 'zoneid') {
params[key] = input
} else if (key === 'ostypeid') {
params[key] = input
} else if (key === 'hypervisor') { } else if (key === 'hypervisor') {
params[key] = this.hyperVisor.opts[input].name params[key] = this.hyperVisor.opts[input].name
} else if (key === 'groupenabled') { } else if (key === 'groupenabled') {

View File

@ -64,10 +64,10 @@
:maskClosable="false" :maskClosable="false"
:footer="null" :footer="null"
:cancelText="$t('label.cancel')" :cancelText="$t('label.cancel')"
@cancel="showCreateForm = false" @cancel="closeAction"
centered centered
width="auto"> width="auto">
<CreateNetwork :resource="{ zoneid: resource.zoneid }"/> <CreateNetwork :resource="{ zoneid: resource.zoneid }" @close-action="closeAction"/>
</a-modal> </a-modal>
</a-spin> </a-spin>
@ -161,6 +161,9 @@ export default {
handleOpenShowCreateForm () { handleOpenShowCreateForm () {
this.showCreateForm = true this.showCreateForm = true
}, },
closeAction () {
this.showCreateForm = false
},
changePage (page, pageSize) { changePage (page, pageSize) {
this.page = page this.page = page
this.pageSize = pageSize this.pageSize = pageSize

View File

@ -82,7 +82,7 @@
<div>{{ acl.traffictype }}</div> <div>{{ acl.traffictype }}</div>
</div> </div>
<div class="list__col"> <div class="list__col">
<div class="list__label">{{ $t('label.reason') }}</div> <div class="list__label">{{ $t('label.description') }}</div>
<div>{{ acl.reason }}</div> <div>{{ acl.reason }}</div>
</div> </div>
</div> </div>
@ -184,7 +184,7 @@
<a-select-option value="egress">{{ $t('label.egress') }}</a-select-option> <a-select-option value="egress">{{ $t('label.egress') }}</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item :label="$t('label.reason')"> <a-form-item :label="$t('label.description')">
<a-textarea <a-textarea
v-decorator="['reason']" v-decorator="['reason']"
:autosize="{ minRows: 2 }" :autosize="{ minRows: 2 }"

View File

@ -554,7 +554,7 @@ export default {
vpcid: this.resource.vpcid vpcid: this.resource.vpcid
}).then(json => { }).then(json => {
this.tiers.data = json.listnetworksresponse.network || [] this.tiers.data = json.listnetworksresponse.network || []
this.selectedTier = this.tiers.data && this.tiers.data[0].id ? this.tiers.data[0].id : null this.selectedTier = this.tiers.data?.[0]?.id ? this.tiers.data[0].id : null
this.$forceUpdate() this.$forceUpdate()
}).catch(error => { }).catch(error => {
this.$notifyError(error) this.$notifyError(error)

View File

@ -503,7 +503,6 @@ export default {
this.addVmModalNicLoading = false this.addVmModalNicLoading = false
this.nics = [] this.nics = []
this.resetTagInputs() this.resetTagInputs()
this.resetAllRules()
}, },
openTagsModal (id) { openTagsModal (id) {
this.tagsModalLoading = true this.tagsModalLoading = true

View File

@ -385,7 +385,13 @@ export default {
publicIps: {}, publicIps: {},
snats: {}, snats: {},
vms: {} vms: {}
} },
lbProviderMap: {
publicLb: {
vpc: ['VpcVirtualRouter', 'Netscaler']
}
},
publicLBExists: false
} }
}, },
created () { created () {
@ -417,6 +423,7 @@ export default {
this.fetchLoadBalancers(network.id) this.fetchLoadBalancers(network.id)
this.fetchVMs(network.id) this.fetchVMs(network.id)
} }
this.publicLBNetworkExists()
}, },
fetchNetworkAclList () { fetchNetworkAclList () {
this.fetchLoading = true this.fetchLoading = true
@ -435,6 +442,38 @@ export default {
this.modalLoading = false this.modalLoading = false
}) })
}, },
getNetworkOffering (networkId) {
return new Promise((resolve, reject) => {
api('listNetworkOfferings', {
id: networkId
}).then(json => {
var networkOffering = json.listnetworkofferingsresponse.networkoffering[0]
resolve(networkOffering)
}).catch(e => {
reject(e)
})
})
},
publicLBNetworkExists () {
api('listNetworks', {
vpcid: this.resource.id,
supportedservices: 'LB'
}).then(async json => {
var lbNetworks = json.listnetworksresponse.network || []
if (lbNetworks.length > 0) {
this.publicLBExists = true
for (var idx = 0; idx < lbNetworks.length; idx++) {
const lbNetworkOffering = await this.getNetworkOffering(lbNetworks[idx].networkofferingid)
const index = lbNetworkOffering.service.map(svc => { return svc.name }).indexOf('Lb')
if (index !== -1 &&
this.lbProviderMap.publicLb.vpc.indexOf(lbNetworkOffering.service.map(svc => { return svc.provider[0].name })[index]) !== -1) {
this.publicLBExists = true
break
}
}
}
})
},
fetchNetworkOfferings () { fetchNetworkOfferings () {
this.fetchLoading = true this.fetchLoading = true
this.modalLoading = true this.modalLoading = true
@ -445,6 +484,17 @@ export default {
state: 'Enabled' state: 'Enabled'
}).then(json => { }).then(json => {
this.networkOfferings = json.listnetworkofferingsresponse.networkoffering || [] this.networkOfferings = json.listnetworkofferingsresponse.networkoffering || []
var filteredOfferings = []
if (this.publicLBExists) {
for (var index in this.networkOfferings) {
const offering = this.networkOfferings[index]
const idx = offering.service.map(svc => { return svc.name }).indexOf('Lb')
if (idx === -1 || this.lbProviderMap.publicLb.vpc.indexOf(offering.service.map(svc => { return svc.provider[0].name })[idx]) === -1) {
filteredOfferings.push(offering)
}
}
this.networkOfferings = filteredOfferings
}
this.$nextTick(function () { this.$nextTick(function () {
this.form.setFieldsValue({ this.form.setFieldsValue({
networkOffering: this.networkOfferings[0].id networkOffering: this.networkOfferings[0].id

View File

@ -680,7 +680,7 @@
:loading="domainLoading" :loading="domainLoading"
:placeholder="this.$t('label.domainid')"> :placeholder="this.$t('label.domainid')">
<a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex"> <a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex">
{{ opt.name || opt.description }} {{ opt.path || opt.name || opt.description }}
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>

View File

@ -368,7 +368,7 @@
:loading="domainLoading" :loading="domainLoading"
:placeholder="this.$t('label.domainid')"> :placeholder="this.$t('label.domainid')">
<a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex"> <a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex">
{{ opt.name || opt.description }} {{ opt.path || opt.name || opt.description }}
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>

View File

@ -228,7 +228,7 @@
message: `${this.$t('message.error.select')}` message: `${this.$t('message.error.select')}`
} }
], ],
initialValue: 0 initialValue: this.serviceOfferings.length > 0 ? this.serviceOfferings[0].id : ''
}]" }]"
showSearch showSearch
optionFilterProp="children" optionFilterProp="children"
@ -237,7 +237,7 @@
}" }"
:loading="serviceOfferingLoading" :loading="serviceOfferingLoading"
:placeholder="this.$t('label.serviceofferingid')"> :placeholder="this.$t('label.serviceofferingid')">
<a-select-option v-for="(opt, optIndex) in this.serviceOfferings" :key="optIndex"> <a-select-option v-for="(opt) in this.serviceOfferings" :key="opt.id">
{{ opt.name || opt.description }} {{ opt.name || opt.description }}
</a-select-option> </a-select-option>
</a-select> </a-select>
@ -410,7 +410,7 @@
:loading="domainLoading" :loading="domainLoading"
:placeholder="this.$t('label.domain')"> :placeholder="this.$t('label.domain')">
<a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex"> <a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex">
{{ opt.name || opt.description }} {{ opt.path || opt.name || opt.description }}
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>

View File

@ -108,7 +108,7 @@
:loading="domainLoading" :loading="domainLoading"
:placeholder="this.$t('label.domain')"> :placeholder="this.$t('label.domain')">
<a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex"> <a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex">
{{ opt.name || opt.description }} {{ opt.path || opt.name || opt.description }}
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>

View File

@ -48,7 +48,7 @@
:loading="domainLoading" :loading="domainLoading"
:placeholder="this.apiParams.domainid.description"> :placeholder="this.apiParams.domainid.description">
<a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex"> <a-select-option v-for="(opt, optIndex) in this.domains" :key="optIndex">
{{ opt.name || opt.description }} {{ opt.path || opt.name || opt.description }}
</a-select-option> </a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>