From 807dc091382fbf6fbd4b8fc53e7cdb414078ea2e Mon Sep 17 00:00:00 2001 From: Rajesh Battala Date: Tue, 28 Jan 2014 06:40:47 +0530 Subject: [PATCH] CLOUDSTACK-5561 Support of multiple public vlans on VR running in HyperV --- .../cloud/agent/api/GetVmConfigAnswer.java | 68 +++++ .../cloud/agent/api/GetVmConfigCommand.java | 46 ++++ .../agent/api/ModifyVmNicConfigAnswer.java | 36 +++ .../agent/api/ModifyVmNicConfigCommand.java | 42 +++ .../ServerResource/AgentShell/App.config | 2 +- .../HypervResource/CloudStackTypes.cs | 18 ++ .../HypervResourceController.cs | 100 +++++++ .../HypervResource/IWmiCallsV2.cs | 1 + .../HypervResource/WmiCallsV2.cs | 62 +++++ .../hypervisor/hyperv/guru/HypervGuru.java | 113 ++++++++ .../hyperv/manager/HypervManager.java | 1 + .../hyperv/manager/HypervManagerImpl.java | 20 +- .../resource/HypervDirectConnectResource.java | 258 +++++++++++++----- .../VirtualNetworkApplianceManagerImpl.java | 2 +- .../debian/config/opt/cloud/bin/ipassoc.sh | 3 +- 15 files changed, 697 insertions(+), 75 deletions(-) create mode 100644 core/src/com/cloud/agent/api/GetVmConfigAnswer.java create mode 100644 core/src/com/cloud/agent/api/GetVmConfigCommand.java create mode 100644 core/src/com/cloud/agent/api/ModifyVmNicConfigAnswer.java create mode 100644 core/src/com/cloud/agent/api/ModifyVmNicConfigCommand.java diff --git a/core/src/com/cloud/agent/api/GetVmConfigAnswer.java b/core/src/com/cloud/agent/api/GetVmConfigAnswer.java new file mode 100644 index 00000000000..46e003ecae3 --- /dev/null +++ b/core/src/com/cloud/agent/api/GetVmConfigAnswer.java @@ -0,0 +1,68 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.agent.api; + +import java.util.List; + +public class GetVmConfigAnswer extends Answer { + + String vmName; + List nics; + + protected GetVmConfigAnswer() { + } + + public GetVmConfigAnswer(String vmName, List nics) { + this.vmName = vmName; + this.nics = nics; + } + + public String getVmName() { + return vmName; + } + + public List getNics() { + return nics; + } + + public class NicDetails { + String macAddress; + int vlanid; + + public NicDetails() { + } + + public NicDetails(String macAddress, int vlanid) { + this.macAddress = macAddress; + this.vlanid = vlanid; + } + + public String getMacAddress() { + return macAddress; + } + + public int getVlanid() { + return vlanid; + } + + } + + @Override + public boolean executeInSequence() { + return false; + } +} \ No newline at end of file diff --git a/core/src/com/cloud/agent/api/GetVmConfigCommand.java b/core/src/com/cloud/agent/api/GetVmConfigCommand.java new file mode 100644 index 00000000000..9133bd6b2c3 --- /dev/null +++ b/core/src/com/cloud/agent/api/GetVmConfigCommand.java @@ -0,0 +1,46 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.agent.api; + + +import java.util.List; + +import com.cloud.agent.api.to.NicTO; + +public class GetVmConfigCommand extends Command { + String vmName; + List nics; + protected GetVmConfigCommand() { + } + + public GetVmConfigCommand(String vmName) { + this.vmName = vmName; + } + + public String getVmName() { + return vmName; + } + + public void setNics(List nics){ + this.nics = nics; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/com/cloud/agent/api/ModifyVmNicConfigAnswer.java b/core/src/com/cloud/agent/api/ModifyVmNicConfigAnswer.java new file mode 100644 index 00000000000..1140485c67c --- /dev/null +++ b/core/src/com/cloud/agent/api/ModifyVmNicConfigAnswer.java @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.agent.api; + +public class ModifyVmNicConfigAnswer extends Answer { + String vmName; + protected ModifyVmNicConfigAnswer() { + } + + public ModifyVmNicConfigAnswer(String vmName) { + this.vmName = vmName; + } + + public String getVmName() { + return vmName; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/com/cloud/agent/api/ModifyVmNicConfigCommand.java b/core/src/com/cloud/agent/api/ModifyVmNicConfigCommand.java new file mode 100644 index 00000000000..0230beca732 --- /dev/null +++ b/core/src/com/cloud/agent/api/ModifyVmNicConfigCommand.java @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.agent.api; + + +public class ModifyVmNicConfigCommand extends Command { + String vmName; + int vlan; + String macAddress; + protected ModifyVmNicConfigCommand() { + } + + public ModifyVmNicConfigCommand(String vmName, int vlan, String macAddress) { + this.vmName = vmName; + this.vlan = vlan; + this.macAddress = macAddress; + } + + public String getVmName() { + return vmName; + } + + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/App.config b/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/App.config index b783dfecd63..3ec08bd29c9 100644 --- a/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/App.config +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/AgentShell/App.config @@ -96,7 +96,7 @@ 2048 - 10.102.192.150 + 0.0.0.0 diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/CloudStackTypes.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/CloudStackTypes.cs index d54295ccdaa..c336a3821e3 100644 --- a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/CloudStackTypes.cs +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/CloudStackTypes.cs @@ -690,6 +690,20 @@ namespace HypervResource public String entityType; } + public class NicDetails + { + [JsonProperty("macAddress")] + public string macaddress; + [JsonProperty("vlanid")] + public int vlanid; + public NicDetails() { } + public NicDetails(String macaddress, int vlanid) + { + this.macaddress = macaddress; + this.vlanid = vlanid; + } + } + /// /// Fully qualified named for a number of types used in CloudStack. Used to specify the intended type for JSON serialised objects. /// @@ -738,6 +752,10 @@ namespace HypervResource public const string GetVmDiskStatsCommand = "com.cloud.agent.api.GetVmDiskStatsCommand"; public const string GetVmStatsAnswer = "com.cloud.agent.api.GetVmStatsAnswer"; public const string GetVmStatsCommand = "com.cloud.agent.api.GetVmStatsCommand"; + public const string GetVmConfigCommand = "com.cloud.agent.api.GetVmConfigCommand"; + public const string GetVmConfigAnswer = "com.cloud.agent.api.GetVmConfigAnswer"; + public const string ModifyVmNicConfigCommand = "com.cloud.agent.api.ModifyVmNicConfigCommand"; + public const string ModifyVmNicConfigAnswer = "com.cloud.agent.api.ModifyVmNicConfigAnswer"; public const string GetVncPortAnswer = "com.cloud.agent.api.GetVncPortAnswer"; public const string GetVncPortCommand = "com.cloud.agent.api.GetVncPortCommand"; public const string HostStatsEntry = "com.cloud.agent.api.HostStatsEntry"; diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs index f7787c32c52..ebb3bce2cf8 100644 --- a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/HypervResourceController.cs @@ -982,6 +982,24 @@ namespace HypervResource return true; } + // POST api/HypervResource/PlugNicCommand + [HttpPost] + [ActionName(CloudStackTypes.PlugNicCommand)] + public JContainer PlugNicCommand([FromBody]dynamic cmd) + { + using (log4net.NDC.Push(Guid.NewGuid().ToString())) + { + logger.Info(CloudStackTypes.PlugNicCommand + cmd.ToString()); + object ansContent = new + { + result = true, + details = "instead of plug, change he network settings", + contextMap = contextMap + }; + return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.PlugNicAnswer); + } + } + // POST api/HypervResource/CleanupNetworkRulesCmd [HttpPost] @@ -1264,6 +1282,88 @@ namespace HypervResource } } + // POST api/HypervResource/ModifyVmVnicVlanCommand + [HttpPost] + [ActionName(CloudStackTypes.ModifyVmNicConfigCommand)] + public JContainer ModifyVmNicConfigCommand([FromBody]dynamic cmd) + { + + using (log4net.NDC.Push(Guid.NewGuid().ToString())) + { + logger.Info(CloudStackTypes.ModifyVmNicConfigCommand + cmd.ToString()); + bool result = false; + String vmName = cmd.vmName; + uint vlan = (uint)cmd.vlan; + string macAddress = cmd.macAddress; + wmiCallsV2.ModifyVmVLan(vmName, vlan, macAddress); + + result = true; + + object ansContent = new + { + vmName = vmName, + result = result, + contextMap = contextMap + }; + return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.ModifyVmNicConfigAnswer); + } + + } + + // POST api/HypervResource/GetVmConfigCommand + [HttpPost] + [ActionName(CloudStackTypes.GetVmConfigCommand)] + public JContainer GetVmConfigCommand([FromBody]dynamic cmd) + { + using (log4net.NDC.Push(Guid.NewGuid().ToString())) + { + logger.Info(CloudStackTypes.GetVmConfigCommand + cmd.ToString()); + bool result = false; + String vmName = cmd.vmName; + ComputerSystem vm = wmiCallsV2.GetComputerSystem(vmName); + List nicDetails = new List(); + var nicSettingsViaVm = wmiCallsV2.GetEthernetPortSettings(vm); + NicDetails nic = null; + String[] macAddress = new String[nicSettingsViaVm.Length]; + int index = 0; + foreach (SyntheticEthernetPortSettingData item in nicSettingsViaVm) + { + macAddress[index++] = item.Address; + } + + index = 0; + var ethernetConnections = wmiCallsV2.GetEthernetConnections(vm); + int vlanid = 1; + foreach (EthernetPortAllocationSettingData item in ethernetConnections) + { + EthernetSwitchPortVlanSettingData vlanSettings = wmiCallsV2.GetVlanSettings(item); + if (vlanSettings == null) + { + vlanid = -1; + } + else + { + vlanid = vlanSettings.AccessVlanId; + } + nic = new NicDetails(macAddress[index++], vlanid); + nicDetails.Add(nic); + } + + result = true; + + object ansContent = new + { + vmName = vmName, + nics = nicDetails, + result = result, + contextMap = contextMap + }; + return ReturnCloudStackTypedJArray(ansContent, CloudStackTypes.GetVmConfigAnswer); + } + } + + + // POST api/HypervResource/GetVmStatsCommand [HttpPost] [ActionName(CloudStackTypes.GetVmStatsCommand)] diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/IWmiCallsV2.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/IWmiCallsV2.cs index 5f814c59535..9042d7c5e12 100644 --- a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/IWmiCallsV2.cs +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/IWmiCallsV2.cs @@ -66,5 +66,6 @@ namespace HypervResource void patchSystemVmIso(string vmName, string systemVmIso); void SetState(ComputerSystem vm, ushort requiredState); Dictionary GetVmSync(String privateIpAddress); + void ModifyVmVLan(string vmName, uint vlanid, string mac); } } diff --git a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs index 2e3aca59675..b9694f00eca 100644 --- a/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs +++ b/plugins/hypervisors/hyperv/DotNet/ServerResource/HypervResource/WmiCallsV2.cs @@ -443,6 +443,7 @@ namespace HypervResource nicCount++; } + // pass the boot args for the VM using KVP component. // We need to pass the boot args to system vm's to get them configured with cloudstack configuration. // Add new user data @@ -909,6 +910,37 @@ namespace HypervResource return new ResourceAllocationSettingData((ManagementBaseObject)defaultDiskDriveSettings.LateBoundObject.Clone()); } + + // Modify the systemvm nic's VLAN id + public void ModifyVmVLan(string vmName, uint vlanid, String mac) + { + ComputerSystem vm = GetComputerSystem(vmName); + SyntheticEthernetPortSettingData[] nicSettingsViaVm = GetEthernetPortSettings(vm); + // Obtain controller for Hyper-V virtualisation subsystem + VirtualSystemManagementService vmMgmtSvc = GetVirtualisationSystemManagementService(); + string normalisedMAC = string.Join("", (mac.Split(new char[] { ':' }))); + int index = 0; + foreach (SyntheticEthernetPortSettingData item in nicSettingsViaVm) + { + if (normalisedMAC.ToLower().Equals(item.Address.ToLower())) + { + break; + } + index++; + } + + //TODO: make sure the index wont be out of range. + + EthernetPortAllocationSettingData[] ethernetConnections = GetEthernetConnections(vm); + EthernetSwitchPortVlanSettingData vlanSettings = GetVlanSettings(ethernetConnections[index]); + + //Assign configuration to new NIC + vlanSettings.LateBoundObject["AccessVlanId"] = vlanid; + vlanSettings.LateBoundObject["OperationMode"] = 1; + ModifyFeatureVmResources(vmMgmtSvc, vm, new String[] { + vlanSettings.LateBoundObject.GetText(TextFormat.CimDtd20)}); + } + public void AttachIso(string displayName, string iso) { logger.DebugFormat("Got request to attach iso {0} to vm {1}", iso, displayName); @@ -1420,6 +1452,36 @@ namespace HypervResource return vSwitch; } + + private static void ModifyFeatureVmResources(VirtualSystemManagementService vmMgmtSvc, ComputerSystem vm, string[] resourceSettings) + { + // Resource settings are changed through the management service + System.Management.ManagementPath jobPath; + System.Management.ManagementPath[] results; + + var ret_val = vmMgmtSvc.ModifyFeatureSettings( + resourceSettings, + out jobPath, + out results); + + // If the Job is done asynchronously + if (ret_val == ReturnCode.Started) + { + JobCompleted(jobPath); + } + else if (ret_val != ReturnCode.Completed) + { + var errMsg = string.Format( + "Failed to update VM {0} (GUID {1}) due to {2} (ModifyVirtualSystem call), existing VM not deleted", + vm.ElementName, + vm.Name, + ReturnCode.ToString(ret_val)); + var ex = new WmiException(errMsg); + logger.Error(errMsg, ex); + throw ex; + } + } + private static void ModifyVmResources(VirtualSystemManagementService vmMgmtSvc, ComputerSystem vm, string[] resourceSettings) { // Resource settings are changed through the management service diff --git a/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/guru/HypervGuru.java b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/guru/HypervGuru.java index 5845038bbff..1d9e7f619f9 100644 --- a/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/guru/HypervGuru.java +++ b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/guru/HypervGuru.java @@ -16,15 +16,30 @@ // under the License. package com.cloud.hypervisor.hyperv.guru; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + import javax.ejb.Local; import javax.inject.Inject; +import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.HypervisorGuru; import com.cloud.hypervisor.HypervisorGuruBase; import com.cloud.storage.GuestOSVO; import com.cloud.storage.dao.GuestOSDao; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.hypervisor.hyperv.manager.HypervManager; +import com.cloud.network.NetworkModel; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.NicProfile; +import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; /** @@ -35,6 +50,9 @@ public class HypervGuru extends HypervisorGuruBase implements HypervisorGuru { @Inject private GuestOSDao _guestOsDao; + @Inject HypervManager _hypervMgr; + @Inject NetworkDao _networkDao; + @Inject NetworkModel _networkMgr; @Override public final HypervisorType getHypervisorType() { @@ -51,6 +69,78 @@ public class HypervGuru extends HypervisorGuruBase implements HypervisorGuru { @Override public final VirtualMachineTO implement(VirtualMachineProfile vm) { VirtualMachineTO to = toVirtualMachineTO(vm); + List nicProfiles = vm.getNics(); + + if(vm.getVirtualMachine().getType() == VirtualMachine.Type.DomainRouter) { + + NicProfile publicNicProfile = null; + for(NicProfile nicProfile : nicProfiles) { + if(nicProfile.getTrafficType() == TrafficType.Public) { + publicNicProfile = nicProfile; + break; + } + } + + if(publicNicProfile != null) { + NicTO[] nics = to.getNics(); + + // reserve extra NICs + NicTO[] expandedNics = new NicTO[nics.length + _hypervMgr.getRouterExtraPublicNics()]; + int i = 0; + int deviceId = -1; + for(i = 0; i < nics.length; i++) { + expandedNics[i] = nics[i]; + if(nics[i].getDeviceId() > deviceId) + deviceId = nics[i].getDeviceId(); + } + deviceId++; + + long networkId = publicNicProfile.getNetworkId(); + NetworkVO network = _networkDao.findById(networkId); + + for(; i < nics.length + _hypervMgr.getRouterExtraPublicNics(); i++) { + NicTO nicTo = new NicTO(); + nicTo.setDeviceId(deviceId++); + nicTo.setBroadcastType(publicNicProfile.getBroadcastType()); + nicTo.setType(publicNicProfile.getTrafficType()); + nicTo.setIp("0.0.0.0"); + nicTo.setNetmask("255.255.255.255"); + nicTo.setName(publicNicProfile.getName()); + + try { + String mac = _networkMgr.getNextAvailableMacAddressInNetwork(networkId); + nicTo.setMac(mac); + } catch (InsufficientAddressCapacityException e) { + throw new CloudRuntimeException("unable to allocate mac address on network: " + networkId); + } + nicTo.setDns1(publicNicProfile.getDns1()); + nicTo.setDns2(publicNicProfile.getDns2()); + if (publicNicProfile.getGateway() != null) { + nicTo.setGateway(publicNicProfile.getGateway()); + } else { + nicTo.setGateway(network.getGateway()); + } + nicTo.setDefaultNic(false); + nicTo.setBroadcastUri(publicNicProfile.getBroadCastUri()); + nicTo.setIsolationuri(publicNicProfile.getIsolationUri()); + + Integer networkRate = _networkMgr.getNetworkRate(network.getId(), null); + nicTo.setNetworkRateMbps(networkRate); + + expandedNics[i] = nicTo; + } + to.setNics(expandedNics); + } + + StringBuffer sbMacSequence = new StringBuffer(); + for(NicTO nicTo : sortNicsByDeviceId(to.getNics())) { + sbMacSequence.append(nicTo.getMac()).append("|"); + } + sbMacSequence.deleteCharAt(sbMacSequence.length() - 1); + String bootArgs = to.getBootArgs(); + to.setBootArgs(bootArgs + " nic_macs=" + sbMacSequence.toString()); + + } // Determine the VM's OS description GuestOSVO guestOS = _guestOsDao.findById(vm.getVirtualMachine().getGuestOSId()); @@ -59,6 +149,29 @@ public class HypervGuru extends HypervisorGuruBase implements HypervisorGuru { return to; } + private NicTO[] sortNicsByDeviceId(NicTO[] nics) { + + List listForSort = new ArrayList(); + for (NicTO nic : nics) { + listForSort.add(nic); + } + Collections.sort(listForSort, new Comparator() { + + @Override + public int compare(NicTO arg0, NicTO arg1) { + if (arg0.getDeviceId() < arg1.getDeviceId()) { + return -1; + } else if (arg0.getDeviceId() == arg1.getDeviceId()) { + return 0; + } + + return 1; + } + }); + + return listForSort.toArray(new NicTO[0]); + } + @Override public final boolean trackVmHostChange() { return false; diff --git a/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/manager/HypervManager.java b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/manager/HypervManager.java index 9030e29e7a4..5821fe4dcae 100644 --- a/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/manager/HypervManager.java +++ b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/manager/HypervManager.java @@ -21,4 +21,5 @@ import com.cloud.utils.component.Manager; public interface HypervManager extends Manager { public String prepareSecondaryStorageStore(long zoneId); + int getRouterExtraPublicNics(); } diff --git a/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/manager/HypervManagerImpl.java b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/manager/HypervManagerImpl.java index a30eb7df005..71a619a0ad5 100644 --- a/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/manager/HypervManagerImpl.java +++ b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/manager/HypervManagerImpl.java @@ -45,6 +45,8 @@ import com.cloud.utils.NumbersUtil; import com.cloud.utils.db.GlobalLock; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.script.Script; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.VMInstanceDao; @Local(value = {HypervManager.class}) public class HypervManagerImpl implements HypervManager { @@ -60,10 +62,11 @@ public class HypervManagerImpl implements HypervManager { Map _storageMounts = new HashMap(); StorageLayer _storage; - @Inject - ConfigurationDao _configDao; - @Inject - DataStoreManager _dataStoreMgr; + @Inject ConfigurationDao _configDao; + @Inject DataStoreManager _dataStoreMgr; + @Inject VMInstanceDao _vminstanceDao; + @Inject NicDao _nicDao; + int _routerExtraPublicNics = 2; @Override public boolean configure(String name, Map params) throws ConfigurationException { @@ -77,7 +80,7 @@ public class HypervManagerImpl implements HypervManager { _storage = new JavaStorageLayer(); _storage.configure("StorageLayer", params); } - + _routerExtraPublicNics = NumbersUtil.parseInt(_configDao.getValue(Config.RouterExtraPublicNics.key()), 2); return true; } @@ -373,4 +376,9 @@ public class HypervManagerImpl implements HypervManager { } } } -} + + @Override + public int getRouterExtraPublicNics() { + return _routerExtraPublicNics; + } +} \ No newline at end of file diff --git a/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java index d6ffa1db5c2..e9647ad4a81 100644 --- a/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java +++ b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java @@ -71,7 +71,12 @@ import com.cloud.agent.api.CheckS2SVpnConnectionsCommand; import com.cloud.agent.api.Command; import com.cloud.agent.api.GetDomRVersionAnswer; import com.cloud.agent.api.GetDomRVersionCmd; +import com.cloud.agent.api.GetVmConfigAnswer; +import com.cloud.agent.api.GetVmConfigAnswer.NicDetails; +import com.cloud.agent.api.GetVmConfigCommand; import com.cloud.agent.api.HostVmStateReportEntry; +import com.cloud.agent.api.ModifyVmNicConfigAnswer; +import com.cloud.agent.api.ModifyVmNicConfigCommand; import com.cloud.agent.api.NetworkUsageAnswer; import com.cloud.agent.api.NetworkUsageCommand; import com.cloud.agent.api.PingCommand; @@ -117,11 +122,13 @@ import com.cloud.agent.api.to.PortForwardingRuleTO; import com.cloud.agent.api.to.StaticNatRuleTO; import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.exception.InternalErrorException; import com.cloud.host.Host.Type; import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.hyperv.manager.HypervManager; import com.cloud.network.HAProxyConfigurator; import com.cloud.network.LoadBalancerConfigurator; +import com.cloud.network.Networks.BroadcastDomainType; import com.cloud.network.Networks.RouterPrivateIpStrategy; import com.cloud.network.rules.FirewallRule; import com.cloud.resource.ServerResource; @@ -133,6 +140,8 @@ import com.cloud.utils.net.NetUtils; import com.cloud.utils.ssh.SshHelper; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineName; + + /** * Implementation of dummy resource to be returned from discoverer. **/ @@ -706,65 +715,6 @@ public class HypervDirectConnectResource extends ServerResourceBase implements S } } - // - // find mac address of a specified ethx device - // ip address show ethx | grep link/ether | sed -e 's/^[ \t]*//' | cut -d' ' -f2 - // returns - // eth0:xx.xx.xx.xx - - // - // list IP with eth devices - // ifconfig ethx |grep -B1 "inet addr" | awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' - // | awk -F: '{ print $1 ": " $3 }' - // - // returns - // eth0:xx.xx.xx.xx - // - // - - private int findRouterEthDeviceIndex(String domrName, String routerIp, String mac) throws Exception { - - s_logger.info("findRouterEthDeviceIndex. mac: " + mac); - - // TODO : this is a temporary very inefficient solution, will refactor it later - Pair result = SshHelper.sshExecute(routerIp, DEFAULT_DOMR_SSHPORT, "root", getSystemVMKeyFile(), null, "ls /proc/sys/net/ipv4/conf"); - - // when we dynamically plug in a new NIC into virtual router, it may take time to show up in guest OS - // we use a waiting loop here as a workaround to synchronize activities in systems - long startTick = System.currentTimeMillis(); - while (System.currentTimeMillis() - startTick < 15000) { - if (result.first()) { - String[] tokens = result.second().split("\\s+"); - for (String token : tokens) { - if (!("all".equalsIgnoreCase(token) || "default".equalsIgnoreCase(token) || "lo".equalsIgnoreCase(token))) { - String cmd = String.format("ip address show %s | grep link/ether | sed -e 's/^[ \t]*//' | cut -d' ' -f2", token); - - if (s_logger.isDebugEnabled()) - s_logger.debug("Run domr script " + cmd); - Pair result2 = SshHelper.sshExecute(routerIp, DEFAULT_DOMR_SSHPORT, "root", getSystemVMKeyFile(), null, - // TODO need to find the dev index inside router based on IP address - cmd); - if (s_logger.isDebugEnabled()) - s_logger.debug("result: " + result2.first() + ", output: " + result2.second()); - - if (result2.first() && result2.second().trim().equalsIgnoreCase(mac.trim())) - return Integer.parseInt(token.substring(3)); - } - } - } - - s_logger.warn("can not find intereface associated with mac: " + mac + ", guest OS may still at loading state, retry..."); - - try { - Thread.currentThread(); - Thread.sleep(1000); - } catch (InterruptedException e) { - } - } - - return -1; - } - protected Answer execute(SetPortForwardingRulesCommand cmd) { if (s_logger.isInfoEnabled()) { s_logger.info("Executing resource SetPortForwardingRulesCommand: " + s_gson.toJson(cmd)); @@ -1170,7 +1120,6 @@ public class HypervDirectConnectResource extends ServerResourceBase implements S if (s_logger.isInfoEnabled()) { s_logger.info("Executing resource VmDataCommand: " + s_gson.toJson(cmd)); } - String routerPrivateIpAddress = cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP); String controlIp = getRouterSshControlIp(cmd); @@ -1386,6 +1335,102 @@ public class HypervDirectConnectResource extends ServerResourceBase implements S return new Answer(cmd); } + // + // find mac address of a specified ethx device + // ip address show ethx | grep link/ether | sed -e 's/^[ \t]*//' | cut -d' ' -f2 + // returns + // eth0:xx.xx.xx.xx + + // + // list IP with eth devices + // ifconfig ethx |grep -B1 "inet addr" | awk '{ if ( $1 == "inet" ) { print $2 } else if ( $2 == "Link" ) { printf "%s:" ,$1 } }' + // | awk -F: '{ print $1 ": " $3 }' + // + // returns + // eth0:xx.xx.xx.xx + // + // + + private int findRouterEthDeviceIndex(String domrName, String routerIp, String mac) throws Exception { + + s_logger.info("findRouterEthDeviceIndex. mac: " + mac); + + // TODO : this is a temporary very inefficient solution, will refactor it later + Pair result = SshHelper.sshExecute(routerIp, DEFAULT_DOMR_SSHPORT, "root", getSystemVMKeyFile(), null, + "ls /proc/sys/net/ipv4/conf"); + + // when we dynamically plug in a new NIC into virtual router, it may take time to show up in guest OS + // we use a waiting loop here as a workaround to synchronize activities in systems + long startTick = System.currentTimeMillis(); + while (System.currentTimeMillis() - startTick < 15000) { + if (result.first()) { + String[] tokens = result.second().split("\\s+"); + for (String token : tokens) { + if (!("all".equalsIgnoreCase(token) || "default".equalsIgnoreCase(token) || "lo".equalsIgnoreCase(token))) { + String cmd = String.format("ip address show %s | grep link/ether | sed -e 's/^[ \t]*//' | cut -d' ' -f2", token); + + if (s_logger.isDebugEnabled()) + s_logger.debug("Run domr script " + cmd); + Pair result2 = SshHelper.sshExecute(routerIp, DEFAULT_DOMR_SSHPORT, "root", getSystemVMKeyFile(), null, + // TODO need to find the dev index inside router based on IP address + cmd); + if (s_logger.isDebugEnabled()) + s_logger.debug("result: " + result2.first() + ", output: " + result2.second()); + + if (result2.first() && result2.second().trim().equalsIgnoreCase(mac.trim())) + return Integer.parseInt(token.substring(3)); + } + } + } + + s_logger.warn("can not find intereface associated with mac: " + mac + ", guest OS may still at loading state, retry..."); + + } + + return -1; + } + + private Pair findRouterFreeEthDeviceIndex(String routerIp) throws Exception { + + s_logger.info("findRouterFreeEthDeviceIndex. mac: "); + + // TODO : this is a temporary very inefficient solution, will refactor it later + Pair result = SshHelper.sshExecute(routerIp, DEFAULT_DOMR_SSHPORT, "root", getSystemVMKeyFile(), null, + "ip address | grep DOWN| cut -f2 -d :"); + + // when we dynamically plug in a new NIC into virtual router, it may take time to show up in guest OS + // we use a waiting loop here as a workaround to synchronize activities in systems + long startTick = System.currentTimeMillis(); + while (System.currentTimeMillis() - startTick < 15000) { + if (result.first() && !result.second().isEmpty()) { + String[] tokens = result.second().split("\\n"); + for (String token : tokens) { + if (!("all".equalsIgnoreCase(token) || "default".equalsIgnoreCase(token) || "lo".equalsIgnoreCase(token))) { + //String cmd = String.format("ip address show %s | grep link/ether | sed -e 's/^[ \t]*//' | cut -d' ' -f2", token); + //TODO: don't check for eth0,1,2, as they will be empty by default. + //String cmd = String.format("ip address show %s ", token); + String cmd = String.format("ip address show %s | grep link/ether | sed -e 's/^[ \t]*//' | cut -d' ' -f2", token); + if (s_logger.isDebugEnabled()) + s_logger.debug("Run domr script " + cmd); + Pair result2 = SshHelper.sshExecute(routerIp, DEFAULT_DOMR_SSHPORT, "root", getSystemVMKeyFile(), null, + // TODO need to find the dev index inside router based on IP address + cmd); + if (s_logger.isDebugEnabled()) + s_logger.debug("result: " + result2.first() + ", output: " + result2.second()); + + if (result2.first() && result2.second().trim().length() > 0) + return new Pair(Integer.parseInt(token.trim().substring(3)), result2.second().trim()) ; + } + } + } + + //s_logger.warn("can not find intereface associated with mac: , guest OS may still at loading state, retry..."); + + } + + return new Pair(-1, ""); + } + protected Answer execute(IpAssocCommand cmd) { if (s_logger.isInfoEnabled()) { s_logger.info("Executing resource IPAssocCommand: " + s_gson.toJson(cmd)); @@ -1401,7 +1446,7 @@ public class HypervDirectConnectResource extends ServerResourceBase implements S String controlIp = getRouterSshControlIp(cmd); for (IpAddressTO ip : ips) { assignPublicIpAddress(routerName, controlIp, ip.getPublicIp(), ip.isAdd(), ip.isFirstIP(), ip.isSourceNat(), ip.getBroadcastUri(), ip.getVlanGateway(), - ip.getVlanNetmask(), ip.getVifMacAddress()); + ip.getVlanNetmask(), ip.getVifMacAddress()); results[i++] = ip.getPublicIp() + " - success"; } @@ -1419,16 +1464,97 @@ public class HypervDirectConnectResource extends ServerResourceBase implements S return new IpAssocAnswer(cmd, results); } + protected int getVmNics(String vmName, int vlanid) { + GetVmConfigCommand vmConfig = new GetVmConfigCommand(vmName); + URI agentUri = null; + int nicposition = -1; + try { + String cmdName = GetVmConfigCommand.class.getName(); + agentUri = + new URI("https", null, _agentIp, _port, + "/api/HypervResource/" + cmdName, null, null); + } catch (URISyntaxException e) { + String errMsg = "Could not generate URI for Hyper-V agent"; + s_logger.error(errMsg, e); + } + String ansStr = postHttpRequest(s_gson.toJson(vmConfig), agentUri); + Answer[] result = s_gson.fromJson(ansStr, Answer[].class); + s_logger.debug("executeRequest received response " + + s_gson.toJson(result)); + if (result.length > 0) { + GetVmConfigAnswer ans = ((GetVmConfigAnswer)result[0]); + List nics = ans.getNics(); + for (NicDetails nic : nics) { + if (nic.getVlanid() == vlanid) { + nicposition = nics.indexOf(nic); + break; + } + } + } + return nicposition; + } + + protected void modifyNicVlan(String vmName, int vlanId, String macAddress) { + ModifyVmNicConfigCommand modifynic = new ModifyVmNicConfigCommand(vmName, vlanId, macAddress); + URI agentUri = null; + try { + String cmdName = ModifyVmNicConfigCommand.class.getName(); + agentUri = + new URI("https", null, _agentIp, _port, + "/api/HypervResource/" + cmdName, null, null); + } catch (URISyntaxException e) { + String errMsg = "Could not generate URI for Hyper-V agent"; + s_logger.error(errMsg, e); + } + String ansStr = postHttpRequest(s_gson.toJson(modifynic), agentUri); + Answer[] result = s_gson.fromJson(ansStr, Answer[].class); + s_logger.debug("executeRequest received response " + + s_gson.toJson(result)); + if (result.length > 0) { + ModifyVmNicConfigAnswer ans = ((ModifyVmNicConfigAnswer)result[0]); + } + } + protected void assignPublicIpAddress(final String vmName, final String privateIpAddress, final String publicIpAddress, final boolean add, final boolean firstIP, - final boolean sourceNat, final String vlanId, final String vlanGateway, final String vlanNetmask, final String vifMacAddress) throws Exception { + final boolean sourceNat, final String broadcastId, final String vlanGateway, final String vlanNetmask, final String vifMacAddress) throws Exception { + + URI broadcastUri = BroadcastDomainType.fromString(broadcastId); + if (BroadcastDomainType.getSchemeValue(broadcastUri) != BroadcastDomainType.Vlan) { + throw new InternalErrorException("Unable to assign a public IP to a VIF on network " + broadcastId); + } + int vlanId = Integer.parseInt(BroadcastDomainType.getValue(broadcastUri)); + + int publicNicInfo = -1; + publicNicInfo = getVmNics(vmName, vlanId); boolean addVif = false; - if (add) { + if (add && publicNicInfo == -1) { if (s_logger.isDebugEnabled()) { s_logger.debug("Plug new NIC to associate" + privateIpAddress + " to " + publicIpAddress); } addVif = true; } else if (!add && firstIP) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Unplug NIC " + publicNicInfo); + } + } + + if (addVif) { + Pair nicdevice = findRouterFreeEthDeviceIndex(privateIpAddress); + publicNicInfo = nicdevice.first(); + if (publicNicInfo > 0) { + modifyNicVlan(vmName, vlanId, nicdevice.second()); + // After modifying the vnic on VR, check the VR VNics config in the host and get the device position + publicNicInfo = getVmNics(vmName, vlanId); + // As a new nic got activated in the VR. add the entry in the NIC's table. + networkUsage(privateIpAddress, "addVif", "eth" + publicNicInfo); + } + else { + // we didn't find any eth device available in VR to configure the ip range with new VLAN + String msg = "No Nic is available on DomR VIF to associate/disassociate IP with."; + s_logger.error(msg); + throw new InternalErrorException(msg); + } } String args = null; @@ -1450,8 +1576,8 @@ public class HypervDirectConnectResource extends ServerResourceBase implements S args += publicIpAddress + "/" + cidrSize; args += " -c "; - args += "eth" + "2"; // currently hardcoding to eth 2 (which is default public ipd)//publicNicInfo.first(); + args += "eth" + publicNicInfo; args += " -g "; args += vlanGateway; @@ -1464,7 +1590,7 @@ public class HypervDirectConnectResource extends ServerResourceBase implements S } Pair result = - SshHelper.sshExecute(privateIpAddress, DEFAULT_DOMR_SSHPORT, "root", getSystemVMKeyFile(), null, "/opt/cloud/bin/ipassoc.sh " + args); + SshHelper.sshExecute(privateIpAddress, DEFAULT_DOMR_SSHPORT, "root", getSystemVMKeyFile(), null, "/opt/cloud/bin/ipassoc.sh " + args); if (!result.first()) { s_logger.error("ipassoc command on domain router " + privateIpAddress + " failed. message: " + result.second()); @@ -1840,7 +1966,7 @@ public class HypervDirectConnectResource extends ServerResourceBase implements S sch.connect(addr); return null; } catch (IOException e) { - s_logger.info("Could not connect to " + ipAddress + " due to " + e.toString()); + s_logger.info("Could] not connect to " + ipAddress + " due to " + e.toString()); if (e instanceof ConnectException) { // if connection is refused because of VM is being started, // we give it more sleep time diff --git a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java index 13291d057a5..bf2516a24a9 100755 --- a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -2125,7 +2125,7 @@ public class VirtualNetworkApplianceManagerImpl extends ManagerBase implements V buf.append(" dnssearchorder=").append(domain_suffix); } - if (profile.getHypervisorType() == HypervisorType.VMware) { + if (profile.getHypervisorType() == HypervisorType.VMware || profile.getHypervisorType() == HypervisorType.Hyperv) { buf.append(" extra_pubnics=" + _routerExtraPublicNics); } diff --git a/systemvm/patches/debian/config/opt/cloud/bin/ipassoc.sh b/systemvm/patches/debian/config/opt/cloud/bin/ipassoc.sh index ae2d7e4296c..2a9d7807743 100755 --- a/systemvm/patches/debian/config/opt/cloud/bin/ipassoc.sh +++ b/systemvm/patches/debian/config/opt/cloud/bin/ipassoc.sh @@ -254,10 +254,11 @@ remove_first_ip() { if [ $? -gt 0 -a $? -ne 2 ] then remove_routing $1 + sudo ip link set $ethDev down return 1 fi remove_routing $1 - + sudo ip link set $ethDev down return $? }