From 458d3b5b4753b16b1f07bfa1fe667037cb2623d6 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Wed, 19 Feb 2020 15:02:12 +0100 Subject: [PATCH] =?UTF-8?q?Multiple=20networks=20support=20for=20vms=20in?= =?UTF-8?q?=20advanced=20zone=20with=20securit=E2=80=A6=20(#3639)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/cloud/agent/api/RebootCommand.java | 11 + .../agent/api/SecurityGroupRulesCmd.java | 14 +- .../security/SecurityGroupManager.java | 2 + .../cloud/vm/VirtualMachineManagerImpl.java | 38 +- .../main/java/com/cloud/vm/dao/NicDao.java | 2 + .../java/com/cloud/vm/dao/NicDaoImpl.java | 9 + .../resource/LibvirtComputingResource.java | 123 +++- ...tworkRulesVmSecondaryIpCommandWrapper.java | 4 +- .../wrapper/LibvirtPlugNicCommandWrapper.java | 10 +- .../wrapper/LibvirtRebootCommandWrapper.java | 8 +- ...bvirtSecurityGroupRulesCommandWrapper.java | 9 +- .../wrapper/LibvirtStartCommandWrapper.java | 26 +- .../LibvirtUnPlugNicCommandWrapper.java | 5 +- .../LibvirtComputingResourceTest.java | 7 +- scripts/vm/network/security_group.py | 466 +++++++++++-- .../security/SecurityGroupManagerImpl.java | 42 +- .../security/SecurityGroupManagerImpl2.java | 7 +- .../java/com/cloud/vm/UserVmManagerImpl.java | 26 +- .../component/test_multiple_nic_support.py | 629 ++++++++++++++++++ tools/marvin/marvin/lib/utils.py | 10 +- ui/scripts/instanceWizard.js | 47 +- ui/scripts/instances.js | 10 + ui/scripts/network.js | 8 + ui/scripts/ui-custom/instanceWizard.js | 17 +- 24 files changed, 1405 insertions(+), 125 deletions(-) rename {server => engine/components-api}/src/main/java/com/cloud/network/security/SecurityGroupManager.java (95%) create mode 100644 test/integration/component/test_multiple_nic_support.py diff --git a/core/src/main/java/com/cloud/agent/api/RebootCommand.java b/core/src/main/java/com/cloud/agent/api/RebootCommand.java index eecf7f635d3..74ed7628351 100644 --- a/core/src/main/java/com/cloud/agent/api/RebootCommand.java +++ b/core/src/main/java/com/cloud/agent/api/RebootCommand.java @@ -19,8 +19,11 @@ package com.cloud.agent.api; +import com.cloud.agent.api.to.VirtualMachineTO; + public class RebootCommand extends Command { String vmName; + VirtualMachineTO vm; protected boolean executeInSequence = false; protected RebootCommand() { @@ -35,6 +38,14 @@ public class RebootCommand extends Command { return this.vmName; } + public void setVirtualMachine(VirtualMachineTO vm) { + this.vm = vm; + } + + public VirtualMachineTO getVirtualMachine() { + return vm; + } + @Override public boolean executeInSequence() { return this.executeInSequence; diff --git a/core/src/main/java/com/cloud/agent/api/SecurityGroupRulesCmd.java b/core/src/main/java/com/cloud/agent/api/SecurityGroupRulesCmd.java index 1d3d1543992..ea4ab96c5a7 100644 --- a/core/src/main/java/com/cloud/agent/api/SecurityGroupRulesCmd.java +++ b/core/src/main/java/com/cloud/agent/api/SecurityGroupRulesCmd.java @@ -30,12 +30,13 @@ import org.apache.commons.codec.digest.DigestUtils; import org.apache.log4j.Logger; import com.cloud.agent.api.LogLevel.Log4jLevel; +import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.utils.net.NetUtils; public class SecurityGroupRulesCmd extends Command { private static final String CIDR_LENGTH_SEPARATOR = "/"; - private static final char RULE_TARGET_SEPARATOR = ','; - private static final char RULE_COMMAND_SEPARATOR = ';'; + public static final char RULE_TARGET_SEPARATOR = ','; + public static final char RULE_COMMAND_SEPARATOR = ';'; protected static final String EGRESS_RULE = "E:"; protected static final String INGRESS_RULE = "I:"; private static final Logger LOGGER = Logger.getLogger(SecurityGroupRulesCmd.class); @@ -51,6 +52,7 @@ public class SecurityGroupRulesCmd extends Command { private List ingressRuleSet; private List egressRuleSet; private final List secIps; + private VirtualMachineTO vmTO; public static class IpPortAndProto { private final String proto; @@ -252,6 +254,14 @@ public class SecurityGroupRulesCmd extends Command { return vmId; } + public void setVmTO(VirtualMachineTO vmTO) { + this.vmTO = vmTO; + } + + public VirtualMachineTO getVmTO() { + return vmTO; + } + /** * used for logging * @return the number of Cidrs in the in and egress rule sets for this security group rules command. diff --git a/server/src/main/java/com/cloud/network/security/SecurityGroupManager.java b/engine/components-api/src/main/java/com/cloud/network/security/SecurityGroupManager.java similarity index 95% rename from server/src/main/java/com/cloud/network/security/SecurityGroupManager.java rename to engine/components-api/src/main/java/com/cloud/network/security/SecurityGroupManager.java index 16d8ba617bc..ffca4bb013b 100644 --- a/server/src/main/java/com/cloud/network/security/SecurityGroupManager.java +++ b/engine/components-api/src/main/java/com/cloud/network/security/SecurityGroupManager.java @@ -53,4 +53,6 @@ public interface SecurityGroupManager { SecurityGroup getSecurityGroup(String name, long accountId); boolean isVmMappedToDefaultSecurityGroup(long vmId); + + void scheduleRulesetUpdateToHosts(List affectedVms, boolean updateSeqno, Long delayMs); } diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index 2c4079cd3fd..8e52c38902c 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -24,6 +24,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; @@ -166,6 +167,7 @@ import com.cloud.network.NetworkModel; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; import com.cloud.network.router.VirtualRouter; +import com.cloud.network.security.SecurityGroupManager; import com.cloud.offering.DiskOffering; import com.cloud.offering.DiskOfferingInfo; import com.cloud.offering.NetworkOffering; @@ -333,6 +335,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac private NetworkOfferingDetailsDao networkOfferingDetailsDao; @Inject private NetworkDetailsDao networkDetailsDao; + @Inject + private SecurityGroupManager _securityGroupManager; VmWorkJobHandlerProxy _jobHandlerProxy = new VmWorkJobHandlerProxy(this); @@ -3140,11 +3144,18 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac try { final Commands cmds = new Commands(Command.OnError.Stop); - cmds.addCommand(new RebootCommand(vm.getInstanceName(), getExecuteInSequence(vm.getHypervisorType()))); + RebootCommand rebootCmd = new RebootCommand(vm.getInstanceName(), getExecuteInSequence(vm.getHypervisorType())); + rebootCmd.setVirtualMachine(getVmTO(vm.getId())); + cmds.addCommand(rebootCmd); _agentMgr.send(host.getId(), cmds); final Answer rebootAnswer = cmds.getAnswer(RebootAnswer.class); if (rebootAnswer != null && rebootAnswer.getResult()) { + if (dc.isSecurityGroupEnabled() && vm.getType() == VirtualMachine.Type.User) { + List affectedVms = new ArrayList(); + affectedVms.add(vm.getId()); + _securityGroupManager.scheduleRulesetUpdateToHosts(affectedVms, true, null); + } return; } s_logger.info("Unable to reboot VM " + vm + " on " + dest.getHost() + " due to " + (rebootAnswer == null ? " no reboot answer" : rebootAnswer.getDetails())); @@ -3154,6 +3165,29 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac } } + protected VirtualMachineTO getVmTO(Long vmId) { + final VMInstanceVO vm = _vmDao.findById(vmId); + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + final List nics = _nicsDao.listByVmId(profile.getId()); + Collections.sort(nics, new Comparator() { + @Override + public int compare(NicVO nic1, NicVO nic2) { + Long nicId1 = Long.valueOf(nic1.getDeviceId()); + Long nicId2 = Long.valueOf(nic2.getDeviceId()); + return nicId1.compareTo(nicId2); + } + }); + for (final NicVO nic : nics) { + final Network network = _networkModel.getNetwork(nic.getNetworkId()); + final NicProfile nicProfile = + new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), null, _networkModel.isSecurityGroupSupportedInNetwork(network), + _networkModel.getNetworkTag(profile.getHypervisorType(), network)); + profile.addNic(nicProfile); + } + final VirtualMachineTO to = toVmTO(profile); + return to; + } + public Command cleanup(final VirtualMachine vm, Map dpdkInterfaceMapping) { StopCommand cmd = new StopCommand(vm, getExecuteInSequence(vm.getHypervisorType()), false); cmd.setControlIp(getControlNicIpForVM(vm)); @@ -3670,7 +3704,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac //3) Remove the nic _networkMgr.removeNic(vmProfile, nic); - _nicsDao.expunge(nic.getId()); + _nicsDao.remove(nic.getId()); return true; } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java index df4fb06325f..316c2dd53d7 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java @@ -50,6 +50,8 @@ public interface NicDao extends GenericDao { NicVO findDefaultNicForVM(long instanceId); + NicVO findFirstNicForVM(long instanceId); + /** * @param networkId * @param instanceId diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java index c125d80c534..6314ca0391b 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java @@ -69,6 +69,7 @@ public class NicDaoImpl extends GenericDaoBase implements NicDao { AllFieldsSearch.and("strategy", AllFieldsSearch.entity().getReservationStrategy(), Op.EQ); AllFieldsSearch.and("reserverName",AllFieldsSearch.entity().getReserver(),Op.EQ); AllFieldsSearch.and("macAddress", AllFieldsSearch.entity().getMacAddress(), Op.EQ); + AllFieldsSearch.and("deviceid", AllFieldsSearch.entity().getDeviceId(), Op.EQ); AllFieldsSearch.done(); IpSearch = createSearchBuilder(String.class); @@ -222,6 +223,14 @@ public class NicDaoImpl extends GenericDaoBase implements NicDao { return findOneBy(sc); } + @Override + public NicVO findFirstNicForVM(long instanceId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("instance", instanceId); + sc.setParameters("deviceid", 0); + return findOneBy(sc); + } + @Override public NicVO getControlNicForVM(long vmId){ SearchCriteria sc = AllFieldsSearch.create(); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 8129a84dc20..c7506f57c40 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -107,6 +107,7 @@ import com.cloud.agent.dao.impl.PropertiesStorage; import com.cloud.agent.resource.virtualnetwork.VRScripts; import com.cloud.agent.resource.virtualnetwork.VirtualRouterDeployer; import com.cloud.agent.resource.virtualnetwork.VirtualRoutingResource; +import com.cloud.agent.api.SecurityGroupRulesCmd; import com.cloud.dc.Vlan; import com.cloud.exception.InternalErrorException; import com.cloud.host.Host.Type; @@ -147,6 +148,7 @@ import com.cloud.hypervisor.kvm.storage.KVMStoragePool; import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; import com.cloud.hypervisor.kvm.storage.KVMStorageProcessor; import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.Networks.IsolationType; import com.cloud.network.Networks.RouterPrivateIpStrategy; import com.cloud.network.Networks.TrafficType; import com.cloud.resource.RequestWrapper; @@ -3567,7 +3569,117 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv return true; } - public boolean defaultNetworkRules(final Connect conn, final String vmName, final NicTO nic, final Long vmId, final String secIpStr) { + /** + * Function to destroy the security group rules applied to the nic's + * @param conn + * @param vmName + * @param nic + * @return + * true : If success + * false : If failure + */ + public boolean destroyNetworkRulesForNic(final Connect conn, final String vmName, final NicTO nic) { + if (!_canBridgeFirewall) { + return false; + } + final List nicSecIps = nic.getNicSecIps(); + String secIpsStr; + final StringBuilder sb = new StringBuilder(); + if (nicSecIps != null) { + for (final String ip : nicSecIps) { + sb.append(ip).append(SecurityGroupRulesCmd.RULE_COMMAND_SEPARATOR); + } + secIpsStr = sb.toString(); + } else { + secIpsStr = "0" + SecurityGroupRulesCmd.RULE_COMMAND_SEPARATOR; + } + final List intfs = getInterfaces(conn, vmName); + if (intfs.size() == 0 || intfs.size() < nic.getDeviceId()) { + return false; + } + + final InterfaceDef intf = intfs.get(nic.getDeviceId()); + final String brname = intf.getBrName(); + final String vif = intf.getDevName(); + + final Script cmd = new Script(_securityGroupPath, _timeout, s_logger); + cmd.add("destroy_network_rules_for_vm"); + cmd.add("--vmname", vmName); + if (nic.getIp() != null) { + cmd.add("--vmip", nic.getIp()); + } + cmd.add("--vmmac", nic.getMac()); + cmd.add("--vif", vif); + cmd.add("--nicsecips", secIpsStr); + + final String result = cmd.execute(); + if (result != null) { + return false; + } + return true; + } + + /** + * Function to apply default network rules for a VM + * @param conn + * @param vm + * @param checkBeforeApply + * @return + */ + public boolean applyDefaultNetworkRules(final Connect conn, final VirtualMachineTO vm, final boolean checkBeforeApply) { + NicTO[] nicTOs = new NicTO[] {}; + if (vm != null && vm.getNics() != null) { + s_logger.debug("Checking default network rules for vm " + vm.getName()); + nicTOs = vm.getNics(); + } + for (NicTO nic : nicTOs) { + if (vm.getType() != VirtualMachine.Type.User) { + nic.setPxeDisable(true); + } + } + boolean isFirstNic = true; + for (final NicTO nic : nicTOs) { + if (nic.isSecurityGroupEnabled() || nic.getIsolationUri() != null && nic.getIsolationUri().getScheme().equalsIgnoreCase(IsolationType.Ec2.toString())) { + if (vm.getType() != VirtualMachine.Type.User) { + configureDefaultNetworkRulesForSystemVm(conn, vm.getName()); + break; + } + if (!applyDefaultNetworkRulesOnNic(conn, vm.getName(), vm.getId(), nic, isFirstNic, checkBeforeApply)) { + s_logger.error("Unable to apply default network rule for nic " + nic.getName() + " for VM " + vm.getName()); + return false; + } + isFirstNic = false; + } + } + return true; + } + + /** + * Function to apply default network rules for a NIC + * @param conn + * @param vmName + * @param vmId + * @param nic + * @param isFirstNic + * @param checkBeforeApply + * @return + */ + public boolean applyDefaultNetworkRulesOnNic(final Connect conn, final String vmName, final Long vmId, final NicTO nic, boolean isFirstNic, boolean checkBeforeApply) { + final List nicSecIps = nic.getNicSecIps(); + String secIpsStr; + final StringBuilder sb = new StringBuilder(); + if (nicSecIps != null) { + for (final String ip : nicSecIps) { + sb.append(ip).append(SecurityGroupRulesCmd.RULE_COMMAND_SEPARATOR); + } + secIpsStr = sb.toString(); + } else { + secIpsStr = "0" + SecurityGroupRulesCmd.RULE_COMMAND_SEPARATOR; + } + return defaultNetworkRules(conn, vmName, nic, vmId, secIpsStr, isFirstNic, checkBeforeApply); + } + + public boolean defaultNetworkRules(final Connect conn, final String vmName, final NicTO nic, final Long vmId, final String secIpStr, final boolean isFirstNic, final boolean checkBeforeApply) { if (!_canBridgeFirewall) { return false; } @@ -3595,6 +3707,12 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv cmd.add("--vif", vif); cmd.add("--brname", brname); cmd.add("--nicsecips", secIpStr); + if (isFirstNic) { + cmd.add("--isFirstNic"); + } + if (checkBeforeApply) { + cmd.add("--check"); + } final String result = cmd.execute(); if (result != null) { return false; @@ -3684,7 +3802,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv return true; } - public boolean configureNetworkRulesVMSecondaryIP(final Connect conn, final String vmName, final String secIp, final String action) { + public boolean configureNetworkRulesVMSecondaryIP(final Connect conn, final String vmName, final String vmMac, final String secIp, final String action) { if (!_canBridgeFirewall) { return false; @@ -3693,6 +3811,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv final Script cmd = new Script(_securityGroupPath, _timeout, s_logger); cmd.add("network_rules_vmSecondaryIp"); cmd.add("--vmname", vmName); + cmd.add("--vmmac", vmMac); cmd.add("--nicsecips", secIp); cmd.add("--action=" + action); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtNetworkRulesVmSecondaryIpCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtNetworkRulesVmSecondaryIpCommandWrapper.java index 1ad5cb459cf..07c091ee0ee 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtNetworkRulesVmSecondaryIpCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtNetworkRulesVmSecondaryIpCommandWrapper.java @@ -41,11 +41,11 @@ public final class LibvirtNetworkRulesVmSecondaryIpCommandWrapper extends Comman final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper(); final Connect conn = libvirtUtilitiesHelper.getConnectionByVmName(command.getVmName()); - result = libvirtComputingResource.configureNetworkRulesVMSecondaryIP(conn, command.getVmName(), command.getVmSecIp(), command.getAction()); + result = libvirtComputingResource.configureNetworkRulesVMSecondaryIP(conn, command.getVmName(), command.getVmMac(), command.getVmSecIp(), command.getAction()); } catch (final LibvirtException e) { s_logger.debug("Could not configure VM secondary IP! => " + e.getLocalizedMessage()); } return new Answer(command, result, ""); } -} \ No newline at end of file +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPlugNicCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPlugNicCommandWrapper.java index 1ef32afccbf..553a71a30df 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPlugNicCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPlugNicCommandWrapper.java @@ -29,6 +29,7 @@ import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef; import com.cloud.hypervisor.kvm.resource.VifDriver; import com.cloud.resource.CommandWrapper; import com.cloud.resource.ResourceWrapper; +import com.cloud.vm.VirtualMachine; import org.apache.log4j.Logger; import org.libvirt.Connect; import org.libvirt.Domain; @@ -45,6 +46,7 @@ public final class LibvirtPlugNicCommandWrapper extends CommandWrapper nicSecIps = nic.getNicSecIps(); - String secIpsStr; - final StringBuilder sb = new StringBuilder(); - if (nicSecIps != null) { - for (final String ip : nicSecIps) { - sb.append(ip).append(";"); - } - secIpsStr = sb.toString(); - } else { - secIpsStr = "0;"; - } - libvirtComputingResource.defaultNetworkRules(conn, vmName, nic, vmSpec.getId(), secIpsStr); - } - } - } + libvirtComputingResource.applyDefaultNetworkRules(conn, vmSpec, false); // pass cmdline info to system vms if (vmSpec.getType() != VirtualMachine.Type.User) { String controlIp = null; - for (final NicTO nic : nics) { + for (final NicTO nic : vmSpec.getNics()) { if (nic.getType() == TrafficType.Control) { controlIp = nic.getIp(); break; diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUnPlugNicCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUnPlugNicCommandWrapper.java index 57f4083c96f..071352c5c9a 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUnPlugNicCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUnPlugNicCommandWrapper.java @@ -55,6 +55,9 @@ public final class LibvirtUnPlugNicCommandWrapper extends CommandWrapper affectedVms, final boolean updateSeqno, Long delayMs) { if (affectedVms.size() == 0) { return; @@ -514,8 +518,35 @@ public class SecurityGroupManagerImpl extends ManagerBase implements SecurityGro egressResult.add(ipPortAndProto); } } - return new SecurityGroupRulesCmd(guestIp, guestIp6, guestMac, vmName, vmId, signature, seqnum, ingressResult.toArray(new IpPortAndProto[ingressResult.size()]), + SecurityGroupRulesCmd cmd = new SecurityGroupRulesCmd(guestIp, guestIp6, guestMac, vmName, vmId, signature, seqnum, ingressResult.toArray(new IpPortAndProto[ingressResult.size()]), egressResult.toArray(new IpPortAndProto[egressResult.size()]), secIps); + + final VirtualMachineTO to = getVmTO(vmId); + cmd.setVmTO(to); + return cmd; + } + + protected VirtualMachineTO getVmTO(Long vmId) { + final VMInstanceVO vm = _vmDao.findById(vmId); + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + final List nics = _nicDao.listByVmId(profile.getId()); + Collections.sort(nics, new Comparator() { + @Override + public int compare(NicVO nic1, NicVO nic2) { + Long nicId1 = Long.valueOf(nic1.getDeviceId()); + Long nicId2 = Long.valueOf(nic2.getDeviceId()); + return nicId1.compareTo(nicId2); + } + }); + for (final NicVO nic : nics) { + final Network network = _networkModel.getNetwork(nic.getNetworkId()); + final NicProfile nicProfile = + new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), null, _networkModel.isSecurityGroupSupportedInNetwork(network), + _networkModel.getNetworkTag(profile.getHypervisorType(), network)); + profile.addNic(nicProfile); + } + final VirtualMachineTO to = _itMgr.toVmTO(profile); + return to; } protected void handleVmStopped(VMInstanceVO vm) { @@ -1019,16 +1050,17 @@ public class SecurityGroupManagerImpl extends ManagerBase implements SecurityGro agentId = vm.getHostId(); if (agentId != null) { // get nic secondary ip address - String privateIp = vm.getPrivateIpAddress(); - NicVO nic = _nicDao.findByIp4AddressAndVmId(privateIp, vm.getId()); + NicVO nic = _nicDao.findFirstNicForVM(vm.getId()); List nicSecIps = null; if (nic != null) { if (nic.getSecondaryIp()) { //get secondary ips of the vm nicSecIps = _nicSecIpDao.getSecondaryIpAddressesForNic(nic.getId()); } + } else { + return; } - SecurityGroupRulesCmd cmd = generateRulesetCmd(vm.getInstanceName(), nic.getIPv6Address(), vm.getPrivateIpAddress(), vm.getPrivateMacAddress(), vm.getId(), + SecurityGroupRulesCmd cmd = generateRulesetCmd(vm.getInstanceName(), nic.getIPv4Address(), nic.getIPv6Address(), vm.getPrivateMacAddress(), vm.getId(), generateRulesetSignature(ingressRules, egressRules), seqnum, ingressRules, egressRules, nicSecIps); Commands cmds = new Commands(cmd); try { @@ -1416,7 +1448,7 @@ public class SecurityGroupManagerImpl extends ManagerBase implements SecurityGro return true; } - String vmMac = vm.getPrivateMacAddress(); + String vmMac = nic.getMacAddress(); String vmName = vm.getInstanceName(); if (vmMac == null || vmName == null) { throw new InvalidParameterValueException("vm name or vm mac can't be null"); diff --git a/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl2.java b/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl2.java index 2d0ec61d09c..5b4b85fc70c 100644 --- a/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl2.java +++ b/server/src/main/java/com/cloud/network/security/SecurityGroupManagerImpl2.java @@ -177,16 +177,17 @@ public class SecurityGroupManagerImpl2 extends SecurityGroupManagerImpl { Map> egressRules = generateRulesForVM(userVmId, SecurityRuleType.EgressRule); Long agentId = vm.getHostId(); if (agentId != null) { - String privateIp = vm.getPrivateIpAddress(); - NicVO nic = _nicDao.findByIp4AddressAndVmId(privateIp, vm.getId()); + NicVO nic = _nicDao.findFirstNicForVM(vm.getId()); List nicSecIps = null; if (nic != null) { if (nic.getSecondaryIp()) { nicSecIps = _nicSecIpDao.getSecondaryIpAddressesForNic(nic.getId()); } + } else { + return; } SecurityGroupRulesCmd cmd = - generateRulesetCmd(vm.getInstanceName(), vm.getPrivateIpAddress(), nic.getIPv6Address(), vm.getPrivateMacAddress(), vm.getId(), null, work.getLogsequenceNumber(), + generateRulesetCmd(vm.getInstanceName(), nic.getIPv4Address(), nic.getIPv6Address(), vm.getPrivateMacAddress(), vm.getId(), null, work.getLogsequenceNumber(), ingressRules, egressRules, nicSecIps); cmd.setMsId(_serverId); if (s_logger.isDebugEnabled()) { diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index ad2e453080e..3f28f1283d8 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -3225,21 +3225,23 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir throw new InvalidParameterValueException("Security group feature is not supported for vmWare hypervisor"); } // Only one network can be specified, and it should be security group enabled - if (networkIdList.size() > 1) { + if (networkIdList.size() > 1 && template.getHypervisorType() != HypervisorType.KVM && hypervisor != HypervisorType.KVM) { throw new InvalidParameterValueException("Only support one network per VM if security group enabled"); } - NetworkVO network = _networkDao.findById(networkIdList.get(0)); + for (Long networkId : networkIdList) { + NetworkVO network = _networkDao.findById(networkId); - if (network == null) { - throw new InvalidParameterValueException("Unable to find network by id " + networkIdList.get(0).longValue()); + if (network == null) { + throw new InvalidParameterValueException("Unable to find network by id " + networkId); + } + + if (!_networkModel.isSecurityGroupSupportedInNetwork(network)) { + throw new InvalidParameterValueException("Network is not security group enabled: " + network.getId()); + } + + networkList.add(network); } - - if (!_networkModel.isSecurityGroupSupportedInNetwork(network)) { - throw new InvalidParameterValueException("Network is not security group enabled: " + network.getId()); - } - - networkList.add(network); isSecurityGroupEnabledNetworkUsed = true; } else { @@ -3253,10 +3255,6 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir boolean isSecurityGroupEnabled = _networkModel.isSecurityGroupSupportedInNetwork(network); if (isSecurityGroupEnabled) { - if (networkIdList.size() > 1) { - throw new InvalidParameterValueException("Can't create a vm with multiple networks one of" + " which is Security Group enabled"); - } - isSecurityGroupEnabledNetworkUsed = true; } diff --git a/test/integration/component/test_multiple_nic_support.py b/test/integration/component/test_multiple_nic_support.py new file mode 100644 index 00000000000..cf3a2336495 --- /dev/null +++ b/test/integration/component/test_multiple_nic_support.py @@ -0,0 +1,629 @@ +# 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. +""" tests for supporting multiple NIC's in advanced zone with security groups in cloudstack 4.14.0.0 + +""" +# Import Local Modules +from nose.plugins.attrib import attr +from marvin.cloudstackTestCase import cloudstackTestCase, unittest +from marvin.sshClient import SshClient +from marvin.lib.utils import (validateList, + cleanup_resources, + get_host_credentials, + get_process_status, + execute_command_in_host, + random_gen) +from marvin.lib.base import (PhysicalNetwork, + Account, + Host, + TrafficType, + Domain, + Network, + NetworkOffering, + VirtualMachine, + ServiceOffering, + Zone, + NIC, + SecurityGroup) +from marvin.lib.common import (get_domain, + get_zone, + get_template, + list_virtual_machines, + list_routers, + list_hosts, + get_free_vlan) +from marvin.codes import (PASS, FAILED) +import logging +import random +import time + +class TestMulipleNicSupport(cloudstackTestCase): + @classmethod + def setUpClass(cls): + cls.testClient = super( + TestMulipleNicSupport, + cls).getClsTestClient() + cls.apiclient = cls.testClient.getApiClient() + cls.testdata = cls.testClient.getParsedTestDataConfig() + cls.services = cls.testClient.getParsedTestDataConfig() + zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.zone = Zone(zone.__dict__) + cls._cleanup = [] + + if str(cls.zone.securitygroupsenabled) != "True": + sys.exit(1) + + cls.logger = logging.getLogger("TestMulipleNicSupport") + cls.stream_handler = logging.StreamHandler() + cls.logger.setLevel(logging.DEBUG) + cls.logger.addHandler(cls.stream_handler) + + # Get Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.services['mode'] = cls.zone.networktype + + cls.template = get_template(cls.apiclient, cls.zone.id, hypervisor="KVM") + if cls.template == FAILED: + sys.exit(1) + + # Create new domain, account, network and VM + cls.user_domain = Domain.create( + cls.apiclient, + services=cls.testdata["acl"]["domain2"], + parentdomainid=cls.domain.id) + + # Create account + cls.account1 = Account.create( + cls.apiclient, + cls.testdata["acl"]["accountD2"], + admin=True, + domainid=cls.user_domain.id + ) + + # Create small service offering + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.testdata["service_offerings"]["small"] + ) + + cls._cleanup.append(cls.service_offering) + cls.services["network"]["zoneid"] = cls.zone.id + cls.network_offering = NetworkOffering.create( + cls.apiclient, + cls.services["network_offering"], + ) + # Enable Network offering + cls.network_offering.update(cls.apiclient, state='Enabled') + + cls._cleanup.append(cls.network_offering) + cls.testdata["virtual_machine"]["zoneid"] = cls.zone.id + cls.testdata["virtual_machine"]["template"] = cls.template.id + + if cls.zone.securitygroupsenabled: + # Enable networking for reaching to VM thorugh SSH + security_group = SecurityGroup.create( + cls.apiclient, + cls.testdata["security_group"], + account=cls.account1.name, + domainid=cls.account1.domainid + ) + + # Authorize Security group to SSH to VM + ingress_rule = security_group.authorize( + cls.apiclient, + cls.testdata["ingress_rule"], + account=cls.account1.name, + domainid=cls.account1.domainid + ) + + # Authorize Security group to SSH to VM + ingress_rule2 = security_group.authorize( + cls.apiclient, + cls.testdata["ingress_rule_ICMP"], + account=cls.account1.name, + domainid=cls.account1.domainid + ) + + cls.testdata["shared_network_offering_sg"]["specifyVlan"] = 'True' + cls.testdata["shared_network_offering_sg"]["specifyIpRanges"] = 'True' + cls.shared_network_offering = NetworkOffering.create( + cls.apiclient, + cls.testdata["shared_network_offering_sg"], + conservemode=False + ) + + NetworkOffering.update( + cls.shared_network_offering, + cls.apiclient, + id=cls.shared_network_offering.id, + state="enabled" + ) + + physical_network, vlan = get_free_vlan(cls.apiclient, cls.zone.id) + cls.testdata["shared_network_sg"]["physicalnetworkid"] = physical_network.id + + random_subnet_number = random.randrange(90, 99) + cls.testdata["shared_network_sg"]["name"] = "Shared-Network-SG-Test-vlan" + str(random_subnet_number) + cls.testdata["shared_network_sg"]["displaytext"] = "Shared-Network-SG-Test-vlan" + str(random_subnet_number) + cls.testdata["shared_network_sg"]["vlan"] = "vlan://" + str(random_subnet_number) + cls.testdata["shared_network_sg"]["startip"] = "192.168." + str(random_subnet_number) + ".240" + cls.testdata["shared_network_sg"]["endip"] = "192.168." + str(random_subnet_number) + ".250" + cls.testdata["shared_network_sg"]["gateway"] = "192.168." + str(random_subnet_number) + ".254" + cls.network1 = Network.create( + cls.apiclient, + cls.testdata["shared_network_sg"], + networkofferingid=cls.shared_network_offering.id, + zoneid=cls.zone.id, + accountid=cls.account1.name, + domainid=cls.account1.domainid + ) + + random_subnet_number = random.randrange(100, 110) + cls.testdata["shared_network_sg"]["name"] = "Shared-Network-SG-Test-vlan" + str(random_subnet_number) + cls.testdata["shared_network_sg"]["displaytext"] = "Shared-Network-SG-Test-vlan" + str(random_subnet_number) + cls.testdata["shared_network_sg"]["vlan"] = "vlan://" + str(random_subnet_number) + cls.testdata["shared_network_sg"]["startip"] = "192.168." + str(random_subnet_number) + ".240" + cls.testdata["shared_network_sg"]["endip"] = "192.168." + str(random_subnet_number) + ".250" + cls.testdata["shared_network_sg"]["gateway"] = "192.168." + str(random_subnet_number) + ".254" + cls.network2 = Network.create( + cls.apiclient, + cls.testdata["shared_network_sg"], + networkofferingid=cls.shared_network_offering.id, + zoneid=cls.zone.id, + accountid=cls.account1.name, + domainid=cls.account1.domainid + ) + + random_subnet_number = random.randrange(111, 120) + cls.testdata["shared_network_sg"]["name"] = "Shared-Network-SG-Test-vlan" + str(random_subnet_number) + cls.testdata["shared_network_sg"]["displaytext"] = "Shared-Network-SG-Test-vlan" + str(random_subnet_number) + cls.testdata["shared_network_sg"]["vlan"] = "vlan://" + str(random_subnet_number) + cls.testdata["shared_network_sg"]["startip"] = "192.168." + str(random_subnet_number) + ".240" + cls.testdata["shared_network_sg"]["endip"] = "192.168." + str(random_subnet_number) + ".250" + cls.testdata["shared_network_sg"]["gateway"] = "192.168." + str(random_subnet_number) + ".254" + cls.network3 = Network.create( + cls.apiclient, + cls.testdata["shared_network_sg"], + networkofferingid=cls.shared_network_offering.id, + zoneid=cls.zone.id, + accountid=cls.account1.name, + domainid=cls.account1.domainid + ) + + try: + cls.virtual_machine1 = VirtualMachine.create( + cls.apiclient, + cls.testdata["virtual_machine"], + accountid=cls.account1.name, + domainid=cls.account1.domainid, + serviceofferingid=cls.service_offering.id, + templateid=cls.template.id, + securitygroupids=[security_group.id], + networkids=cls.network1.id + ) + for nic in cls.virtual_machine1.nic: + if nic.isdefault: + cls.virtual_machine1.ssh_ip = nic.ipaddress + cls.virtual_machine1.default_network_id = nic.networkid + break + except Exception as e: + cls.fail("Exception while deploying virtual machine: %s" % e) + + try: + cls.virtual_machine2 = VirtualMachine.create( + cls.apiclient, + cls.testdata["virtual_machine"], + accountid=cls.account1.name, + domainid=cls.account1.domainid, + serviceofferingid=cls.service_offering.id, + templateid=cls.template.id, + securitygroupids=[security_group.id], + networkids=[str(cls.network1.id), str(cls.network2.id)] + ) + for nic in cls.virtual_machine2.nic: + if nic.isdefault: + cls.virtual_machine2.ssh_ip = nic.ipaddress + cls.virtual_machine2.default_network_id = nic.networkid + break + except Exception as e: + cls.fail("Exception while deploying virtual machine: %s" % e) + + cls._cleanup.append(cls.virtual_machine1) + cls._cleanup.append(cls.virtual_machine2) + cls._cleanup.append(cls.network1) + cls._cleanup.append(cls.network2) + cls._cleanup.append(cls.network3) + cls._cleanup.append(cls.shared_network_offering) + if cls.zone.securitygroupsenabled: + cls._cleanup.append(security_group) + cls._cleanup.append(cls.account1) + cls._cleanup.append(cls.user_domain) + + @classmethod + def tearDownClass(self): + try: + cleanup_resources(self.apiclient, self._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.cleanup = [] + return + + def tearDown(self): + try: + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def verify_network_rules(self, vm_id): + virtual_machine = VirtualMachine.list( + self.apiclient, + id=vm_id + ) + vm = virtual_machine[0] + hosts = list_hosts( + self.apiclient, + id=vm.hostid + ) + host = hosts[0] + if host.hypervisor.lower() not in "kvm": + return + host.user, host.password = get_host_credentials(self.config, host.ipaddress) + for nic in vm.nic: + secips = "" + if len(nic.secondaryip) > 0: + for secip in nic.secondaryip: + secips += secip.ipaddress + ";" + command="/usr/share/cloudstack-common/scripts/vm/network/security_group.py verify_network_rules --vmname %s --vmip %s --vmmac %s --nicsecips '%s'" % (vm.instancename, nic.ipaddress, nic.macaddress, secips) + self.logger.debug("Executing command '%s' in host %s" % (command, host.ipaddress)) + result=execute_command_in_host(host.ipaddress, 22, + host.user, + host.password, + command) + if len(result) > 0: + self.fail("The iptables/ebtables rules for nic %s on vm %s on host %s are not correct" %(nic.ipaddress, vm.instancename, host.name)) + + @attr(tags=["adeancedsg"], required_hardware="false") + def test_01_create_vm_with_multiple_nics(self): + """Create Vm with multiple NIC's + + Steps: + # 1. Create more than 1 isolated or shared network + # 2. Create a vm and select more than 1 network while deploying + # 3. Vm is deployed successfully with 1 nic from each network + # 4. All the vm's should be pingable + :return: + """ + virtual_machine = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine2.id + ) + self.assertEqual( + len(virtual_machine), 1, + "Virtual Machine create with 2 NIC's failed") + + nicIdInVm = virtual_machine[0].nic[0] + self.assertIsNotNone(nicIdInVm, "NIC 1 not found in Virtual Machine") + + nicIdInVm = virtual_machine[0].nic[1] + self.assertIsNotNone(nicIdInVm, "NIC 2 not found in Virtual Machine") + + self.verify_network_rules(self.virtual_machine2.id) + + @attr(tags=["advancedsg"], required_hardware="false") + def test_02_add_nic_to_vm(self): + """Create VM with single NIC and then add additional NIC + + Steps: + # 1. Create a VM by selecting one default NIC + # 2. Create few more isolated or shared networks + # 3. Add extra NIC's to the vm from the newly created networks + # 4. The deployed VM should have extra nic's added in the above + # step without any fail + # 5. The IP's of the extra NIC's should be pingable + :return: + """ + self.virtual_machine1.add_nic(self.apiclient, self.network2.id) + + virtual_machine = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine1.id + ) + + nicIdInVm = virtual_machine[0].nic[1] + self.assertIsNotNone(nicIdInVm, "Second NIC not found") + + self.verify_network_rules(self.virtual_machine1.id) + + @attr(tags=["advancedsg"], required_hardware="false") + def test_03_add_ip_to_default_nic(self): + """ Add secondary IP's to the VM + + Steps: + # 1. Create a VM with more than 1 NIC + # 2) Navigate to Instances->NIC->Edit Secondary IP's + # ->Aquire new Secondary IP" + # 3) Add as many secondary Ip as possible to the VM + # 4) Configure the secondary IP's by referring to "Configure + # the secondary IP's" in the "Action Item" section + :return: + """ + ipaddress = NIC.addIp( + self.apiclient, + id=self.virtual_machine2.nic[0].id + ) + + self.assertIsNotNone( + ipaddress, + "Unable to add secondary IP to the default NIC") + + self.verify_network_rules(self.virtual_machine2.id) + + @attr(tags=["advancedsg"], required_hardware="false") + def test_04_add_ip_to_remaining_nics(self): + """ Add secondary IP's to remaining NIC's + + Steps: + # 1) Create a VM with more than 1 NIC + # 2)Navigate to Instances-NIC's->Edit Secondary IP's + # ->Acquire new Secondary IP + # 3) Add secondary IP to all the NIC's of the VM + # 4) Confiugre the secondary IP's by referring to "Configure the + # secondary IP's" in the "Action Item" section + :return: + """ + + self.virtual_machine1.add_nic(self.apiclient, self.network3.id) + + vms = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine1.id + ) + + self.assertIsNotNone( + vms[0].nic[2], + "Third NIC is not added successfully to the VM") + + vms1_nic1_id = vms[0].nic[1]['id'] + vms1_nic2_id = vms[0].nic[2]['id'] + + ipaddress21 = NIC.addIp( + self.apiclient, + id=vms1_nic1_id + ) + + ipaddress22 = NIC.addIp( + self.apiclient, + id=vms1_nic1_id + ) + + self.assertIsNotNone( + ipaddress21, + "Unable to add first secondary IP to the second nic") + self.assertIsNotNone( + ipaddress22, + "Unable to add second secondary IP to second NIC") + + ipaddress31 = NIC.addIp( + self.apiclient, + id=vms1_nic2_id + ) + + ipaddress32 = NIC.addIp( + self.apiclient, + id=vms1_nic2_id + ) + + self.assertIsNotNone( + ipaddress31, + "Unable to add first secondary IP to third NIC") + self.assertIsNotNone( + ipaddress32, + "Unable to add second secondary IP to third NIC") + + self.verify_network_rules(self.virtual_machine1.id) + + @attr(tags=["advancedsg"], required_hardware="false") + def test_05_stop_start_vm_with_multiple_nic(self): + """ Stop and Start a VM with Multple NIC + + Steps: + # 1) Create a Vm with multiple NIC's + # 2) Configure secondary IP's on the VM + # 3) Try to stop/start the VM + # 4) Ping the IP's of the vm + # 5) Remove Secondary IP from one of the NIC + :return: + """ + ipaddress1 = NIC.addIp( + self.apiclient, + id=self.virtual_machine2.nic[0].id + ) + + ipaddress2 = NIC.addIp( + self.apiclient, + id=self.virtual_machine2.nic[1].id + ) + # Stop the VM with multiple NIC's + self.virtual_machine2.stop(self.apiclient) + + virtual_machine = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine2.id + ) + + self.assertEqual( + virtual_machine[0]['state'], 'Stopped', + "Could not stop the VM with multiple NIC's") + + if virtual_machine[0]['state'] == 'Stopped': + # If stopped then try to start the VM + self.virtual_machine2.start(self.apiclient) + virtual_machine = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine2.id + ) + self.assertEqual( + virtual_machine[0]['state'], 'Running', + "Could not start the VM with multiple NIC's") + + self.verify_network_rules(self.virtual_machine2.id) + + @attr(tags=["advancedsg"], required_hardware="false") + def test_06_migrate_vm_with_multiple_nic(self): + """ Migrate a VM with Multple NIC + + Steps: + # 1) Create a Vm with multiple NIC's + # 2) Configure secondary IP's on the VM + # 3) Try to stop/start the VM + # 4) Ping the IP's of the vm + :return: + """ + # Skipping adding Secondary IP to NIC since its already + # done in the previous test cases + + virtual_machine = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine1.id + ) + old_host_id = virtual_machine[0]['hostid'] + + try: + hosts = Host.list( + self.apiclient, + virtualmachineid=self.virtual_machine1.id, + listall=True) + self.assertEqual( + validateList(hosts)[0], + PASS, + "hosts list validation failed") + + # Get a host which is not already assigned to VM + for host in hosts: + if host.id == old_host_id: + continue + else: + host_id = host.id + break + + self.virtual_machine1.migrate(self.apiclient, host_id) + except Exception as e: + self.fail("Exception occured: %s" % e) + + # List the vm again + virtual_machine = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine1.id) + + new_host_id = virtual_machine[0]['hostid'] + + self.assertNotEqual( + old_host_id, new_host_id, + "Migration of VM to new host failed" + ) + + self.verify_network_rules(self.virtual_machine1.id) + + @attr(tags=["advancedsg"], required_hardware="false") + def test_07_remove_secondary_ip_from_nic(self): + """ Remove secondary IP from any NIC + Steps: + # 1) Navigate to Instances + # 2) Select any vm + # 3) NIC's ->Edit secondary IP's->Release IP + # 4) The secondary IP should be successfully removed + """ + virtual_machine = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine2.id) + + # Check which NIC is having secondary IP + secondary_ips = virtual_machine[0].nic[1].secondaryip + + for secondary_ip in secondary_ips: + NIC.removeIp(self.apiclient, ipaddressid=secondary_ip['id']) + + virtual_machine = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine2.id + ) + + self.assertFalse( + virtual_machine[0].nic[1].secondaryip, + 'Failed to remove secondary IP') + + self.verify_network_rules(self.virtual_machine2.id) + + @attr(tags=["advancedsg"], required_hardware="false") + def test_08_remove_nic_from_vm(self): + """ Remove NIC from VM + Steps: + # 1) Navigate to Instances->select any vm->NIC's->NIC 2 + # ->Click on "X" button to remove the second NIC + # 2) Remove other NIC's as well from the VM + # 3) All the NIC's should be successfully removed from the VM + :return: + """ + virtual_machine = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine2.id) + + for nic in virtual_machine[0].nic: + if nic.isdefault: + continue + self.virtual_machine2.remove_nic(self.apiclient, nic.id) + + virtual_machine = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine2.id) + + self.assertEqual( + len(virtual_machine[0].nic), 1, + "Failed to remove all the nics from the virtual machine") + + self.verify_network_rules(self.virtual_machine2.id) + + @attr(tags=["advancedsg"], required_hardware="false") + def test_09_reboot_vm_with_multiple_nic(self): + """ Reboot a VM with Multple NIC + + Steps: + # 1) Create a Vm with multiple NIC's + # 2) Configure secondary IP's on the VM + # 3) Try to reboot the VM + # 4) Ping the IP's of the vm + :return: + """ + # Skipping adding Secondary IP to NIC since its already + # done in the previous test cases + + virtual_machine = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine1.id + ) + try: + self.virtual_machine1.reboot(self.apiclient) + except Exception as e: + self.fail("Exception occured: %s" % e) + + self.verify_network_rules(self.virtual_machine1.id) + diff --git a/tools/marvin/marvin/lib/utils.py b/tools/marvin/marvin/lib/utils.py index 1ad457ccf5b..f170e0dc3bf 100644 --- a/tools/marvin/marvin/lib/utils.py +++ b/tools/marvin/marvin/lib/utils.py @@ -62,13 +62,15 @@ def _configure_timeout(hypervisor): return timeout -def _execute_ssh_command(hostip, port, username, password, ssh_command): +def _execute_ssh_command(hostip, port, username, password, ssh_command, timeout=5): #SSH to the machine ssh = SshClient(hostip, port, username, password) # Ensure the SSH login is successful while True: res = ssh.execute(ssh_command) - if "Connection refused".lower() in res[0].lower(): + if len(res) == 0: + return res + elif "Connection refused".lower() in res[0].lower(): pass elif res[0] != "Host key verification failed.": break @@ -228,6 +230,10 @@ def get_host_credentials(config, hostip): raise Exception("Unresolvable host %s error is %s" % (hostip, e)) raise KeyError("Please provide the marvin configuration file with credentials to your hosts") +def execute_command_in_host(hostip, port, username, password, command, hypervisor=None): + timeout = _configure_timeout(hypervisor) + result = _execute_ssh_command(hostip, port, username, password, command) + return result def get_process_status(hostip, port, username, password, linklocalip, command, hypervisor=None): """Double hop and returns a command execution result""" diff --git a/ui/scripts/instanceWizard.js b/ui/scripts/instanceWizard.js index a6fdfbb3b95..04cceaa9d06 100644 --- a/ui/scripts/instanceWizard.js +++ b/ui/scripts/instanceWizard.js @@ -168,11 +168,16 @@ }); } - return $.grep(selectedNetworks, function(network) { + var total = $.grep(selectedNetworks, function(network) { return $.grep(network.service, function(service) { return service.name == 'SecurityGroup'; }).length; }).length; //return total number of selected sg networks + + if (total > 0 && selectedHypervisor == "KVM") { + return -1; // vm with multiple IPs is supported in KVM + } + return total; }, // Data providers for each wizard step @@ -1284,8 +1289,10 @@ if (selectedZoneObj.networktype == "Advanced" && selectedZoneObj.securitygroupsenabled == true) { // Advanced SG-enabled zone var array2 = []; + var array3 = []; var myNetworks = $('.multi-wizard:visible form').data('my-networks'); //widget limitation: If using an advanced security group zone, get the guest networks like this - var defaultNetworkId = $('.multi-wizard:visible form').find('input[name=defaultNetwork]:checked').val(); + var defaultNetworkId = $('.multi-wizard:visible form').data('defaultNetwork'); + //var defaultNetworkId = $('.multi-wizard:visible form').find('input[name=defaultNetwork]:checked').val(); var checkedNetworkIdArray; if (typeof(myNetworks) == "object" && myNetworks.length != null) { //myNetworks is an array of string, e.g. ["203", "202"], @@ -1302,17 +1309,43 @@ array2.push(defaultNetworkId); } - //then, add other checked networks + var myNetworkIps = $('.multi-wizard:visible form').data('my-network-ips'); if (checkedNetworkIdArray.length > 0) { for (var i = 0; i < checkedNetworkIdArray.length; i++) { - if (checkedNetworkIdArray[i] != defaultNetworkId) //exclude defaultNetworkId that has been added to array2 + if (checkedNetworkIdArray[i] == defaultNetworkId) { + array2.unshift(defaultNetworkId); + + var ipToNetwork = { + networkid: defaultNetworkId + }; + if (myNetworkIps[i] != null && myNetworkIps[i].length > 0) { + $.extend(ipToNetwork, { + ip: myNetworkIps[i] + }); + } + array3.unshift(ipToNetwork); + } else { array2.push(checkedNetworkIdArray[i]); + + var ipToNetwork = { + networkid: checkedNetworkIdArray[i] + }; + if (myNetworkIps[i] != null && myNetworkIps[i].length > 0) { + $.extend(ipToNetwork, { + ip: myNetworkIps[i] + }); + } + array3.push(ipToNetwork); + } } } - $.extend(deployVmData, { - networkids : array2.join(",") - }); + for (var k = 0; k < array3.length; k++) { + deployVmData["iptonetworklist[" + k + "].networkid"] = array3[k].networkid; + if (array3[k].ip != undefined && array3[k].ip.length > 0) { + deployVmData["iptonetworklist[" + k + "].ip"] = array3[k].ip; + } + } } } else if (step6ContainerType == 'nothing-to-select') { if ("vpc" in args.context) { //from VPC tier diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js index 545f7428faa..c6fb671e184 100644 --- a/ui/scripts/instances.js +++ b/ui/scripts/instances.js @@ -3354,6 +3354,16 @@ label: 'label.description' } }], + viewAll: { + path: 'network.securityGroups', + attachTo: 'id', + label: 'label.security.groups', + title: function(args) { + var title = _l('label.security.groups'); + + return title; + } + }, dataProvider: function(args) { // args.response.success({data: args.context.instances[0].securitygroup}); $.ajax({ diff --git a/ui/scripts/network.js b/ui/scripts/network.js index 32671f5cea1..77cce337f0c 100644 --- a/ui/scripts/network.js +++ b/ui/scripts/network.js @@ -4504,6 +4504,14 @@ var data = {}; listViewDataProvider(args, data); + if (args.context != null) { + if ("securityGroups" in args.context) { + $.extend(data, { + id: args.context.securityGroups[0].id + }); + } + } + $.ajax({ url: createURL('listSecurityGroups'), data: data, diff --git a/ui/scripts/ui-custom/instanceWizard.js b/ui/scripts/ui-custom/instanceWizard.js index b732b9ccf4b..2450ed1b4a8 100644 --- a/ui/scripts/ui-custom/instanceWizard.js +++ b/ui/scripts/ui-custom/instanceWizard.js @@ -1516,14 +1516,29 @@ if (advSGFilter == 0) { //when total number of selected sg networks is 0, then 'Select Security Group' is skipped, go to step 6 directly showStep(6); - } else { //when total number of selected sg networks > 0 + } else if (advSGFilter > 0) { //when total number of selected sg networks > 0 if ($activeStep.find('input[type=checkbox]:checked').length > 1) { //when total number of selected networks > 1 cloudStack.dialog.notice({ message: "Can't create a vm with multiple networks one of which is Security Group enabled" }); return false; } + } else if (advSGFilter == -1) { // vm with multiple IPs is supported in KVM + var $selectNetwork = $activeStep.find('input[type=checkbox]:checked'); + var myNetworkIps = []; + $selectNetwork.each(function() { + var $specifyIp = $(this).parent().find('.specify-ip input[type=text]'); + myNetworkIps.push($specifyIp.val() == -1 ? null : $specifyIp.val()); + }); + $activeStep.closest('form').data('my-network-ips', myNetworkIps); + $selectNetwork.each(function() { + if ($(this).parent().find('input[type=radio]').is(':checked')) { + $activeStep.closest('form').data('defaultNetwork', $(this).val()); + return; + } + }) } + } }