diff --git a/api/src/com/cloud/network/Network.java b/api/src/com/cloud/network/Network.java index 2cd41494244..a02c8cfdbe2 100644 --- a/api/src/com/cloud/network/Network.java +++ b/api/src/com/cloud/network/Network.java @@ -47,7 +47,7 @@ public interface Network extends ControlledEntity, StateObject, I private static List supportedServices = new ArrayList(); public static final Service Vpn = new Service("Vpn", Capability.SupportedVpnProtocols, Capability.VpnTypes); - public static final Service Dhcp = new Service("Dhcp"); + public static final Service Dhcp = new Service("Dhcp", Capability.ExtraDhcpOptions); public static final Service Dns = new Service("Dns", Capability.AllowDnsSuffixModification); public static final Service Gateway = new Service("Gateway"); public static final Service Firewall = new Service("Firewall", Capability.SupportedProtocols, Capability.MultipleIps, Capability.TrafficStatistics, @@ -218,6 +218,7 @@ public interface Network extends ControlledEntity, StateObject, I public static final Capability RegionLevelVpc = new Capability("RegionLevelVpc"); public static final Capability NoVlan = new Capability("NoVlan"); public static final Capability PublicAccess = new Capability("PublicAccess"); + public static final Capability ExtraDhcpOptions = new Capability("ExtraDhcpOptions"); private final String name; diff --git a/api/src/com/cloud/network/element/DhcpServiceProvider.java b/api/src/com/cloud/network/element/DhcpServiceProvider.java index 2bd6ebcba35..12830f8cec0 100644 --- a/api/src/com/cloud/network/element/DhcpServiceProvider.java +++ b/api/src/com/cloud/network/element/DhcpServiceProvider.java @@ -16,6 +16,8 @@ // under the License. package com.cloud.network.element; +import java.util.Map; + import com.cloud.deploy.DeployDestination; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; @@ -33,4 +35,6 @@ public interface DhcpServiceProvider extends NetworkElement { throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException; boolean removeDhcpSupportForSubnet(Network network) throws ResourceUnavailableException; + + boolean setExtraDhcpOptions(Network network, long nicId, Map dhcpOptions); } diff --git a/api/src/com/cloud/vm/NicExtraDhcpOption.java b/api/src/com/cloud/vm/NicExtraDhcpOption.java new file mode 100644 index 00000000000..f5d9fa0c6b4 --- /dev/null +++ b/api/src/com/cloud/vm/NicExtraDhcpOption.java @@ -0,0 +1,41 @@ +// 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.vm; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface NicExtraDhcpOption extends InternalIdentity, Identity { + + /** + * Returns the nic id for which the DHCP option applies + * @return nic id + */ + long getNicId(); + + /** + * Returns the DHCP option code + * @return + */ + int getCode(); + + /** + * Returns the Dhcp value + * @return + */ + String getValue(); +} diff --git a/api/src/com/cloud/vm/UserVmService.java b/api/src/com/cloud/vm/UserVmService.java index 68425c3b675..178840bfe0b 100644 --- a/api/src/com/cloud/vm/UserVmService.java +++ b/api/src/com/cloud/vm/UserVmService.java @@ -194,6 +194,8 @@ public interface UserVmService { * @param memory * @param cpuNumber * @param customId + * @param dhcpOptionMap + * - Maps the dhcp option code and the dhcp value to the network uuid * @return UserVm object if successful. * * @throws InsufficientCapacityException @@ -208,7 +210,7 @@ public interface UserVmService { UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIp, Boolean displayVm, String keyboard, - List affinityGroupIdList, Map customParameter, String customId) throws InsufficientCapacityException, + List affinityGroupIdList, Map customParameter, String customId, Map> dhcpOptionMap) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; /** @@ -267,6 +269,8 @@ public interface UserVmService { * @param memory * @param cpuNumber * @param customId + * @param dhcpOptionMap + * - Maps the dhcp option code and the dhcp value to the network uuid * @return UserVm object if successful. * * @throws InsufficientCapacityException @@ -281,7 +285,7 @@ public interface UserVmService { UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, - List affinityGroupIdList, Map customParameters, String customId) throws InsufficientCapacityException, + List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; /** @@ -338,6 +342,8 @@ public interface UserVmService { * @param memory * @param cpuNumber * @param customId + * @param dhcpOptionMap + * - Map that maps the DhcpOption code and their value on the Network uuid * @return UserVm object if successful. * * @throws InsufficientCapacityException @@ -352,7 +358,7 @@ public interface UserVmService { UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, - Map customParameters, String customId) + Map customParameters, String customId, Map> dhcpOptionMap) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; diff --git a/api/src/org/apache/cloudstack/api/ApiConstants.java b/api/src/org/apache/cloudstack/api/ApiConstants.java index 2d5fe779761..a5bd95f83c5 100644 --- a/api/src/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/org/apache/cloudstack/api/ApiConstants.java @@ -88,6 +88,9 @@ public class ApiConstants { public static final String UTILIZATION = "utilization"; public static final String DRIVER = "driver"; public static final String ROOT_DISK_SIZE = "rootdisksize"; + public static final String DHCP_OPTIONS_NETWORK_LIST = "dhcpoptionsnetworklist"; + public static final String DHCP_OPTIONS = "dhcpoptions"; + public static final String DHCP_PREFIX = "dhcp:"; public static final String DISPLAY_NAME = "displayname"; public static final String DISPLAY_NETWORK = "displaynetwork"; public static final String DISPLAY_NIC = "displaynic"; @@ -111,6 +114,10 @@ public class ApiConstants { public static final String END_PORT = "endport"; public static final String ENTRY_TIME = "entrytime"; public static final String EXPIRES = "expires"; + public static final String EXTRA_DHCP_OPTION = "extradhcpoption"; + public static final String EXTRA_DHCP_OPTION_NAME = "extradhcpoptionname"; + public static final String EXTRA_DHCP_OPTION_CODE = "extradhcpoptioncode"; + public static final String EXTRA_DHCP_OPTION_VALUE = "extradhcpvalue"; public static final String FENCE = "fence"; public static final String FETCH_LATEST = "fetchlatest"; public static final String FIRSTNAME = "firstname"; @@ -242,6 +249,7 @@ public class ApiConstants { public static final String SCHEDULE = "schedule"; public static final String SCOPE = "scope"; public static final String SECRET_KEY = "usersecretkey"; + public static final String SECONDARY_IP = "secondaryip"; public static final String SINCE = "since"; public static final String KEY = "key"; public static final String SEARCH_BASE = "searchbase"; @@ -309,6 +317,7 @@ public class ApiConstants { public static final String REMOVE_VLAN = "removevlan"; public static final String VLAN_ID = "vlanid"; public static final String ISOLATED_PVLAN = "isolatedpvlan"; + public static final String ISOLATION_URI = "isolationuri"; public static final String VM_AVAILABLE = "vmavailable"; public static final String VM_LIMIT = "vmlimit"; public static final String VM_TOTAL = "vmtotal"; @@ -410,6 +419,7 @@ public class ApiConstants { public static final String CAPACITY_IOPS = "capacityiops"; public static final String NETWORK_SPEED = "networkspeed"; public static final String BROADCAST_DOMAIN_RANGE = "broadcastdomainrange"; + public static final String BROADCAST_URI = "broadcasturi"; public static final String ISOLATION_METHOD = "isolationmethod"; public static final String ISOLATION_METHODS = "isolationmethods"; public static final String PHYSICAL_NETWORK_ID = "physicalnetworkid"; @@ -536,6 +546,8 @@ public class ApiConstants { public static final String NICIRA_NVP_DEVICE_NAME = "niciradevicename"; public static final String NICIRA_NVP_GATEWAYSERVICE_UUID = "l3gatewayserviceuuid"; public static final String NICIRA_NVP_L2_GATEWAYSERVICE_UUID = "l2gatewayserviceuuid"; + public static final String NSX_LOGICAL_SWITCH = "nsxlogicalswitch"; + public static final String NSX_LOGICAL_SWITCH_PORT = "nsxlogicalswitchport"; public static final String S3_ACCESS_KEY = "accesskey"; public static final String S3_SECRET_KEY = "secretkey"; public static final String S3_END_POINT = "endpoint"; @@ -665,6 +677,7 @@ public class ApiConstants { public static final String SUPPORTS_PUBLIC_ACCESS = "supportspublicaccess"; public static final String REGION_LEVEL_VPC = "regionlevelvpc"; public static final String STRECHED_L2_SUBNET = "strechedl2subnet"; + public static final String NETWORK_NAME = "networkname"; public static final String NETWORK_SPANNED_ZONES = "zonesnetworkspans"; public static final String METADATA = "metadata"; public static final String PHYSICAL_SIZE = "physicalsize"; diff --git a/api/src/org/apache/cloudstack/api/command/user/vm/AddNicToVMCmd.java b/api/src/org/apache/cloudstack/api/command/user/vm/AddNicToVMCmd.java index ef03e78bea5..ed2a4b56375 100644 --- a/api/src/org/apache/cloudstack/api/command/user/vm/AddNicToVMCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/vm/AddNicToVMCmd.java @@ -17,7 +17,10 @@ package org.apache.cloudstack.api.command.user.vm; import java.util.ArrayList; +import java.util.Collection; import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; import org.apache.log4j.Logger; @@ -39,6 +42,7 @@ import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; import com.cloud.user.Account; import com.cloud.uservm.UserVm; +import com.cloud.utils.net.Dhcp; import com.cloud.utils.net.NetUtils; import com.cloud.vm.VirtualMachine; @@ -65,6 +69,10 @@ public class AddNicToVMCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.MAC_ADDRESS, type = CommandType.STRING, description = "Mac Address for the new network") private String macaddr; + @Parameter(name = ApiConstants.DHCP_OPTIONS, type = CommandType.MAP, description = "DHCP options which are passed to the nic" + + " Example: dhcpoptions[0].dhcp:114=url&dhcpoptions[0].dhcp:66=www.test.com") + private Map dhcpOptions; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -125,6 +133,28 @@ public class AddNicToVMCmd extends BaseAsyncCmd { return vm.getAccountId(); } + public Map getDhcpOptionsMap() { + Map dhcpOptionsMap = new HashMap<>(); + if (dhcpOptions != null && !dhcpOptions.isEmpty()) { + + Collection> paramsCollection = this.dhcpOptions.values(); + for(Map dhcpNetworkOptions : paramsCollection) { + for (String key : dhcpNetworkOptions.keySet()) { + if (key.startsWith(ApiConstants.DHCP_PREFIX)) { + int dhcpOptionValue = Integer.parseInt(key.replaceFirst(ApiConstants.DHCP_PREFIX, "")); + dhcpOptionsMap.put(dhcpOptionValue, dhcpNetworkOptions.get(key)); + } else { + Dhcp.DhcpOptionCode dhcpOptionEnum = Dhcp.DhcpOptionCode.valueOfString(key); + dhcpOptionsMap.put(dhcpOptionEnum.getCode(), dhcpNetworkOptions.get(key)); + } + } + + } + } + + return dhcpOptionsMap; + } + @Override public void execute() { CallContext.current().setEventDetails("Vm Id: " + getVmId() + " Network Id: " + getNetworkId()); diff --git a/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index bd2ae6f9c17..548a89d6240 100644 --- a/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -59,6 +59,7 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.Network; import com.cloud.network.Network.IpAddresses; import com.cloud.uservm.UserVm; +import com.cloud.utils.net.Dhcp; import com.cloud.utils.net.NetUtils; import com.cloud.vm.VirtualMachine; @@ -187,6 +188,10 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG @Parameter(name = ApiConstants.DEPLOYMENT_PLANNER, type = CommandType.STRING, description = "Deployment planner to use for vm allocation. Available to ROOT admin only", since = "4.4", authorized = { RoleType.Admin }) private String deploymentPlanner; + @Parameter(name = ApiConstants.DHCP_OPTIONS_NETWORK_LIST, type = CommandType.MAP, description = "DHCP options which are passed to the VM on start up" + + " Example: dhcpoptionsnetworklist[0].dhcp:114=url&dhcpoptionsetworklist[0].networkid=networkid&dhcpoptionsetworklist[0].dhcp:66=www.test.com") + private Map dhcpOptionsNetworkList; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -407,6 +412,37 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG return keyboard; } + public Map> getDhcpOptionsMap() { + Map> dhcpOptionsMap = new HashMap<>(); + if (dhcpOptionsNetworkList != null && !dhcpOptionsNetworkList.isEmpty()) { + + Collection> paramsCollection = this.dhcpOptionsNetworkList.values(); + for(Map dhcpNetworkOptions : paramsCollection) { + String networkId = dhcpNetworkOptions.get(ApiConstants.NETWORK_ID); + + if(networkId == null) { + throw new IllegalArgumentException("No networkid specified when providing extra dhcp options."); + } + + Map dhcpOptionsForNetwork = new HashMap<>(); + dhcpOptionsMap.put(networkId, dhcpOptionsForNetwork); + + for (String key : dhcpNetworkOptions.keySet()) { + if (key.startsWith(ApiConstants.DHCP_PREFIX)) { + int dhcpOptionValue = Integer.parseInt(key.replaceFirst(ApiConstants.DHCP_PREFIX, "")); + dhcpOptionsForNetwork.put(dhcpOptionValue, dhcpNetworkOptions.get(key)); + } else if (!key.equals(ApiConstants.NETWORK_ID)){ + Dhcp.DhcpOptionCode dhcpOptionEnum = Dhcp.DhcpOptionCode.valueOfString(key); + dhcpOptionsForNetwork.put(dhcpOptionEnum.getCode(), dhcpNetworkOptions.get(key)); + } + } + + } + } + + return dhcpOptionsMap; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java b/api/src/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java index eb03f086c69..ea2d19dfc1c 100644 --- a/api/src/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java +++ b/api/src/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.api.command.user.vm; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -40,6 +41,7 @@ import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.user.Account; import com.cloud.uservm.UserVm; +import com.cloud.utils.net.Dhcp; import com.cloud.vm.VirtualMachine; @APICommand(name = "updateVirtualMachine", description="Updates properties of a virtual machine. The VM has to be stopped and restarted for the " + @@ -121,6 +123,11 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction description = "optional boolean field, which indicates if details should be cleaned up or not (if set to true, details removed for this resource, details field ignored; if false or not set, no action)") private Boolean cleanupDetails; + @Parameter(name = ApiConstants.DHCP_OPTIONS_NETWORK_LIST, type = CommandType.MAP, description = "DHCP options which are passed to the VM on start up" + + " Example: dhcpoptionsnetworklist[0].dhcp:114=url&dhcpoptionsetworklist[0].networkid=networkid&dhcpoptionsetworklist[0].dhcp:66=www.test.com") + private Map dhcpOptionsNetworkList; + + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -182,6 +189,37 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction return cleanupDetails == null ? false : cleanupDetails.booleanValue(); } + public Map> getDhcpOptionsMap() { + Map> dhcpOptionsMap = new HashMap<>(); + if (dhcpOptionsNetworkList != null && !dhcpOptionsNetworkList.isEmpty()) { + + Collection> paramsCollection = this.dhcpOptionsNetworkList.values(); + for(Map dhcpNetworkOptions : paramsCollection) { + String networkId = dhcpNetworkOptions.get(ApiConstants.NETWORK_ID); + + if(networkId == null) { + throw new IllegalArgumentException("No networkid specified when providing extra dhcp options."); + } + + Map dhcpOptionsForNetwork = new HashMap<>(); + dhcpOptionsMap.put(networkId, dhcpOptionsForNetwork); + + for (String key : dhcpNetworkOptions.keySet()) { + if (key.startsWith(ApiConstants.DHCP_PREFIX)) { + int dhcpOptionValue = Integer.parseInt(key.replaceFirst(ApiConstants.DHCP_PREFIX, "")); + dhcpOptionsForNetwork.put(dhcpOptionValue, dhcpNetworkOptions.get(key)); + } else if (!key.equals(ApiConstants.NETWORK_ID)) { + Dhcp.DhcpOptionCode dhcpOptionEnum = Dhcp.DhcpOptionCode.valueOfString(key); + dhcpOptionsForNetwork.put(dhcpOptionEnum.getCode(), dhcpNetworkOptions.get(key)); + } + } + + } + } + + return dhcpOptionsMap; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/org/apache/cloudstack/api/response/NicExtraDhcpOptionResponse.java b/api/src/org/apache/cloudstack/api/response/NicExtraDhcpOptionResponse.java new file mode 100644 index 00000000000..4af89a37d8d --- /dev/null +++ b/api/src/org/apache/cloudstack/api/response/NicExtraDhcpOptionResponse.java @@ -0,0 +1,99 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + + +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +import com.cloud.serializer.Param; + +@EntityReference(value = NicExtraDhcpOptionResponse.class) +public class NicExtraDhcpOptionResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "the ID of the extra dhcp option") + private String id; + + @SerializedName(ApiConstants.NIC_ID) + @Param(description = "the ID of the nic") + private String nicId; + + @SerializedName(ApiConstants.EXTRA_DHCP_OPTION_NAME) + @Param(description = "the name of the extra DHCP option") + private String codeName; + + @SerializedName(ApiConstants.EXTRA_DHCP_OPTION_CODE) + @Param(description = "the extra DHCP option code") + private int code; + + @SerializedName(ApiConstants.EXTRA_DHCP_OPTION_VALUE) + @Param(description = "the extra DHCP option value") + private String value; + + public NicExtraDhcpOptionResponse() { + super(); + } + + public NicExtraDhcpOptionResponse(String codeName, int code, String value) { + this.codeName = codeName; + this.code = code; + this.value = value; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getNicId() { + return nicId; + } + + public void setNicId(String nicId) { + this.nicId = nicId; + } + + public String getCodeName() { + return codeName; + } + + public void setCodeName(String codeName) { + this.codeName = codeName; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/api/src/org/apache/cloudstack/api/response/NicResponse.java b/api/src/org/apache/cloudstack/api/response/NicResponse.java index 7689123cbaf..5c3fd7a75a6 100644 --- a/api/src/org/apache/cloudstack/api/response/NicResponse.java +++ b/api/src/org/apache/cloudstack/api/response/NicResponse.java @@ -29,15 +29,15 @@ import java.util.List; @EntityReference(value = Nic.class) public class NicResponse extends BaseResponse { - @SerializedName("id") + @SerializedName(ApiConstants.ID) @Param(description = "the ID of the nic") private String id; - @SerializedName("networkid") + @SerializedName(ApiConstants.NETWORK_ID) @Param(description = "the ID of the corresponding network") private String networkId; - @SerializedName("networkname") + @SerializedName(ApiConstants.NETWORK_NAME) @Param(description = "the name of the corresponding network") private String networkName; @@ -53,11 +53,11 @@ public class NicResponse extends BaseResponse { @Param(description = "the ip address of the nic") private String ipaddress; - @SerializedName("isolationuri") + @SerializedName(ApiConstants.ISOLATION_URI) @Param(description = "the isolation uri of the nic") private String isolationUri; - @SerializedName("broadcasturi") + @SerializedName(ApiConstants.BROADCAST_URI) @Param(description = "the broadcast uri of the nic") private String broadcastUri; @@ -73,7 +73,7 @@ public class NicResponse extends BaseResponse { @Param(description = "true if nic is default, false otherwise") private Boolean isDefault; - @SerializedName("macaddress") + @SerializedName(ApiConstants.MAC_ADDRESS) @Param(description = "true if nic is default, false otherwise") private String macAddress; @@ -89,10 +89,14 @@ public class NicResponse extends BaseResponse { @Param(description = "the IPv6 address of network") private String ip6Address; - @SerializedName("secondaryip") + @SerializedName(ApiConstants.SECONDARY_IP) @Param(description = "the Secondary ipv4 addr of nic") private List secondaryIps; + @SerializedName(ApiConstants.EXTRA_DHCP_OPTION) + @Param(description = "the extra dhcp options on the nic", since = "4.11.0") + private List extraDhcpOptions; + @SerializedName(ApiConstants.DEVICE_ID) @Param(description = "device id for the network when plugged into the virtual machine", since = "4.4") private String deviceId; @@ -101,11 +105,11 @@ public class NicResponse extends BaseResponse { @Param(description = "Id of the vm to which the nic belongs") private String vmId; - @SerializedName("nsxlogicalswitch") + @SerializedName(ApiConstants.NSX_LOGICAL_SWITCH) @Param(description = "Id of the NSX Logical Switch (if NSX based), null otherwise", since="4.6.0") private String nsxLogicalSwitch; - @SerializedName("nsxlogicalswitchport") + @SerializedName(ApiConstants.NSX_LOGICAL_SWITCH_PORT) @Param(description = "Id of the NSX Logical Switch Port (if NSX based), null otherwise", since="4.6.0") private String nsxLogicalSwitchPort; @@ -181,6 +185,10 @@ public class NicResponse extends BaseResponse { this.deviceId = deviceId; } + public void setExtraDhcpOptions(List extraDhcpOptions) { + this.extraDhcpOptions = extraDhcpOptions; + } + @Override public int hashCode() { final int prime = 31; diff --git a/engine/api/src/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/com/cloud/vm/VirtualMachineManager.java index 4b61caaf921..14fead7a057 100644 --- a/engine/api/src/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/com/cloud/vm/VirtualMachineManager.java @@ -78,7 +78,7 @@ public interface VirtualMachineManager extends Manager { */ void allocate(String vmInstanceName, VirtualMachineTemplate template, ServiceOffering serviceOffering, DiskOfferingInfo rootDiskOfferingInfo, List dataDiskOfferings, LinkedHashMap> auxiliaryNetworks, DeploymentPlan plan, - HypervisorType hyperType) throws InsufficientCapacityException; + HypervisorType hyperType, Map> extraDhcpOptions) throws InsufficientCapacityException; void allocate(String vmInstanceName, VirtualMachineTemplate template, ServiceOffering serviceOffering, LinkedHashMap> networkProfiles, DeploymentPlan plan, HypervisorType hyperType) throws InsufficientCapacityException; diff --git a/engine/api/src/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java b/engine/api/src/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java index 1d1bc4e4c49..e2a471fef6a 100644 --- a/engine/api/src/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java +++ b/engine/api/src/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java @@ -93,9 +93,24 @@ public interface NetworkOrchestrationService { boolean errorIfAlreadySetup, Long domainId, ACLType aclType, Boolean subdomainAccess, Long vpcId, Boolean isDisplayNetworkEnabled) throws ConcurrentOperationException; - void allocate(VirtualMachineProfile vm, LinkedHashMap> networks) throws InsufficientCapacityException, + void allocate(VirtualMachineProfile vm, LinkedHashMap> networks, Map> extraDhcpOptions) throws InsufficientCapacityException, ConcurrentOperationException; + /** + * configures the provided dhcp options on the given nic. + * @param network of the nic + * @param nicId + * @param extraDhcpOptions + */ + void configureExtraDhcpOptions(Network network, long nicId, Map extraDhcpOptions); + + /** + * configures dhcp options on the given nic. + * @param network of the nic + * @param nicId + */ + void configureExtraDhcpOptions(Network network, long nicId); + void prepare(VirtualMachineProfile profile, DeployDestination dest, ReservationContext context) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException; @@ -112,6 +127,8 @@ public interface NetworkOrchestrationService { Pair implementNetwork(long networkId, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; + Map getExtraDhcpOptions(long nicId); + /** * prepares vm nic change for migration * @@ -158,6 +175,8 @@ public interface NetworkOrchestrationService { boolean reallocate(VirtualMachineProfile vm, DataCenterDeployment dest) throws InsufficientCapacityException, ConcurrentOperationException; + void saveExtraDhcpOptions(String networkUuid, Long nicId, Map> extraDhcpOptionMap); + /** * @param requested * @param network diff --git a/engine/api/src/org/apache/cloudstack/engine/service/api/OrchestrationService.java b/engine/api/src/org/apache/cloudstack/engine/service/api/OrchestrationService.java index 93f969f7a1c..871745ec601 100644 --- a/engine/api/src/org/apache/cloudstack/engine/service/api/OrchestrationService.java +++ b/engine/api/src/org/apache/cloudstack/engine/service/api/OrchestrationService.java @@ -65,14 +65,14 @@ public interface OrchestrationService { @QueryParam("cpu") int cpu, @QueryParam("speed") int speed, @QueryParam("ram") long memory, @QueryParam("disk-size") Long diskSize, @QueryParam("compute-tags") List computeTags, @QueryParam("root-disk-tags") List rootDiskTags, @QueryParam("network-nic-map") Map networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan, - @QueryParam("root-disk-size") Long rootDiskSize) throws InsufficientCapacityException; + @QueryParam("root-disk-size") Long rootDiskSize, @QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap) throws InsufficientCapacityException; @POST VirtualMachineEntity createVirtualMachineFromScratch(@QueryParam("id") String id, @QueryParam("owner") String owner, @QueryParam("iso-id") String isoId, @QueryParam("host-name") String hostName, @QueryParam("display-name") String displayName, @QueryParam("hypervisor") String hypervisor, @QueryParam("os") String os, @QueryParam("cpu") int cpu, @QueryParam("speed") int speed, @QueryParam("ram") long memory, @QueryParam("disk-size") Long diskSize, @QueryParam("compute-tags") List computeTags, @QueryParam("root-disk-tags") List rootDiskTags, - @QueryParam("network-nic-map") Map networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan) throws InsufficientCapacityException; + @QueryParam("network-nic-map") Map networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan, @QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap) throws InsufficientCapacityException; @POST NetworkEntity createNetwork(String id, String name, String domainName, String cidr, String gateway); diff --git a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java index ef333563621..fe7fcdfd093 100755 --- a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -387,7 +387,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @DB public void allocate(final String vmInstanceName, final VirtualMachineTemplate template, final ServiceOffering serviceOffering, final DiskOfferingInfo rootDiskOfferingInfo, final List dataDiskOfferings, - final LinkedHashMap> auxiliaryNetworks, final DeploymentPlan plan, final HypervisorType hyperType) + final LinkedHashMap> auxiliaryNetworks, final DeploymentPlan plan, final HypervisorType hyperType, final Map> extraDhcpOptions) throws InsufficientCapacityException { final VMInstanceVO vm = _vmDao.findVMByInstanceName(vmInstanceName); @@ -415,7 +415,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac try { if (!vmProfile.getBootArgs().contains("ExternalLoadBalancerVm")) - _networkMgr.allocate(vmProfile, auxiliaryNetworks); + _networkMgr.allocate(vmProfile, auxiliaryNetworks, extraDhcpOptions); } catch (final ConcurrentOperationException e) { throw new CloudRuntimeException("Concurrent operation while trying to allocate resources for the VM", e); } @@ -451,7 +451,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Override public void allocate(final String vmInstanceName, final VirtualMachineTemplate template, final ServiceOffering serviceOffering, final LinkedHashMap> networks, final DeploymentPlan plan, final HypervisorType hyperType) throws InsufficientCapacityException { - allocate(vmInstanceName, template, serviceOffering, new DiskOfferingInfo(serviceOffering), new ArrayList(), networks, plan, hyperType); + allocate(vmInstanceName, template, serviceOffering, new DiskOfferingInfo(serviceOffering), new ArrayList(), networks, plan, hyperType, null); } private VirtualMachineGuru getVmGuru(final VirtualMachine vm) { diff --git a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java index 2b4995466b5..e588431b6b6 100644 --- a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java +++ b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java @@ -155,7 +155,7 @@ public class CloudOrchestrator implements OrchestrationService { @Override public VirtualMachineEntity createVirtualMachine(String id, String owner, String templateId, String hostName, String displayName, String hypervisor, int cpu, int speed, long memory, Long diskSize, List computeTags, List rootDiskTags, Map networkNicMap, DeploymentPlan plan, - Long rootDiskSize) throws InsufficientCapacityException { + Long rootDiskSize, Map> extraDhcpOptionMap) throws InsufficientCapacityException { // VirtualMachineEntityImpl vmEntity = new VirtualMachineEntityImpl(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, networks, // vmEntityManager); @@ -234,14 +234,14 @@ public class CloudOrchestrator implements OrchestrationService { } _itMgr.allocate(vm.getInstanceName(), _templateDao.findById(new Long(templateId)), computeOffering, rootDiskOfferingInfo, dataDiskOfferings, networkIpMap, plan, - hypervisorType); + hypervisorType, extraDhcpOptionMap); return vmEntity; } @Override public VirtualMachineEntity createVirtualMachineFromScratch(String id, String owner, String isoId, String hostName, String displayName, String hypervisor, String os, - int cpu, int speed, long memory, Long diskSize, List computeTags, List rootDiskTags, Map networkNicMap, DeploymentPlan plan) + int cpu, int speed, long memory, Long diskSize, List computeTags, List rootDiskTags, Map networkNicMap, DeploymentPlan plan, Map> extraDhcpOptionMap) throws InsufficientCapacityException { // VirtualMachineEntityImpl vmEntity = new VirtualMachineEntityImpl(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, networks, vmEntityManager); @@ -299,7 +299,7 @@ public class CloudOrchestrator implements OrchestrationService { HypervisorType hypervisorType = HypervisorType.valueOf(hypervisor); - _itMgr.allocate(vm.getInstanceName(), _templateDao.findById(new Long(isoId)), computeOffering, rootDiskOfferingInfo, new ArrayList(), networkIpMap, plan, hypervisorType); + _itMgr.allocate(vm.getInstanceName(), _templateDao.findById(new Long(isoId)), computeOffering, rootDiskOfferingInfo, new ArrayList(), networkIpMap, plan, hypervisorType, extraDhcpOptionMap); return vmEntity; } diff --git a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index db0ff980948..1ddff84f991 100644 --- a/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -25,6 +25,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -32,10 +33,12 @@ import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.vm.NicExtraDhcpOptionVO; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.cloud.entity.api.db.VMNetworkMapVO; @@ -198,6 +201,7 @@ import com.cloud.utils.fsm.NoTransitionException; import com.cloud.utils.fsm.StateMachine2; import com.cloud.utils.net.NetUtils; import com.cloud.vm.DomainRouterVO; +import com.cloud.utils.net.Dhcp; import com.cloud.vm.Nic; import com.cloud.vm.Nic.ReservationStrategy; import com.cloud.vm.NicIpAlias; @@ -212,6 +216,7 @@ import com.cloud.vm.VirtualMachine.Type; import com.cloud.vm.VirtualMachineProfile; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.NicExtraDhcpOptionDao; import com.cloud.vm.dao.NicIpAliasDao; import com.cloud.vm.dao.NicIpAliasVO; import com.cloud.vm.dao.NicSecondaryIpDao; @@ -269,6 +274,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra @Inject protected NicIpAliasDao _nicIpAliasDao; @Inject + protected NicExtraDhcpOptionDao _nicExtraDhcpOptionDao; + @Inject protected IPAddressDao _publicIpAddressDao; @Inject protected IpAddressManager _ipAddrMgr; @@ -728,7 +735,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra @Override @DB - public void allocate(final VirtualMachineProfile vm, final LinkedHashMap> networks) throws InsufficientCapacityException, + public void allocate(final VirtualMachineProfile vm, final LinkedHashMap> networks, final Map> extraDhcpOptions) throws InsufficientCapacityException, ConcurrentOperationException { Transaction.execute(new TransactionCallbackWithExceptionNoReturn() { @@ -800,6 +807,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra nics.add(vmNic); vm.addNic(vmNic); + saveExtraDhcpOptions(config.getUuid(), vmNic.getId(), extraDhcpOptions); } } if (nics.size() != size) { @@ -814,6 +822,24 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra }); } + @Override + public void saveExtraDhcpOptions(final String networkUuid, final Long nicId, final Map> extraDhcpOptionMap) { + + if(extraDhcpOptionMap != null) { + Map extraDhcpOption = extraDhcpOptionMap.get(networkUuid); + if(extraDhcpOption != null) { + List nicExtraDhcpOptionList = new LinkedList<>(); + + for (Integer code : extraDhcpOption.keySet()) { + Dhcp.DhcpOptionCode.valueOfInt(code); //check if code is supported or not. + NicExtraDhcpOptionVO nicExtraDhcpOptionVO = new NicExtraDhcpOptionVO(nicId, code, extraDhcpOption.get(code)); + nicExtraDhcpOptionList.add(nicExtraDhcpOptionVO); + } + _nicExtraDhcpOptionDao.saveExtraDhcpOptions(nicExtraDhcpOptionList); + } + } + } + @DB @Override public Pair allocateNic(final NicProfile requested, final Network network, final Boolean isDefaultNic, int deviceId, final VirtualMachineProfile vm) @@ -1141,6 +1167,15 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra } } + + //Reset the extra DHCP option that may have been cleared per nic. + List nicVOs = _nicDao.listByNetworkId(network.getId()); + for(NicVO nicVO : nicVOs) { + if(nicVO.getState() == Nic.State.Reserved) { + configureExtraDhcpOptions(network, nicVO.getId()); + } + } + for (final NetworkElement element : networkElements) { if (element instanceof AggregatedCommandExecutor && providersToImplement.contains(element.getProvider())) { ((AggregatedCommandExecutor)element).prepareAggregatedExecution(network, dest); @@ -1457,6 +1492,22 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra return resourceCount; } + @Override + public void configureExtraDhcpOptions(Network network, long nicId, Map extraDhcpOptions) { + if(_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dhcp)) { + if (_networkModel.getNetworkServiceCapabilities(network.getId(), Service.Dhcp).containsKey(Capability.ExtraDhcpOptions)) { + DhcpServiceProvider sp = getDhcpServiceProvider(network); + sp.setExtraDhcpOptions(network, nicId, extraDhcpOptions); + } + } + } + + @Override + public void configureExtraDhcpOptions(Network network, long nicId) { + Map extraDhcpOptions = getExtraDhcpOptions(nicId); + configureExtraDhcpOptions(network, nicId, extraDhcpOptions); + } + @Override public void finalizeUpdateInSequence(Network network, boolean success) { List providers = getNetworkProviders(network.getId()); @@ -1593,9 +1644,20 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra profile.setSecurityGroupEnabled(_networkModel.isSecurityGroupSupportedInNetwork(network)); guru.updateNicProfile(profile, network); + + configureExtraDhcpOptions(network, nicId); return profile; } + @Override + public Map getExtraDhcpOptions(long nicId) { + List nicExtraDhcpOptionVOList = _nicExtraDhcpOptionDao.listByNicId(nicId); + return nicExtraDhcpOptionVOList + .stream() + .collect(Collectors.toMap(NicExtraDhcpOptionVO::getCode, NicExtraDhcpOptionVO::getValue)); + } + + @Override public void prepareNicForMigration(final VirtualMachineProfile vm, final DeployDestination dest) { if(vm.getType().equals(VirtualMachine.Type.DomainRouter) && (vm.getHypervisorType().equals(HypervisorType.KVM) || vm.getHypervisorType().equals(HypervisorType.VMware))) { @@ -2949,7 +3011,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra @Override public void doInTransactionWithoutResult(final TransactionStatus status) throws InsufficientCapacityException { cleanupNics(vm); - allocate(vm, profiles); + allocate(vm, profiles, null); } }); } diff --git a/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index 2075b933b64..8a0d7cdde5c 100644 --- a/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -215,6 +215,7 @@ + diff --git a/engine/schema/src/com/cloud/vm/NicExtraDhcpOptionVO.java b/engine/schema/src/com/cloud/vm/NicExtraDhcpOptionVO.java new file mode 100644 index 00000000000..7b6d28fb5ff --- /dev/null +++ b/engine/schema/src/com/cloud/vm/NicExtraDhcpOptionVO.java @@ -0,0 +1,82 @@ +// 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.vm; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.UUID; + +@Entity +@Table(name = "nic_extra_dhcp_options") +public class NicExtraDhcpOptionVO implements NicExtraDhcpOption { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + String uuid = UUID.randomUUID().toString(); + + @Column(name = "nic_id") + private long nicId; + + @Column(name="code") + private int code; + + @Column(name = "value") + private String value; + + public NicExtraDhcpOptionVO (){ + + } + + public NicExtraDhcpOptionVO (long nicId, int code, String value) { + this.nicId = nicId; + this.code = code; + this.value = value; + } + + @Override + public long getNicId() { + return nicId; + } + + @Override + public int getCode() { + return code; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public long getId() { + return id; + } +} diff --git a/engine/schema/src/com/cloud/vm/dao/NicExtraDhcpOptionDao.java b/engine/schema/src/com/cloud/vm/dao/NicExtraDhcpOptionDao.java new file mode 100644 index 00000000000..69d9c00e1e0 --- /dev/null +++ b/engine/schema/src/com/cloud/vm/dao/NicExtraDhcpOptionDao.java @@ -0,0 +1,32 @@ +// 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.vm.dao; + +import java.util.List; + +import com.cloud.utils.db.GenericDao; +import com.cloud.vm.NicExtraDhcpOptionVO; + +public interface NicExtraDhcpOptionDao extends GenericDao { + List listByNicId(long nicId); + + /** + * Persists list of NicExtraDhcpOptionVO + * @param extraDhcpOptions + */ + void saveExtraDhcpOptions(List extraDhcpOptions); +} diff --git a/engine/schema/src/com/cloud/vm/dao/NicExtraDhcpOptionDaoImpl.java b/engine/schema/src/com/cloud/vm/dao/NicExtraDhcpOptionDaoImpl.java new file mode 100644 index 00000000000..3056c73938e --- /dev/null +++ b/engine/schema/src/com/cloud/vm/dao/NicExtraDhcpOptionDaoImpl.java @@ -0,0 +1,77 @@ +// 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.vm.dao; + +import org.springframework.stereotype.Component; + +import java.util.List; + +import com.cloud.utils.db.DB; +import com.cloud.utils.db.GenericDaoBase; + +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.vm.NicExtraDhcpOption; +import com.cloud.vm.NicExtraDhcpOptionVO; + +@Component +public class NicExtraDhcpOptionDaoImpl extends GenericDaoBase implements NicExtraDhcpOptionDao { + private SearchBuilder AllFieldsSearch; + + protected NicExtraDhcpOptionDaoImpl() { + super(); + + AllFieldsSearch = createSearchBuilder(); + AllFieldsSearch.and("nic_id", AllFieldsSearch.entity().getNicId(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("code", AllFieldsSearch.entity().getCode(), SearchCriteria.Op.IN); + + AllFieldsSearch.done(); + } + + @DB() + @Override + public List listByNicId(long nicId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("nic_id", nicId); + + return listBy(sc); + } + + @DB() + @Override + public void saveExtraDhcpOptions(List extraDhcpOptions) { + if (extraDhcpOptions.isEmpty()) { + return; + } + + extraDhcpOptions + .stream() + .map(NicExtraDhcpOption::getNicId) + .distinct() + .forEach(this::removeByNicId); + + extraDhcpOptions.stream() + .forEach(this::persist); + } + + public void removeByNicId(long nicId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("nic_id", nicId); + expunge(sc); + } + +} diff --git a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BaremetalDhcpElement.java b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BaremetalDhcpElement.java index 77f77dd93d6..5696048b847 100644 --- a/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BaremetalDhcpElement.java +++ b/plugins/hypervisors/baremetal/src/com/cloud/baremetal/networkservice/BaremetalDhcpElement.java @@ -180,4 +180,9 @@ public class BaremetalDhcpElement extends AdapterBase implements DhcpServiceProv return true; } + @Override + public boolean setExtraDhcpOptions(Network network, long nicId, Map dhcpOptions) { + return false; + } + } diff --git a/plugins/network-elements/juniper-contrail/src/org/apache/cloudstack/network/contrail/management/ContrailElementImpl.java b/plugins/network-elements/juniper-contrail/src/org/apache/cloudstack/network/contrail/management/ContrailElementImpl.java index 4440b2e2009..62ca29c7e44 100644 --- a/plugins/network-elements/juniper-contrail/src/org/apache/cloudstack/network/contrail/management/ContrailElementImpl.java +++ b/plugins/network-elements/juniper-contrail/src/org/apache/cloudstack/network/contrail/management/ContrailElementImpl.java @@ -374,4 +374,9 @@ public class ContrailElementImpl extends AdapterBase throws ResourceUnavailableException { return false; } + + @Override + public boolean setExtraDhcpOptions(Network network, long nicId, Map dhcpOptions) { + return false; + } } diff --git a/plugins/network-elements/nuage-vsp/src/com/cloud/agent/api/element/ExtraDhcpOptionsVspCommand.java b/plugins/network-elements/nuage-vsp/src/com/cloud/agent/api/element/ExtraDhcpOptionsVspCommand.java new file mode 100644 index 00000000000..45f260182d7 --- /dev/null +++ b/plugins/network-elements/nuage-vsp/src/com/cloud/agent/api/element/ExtraDhcpOptionsVspCommand.java @@ -0,0 +1,86 @@ +// +// 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.element; + +import net.nuage.vsp.acs.client.api.model.VspNetwork; +import org.apache.commons.lang.builder.HashCodeBuilder; + +import java.util.Map; +import java.util.Objects; + +import com.cloud.agent.api.Command; + +public class ExtraDhcpOptionsVspCommand extends Command { + private final VspNetwork network; + private final String nicUuid; + private final Map dhcpOptions; + + public ExtraDhcpOptionsVspCommand (VspNetwork network, String nicUuid, Map dhcpOptions) { + super(); + this.network = network; + this.nicUuid = nicUuid; + this.dhcpOptions = dhcpOptions; + } + + public VspNetwork getNetwork() { + return network; + } + + public String getNicUuid() { + return nicUuid; + } + + public Map getDhcpOptions() { + return dhcpOptions; + } + + @Override + public boolean executeInSequence() { + return false; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof ExtraDhcpOptionsVspCommand)) { + return false; + } + + ExtraDhcpOptionsVspCommand that = (ExtraDhcpOptionsVspCommand) o; + + return super.equals(that) + && Objects.equals(network, that.network) + && Objects.equals(nicUuid, that.nicUuid) + && Objects.equals(dhcpOptions, that.dhcpOptions); + } + + @Override + public int hashCode() { + return new HashCodeBuilder() + .appendSuper(super.hashCode()) + .append(nicUuid) + .append(network) + .append(dhcpOptions) + .toHashCode(); + } +} diff --git a/plugins/network-elements/nuage-vsp/src/com/cloud/network/element/NuageVspElement.java b/plugins/network-elements/nuage-vsp/src/com/cloud/network/element/NuageVspElement.java index d7b422b9853..16cbc9dbfa7 100644 --- a/plugins/network-elements/nuage-vsp/src/com/cloud/network/element/NuageVspElement.java +++ b/plugins/network-elements/nuage-vsp/src/com/cloud/network/element/NuageVspElement.java @@ -59,6 +59,7 @@ import com.cloud.agent.api.StartupCommand; import com.cloud.agent.api.StartupVspCommand; import com.cloud.agent.api.element.ApplyAclRuleVspCommand; import com.cloud.agent.api.element.ApplyStaticNatVspCommand; +import com.cloud.agent.api.element.ExtraDhcpOptionsVspCommand; import com.cloud.agent.api.element.ImplementVspCommand; import com.cloud.agent.api.element.ShutDownVpcVspCommand; import com.cloud.agent.api.element.ShutDownVspCommand; @@ -252,7 +253,8 @@ public class NuageVspElement extends AdapterBase implements ConnectivityProvider Capability.MultipleIps, "true" )) .put(Service.Dhcp, ImmutableMap.of( - Capability.DhcpAccrossMultipleSubnets, "true" + Capability.DhcpAccrossMultipleSubnets, "true", + Capability.ExtraDhcpOptions, "true" )) .put(Service.NetworkACL, ImmutableMap.of( Capability.SupportedProtocols, "tcp,udp,icmp" @@ -515,6 +517,23 @@ public class NuageVspElement extends AdapterBase implements ConnectivityProvider return true; } + @Override + public boolean setExtraDhcpOptions(Network network, long nicId, Map dhcpOptions) { + VspNetwork vspNetwork = _nuageVspEntityBuilder.buildVspNetwork(network); + HostVO nuageVspHost = _nuageVspManager.getNuageVspHost(network.getPhysicalNetworkId()); + NicVO nic = _nicDao.findById(nicId); + + ExtraDhcpOptionsVspCommand extraDhcpOptionsVspCommand = new ExtraDhcpOptionsVspCommand(vspNetwork, nic.getUuid(), dhcpOptions); + Answer answer = _agentMgr.easySend(nuageVspHost.getId(), extraDhcpOptionsVspCommand); + + if (answer == null || !answer.getResult()) { + s_logger.error("[setExtraDhcpOptions] setting extra DHCP options for nic " + nic.getUuid() + " failed."); + return false; + } + + return true; + } + @Override public boolean applyStaticNats(Network config, List rules) throws ResourceUnavailableException { diff --git a/plugins/network-elements/nuage-vsp/src/com/cloud/network/guru/NuageVspGuestNetworkGuru.java b/plugins/network-elements/nuage-vsp/src/com/cloud/network/guru/NuageVspGuestNetworkGuru.java index 112d444c710..b1577de0b39 100644 --- a/plugins/network-elements/nuage-vsp/src/com/cloud/network/guru/NuageVspGuestNetworkGuru.java +++ b/plugins/network-elements/nuage-vsp/src/com/cloud/network/guru/NuageVspGuestNetworkGuru.java @@ -19,6 +19,7 @@ package com.cloud.network.guru; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -42,6 +43,7 @@ import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.resourcedetail.VpcDetailVO; import org.apache.cloudstack.resourcedetail.dao.VpcDetailsDao; @@ -131,6 +133,8 @@ public class NuageVspGuestNetworkGuru extends GuestNetworkGuru { NetworkDetailsDao _networkDetailsDao; @Inject VpcDetailsDao _vpcDetailsDao; + @Inject + NetworkOrchestrationService _networkOrchestrationService; public NuageVspGuestNetworkGuru() { super(); @@ -225,7 +229,9 @@ public class NuageVspGuestNetworkGuru extends GuestNetworkGuru { implemented.setBroadcastUri(Networks.BroadcastDomainType.Vsp.toUri(broadcastUriStr)); implemented.setBroadcastDomainType(Networks.BroadcastDomainType.Vsp); - if (!implement(network.getVpcId(), physicalNetworkId, vspNetwork, _nuageVspEntityBuilder.buildNetworkDhcpOption(network, offering))) { + boolean implementSucceeded = implement(network.getVpcId(), physicalNetworkId, vspNetwork, _nuageVspEntityBuilder.buildNetworkDhcpOption(network, offering)); + + if (!implementSucceeded) { return null; } @@ -383,6 +389,8 @@ public class NuageVspGuestNetworkGuru extends GuestNetworkGuru { // Determine if dhcp options of the other nics in the network need to be updated if (vm.getType() == VirtualMachine.Type.DomainRouter && network.getState() != State.Implementing) { updateDhcpOptionsForExistingVms(network, nuageVspHost, vspNetwork, networkHasDns, networkHasDnsCache); + //update the extra DHCP options + } nic.setBroadcastUri(network.getBroadcastUri()); @@ -427,6 +435,10 @@ public class NuageVspGuestNetworkGuru extends GuestNetworkGuru { } } + private void updateExtraDhcpOptionsForExistingVm(Network network, Nic nic) { + _networkOrchestrationService.configureExtraDhcpOptions(network, nic.getId()); + } + private void updateDhcpOptionsForExistingVms(Network network, HostVO nuageVspHost, VspNetwork vspNetwork, boolean networkHasDns, Map networkHasDnsCache) throws InsufficientVirtualNetworkCapacityException { // Update dhcp options if a VR is added when we are not initiating the network @@ -437,12 +449,14 @@ public class NuageVspGuestNetworkGuru extends GuestNetworkGuru { List userNics = _nicDao.listByNetworkId(network.getId()); LinkedListMultimap dhcpOptionsPerDomain = LinkedListMultimap.create(); - for (NicVO userNic : userNics) { - if (userNic.getVmType() == VirtualMachine.Type.DomainRouter) { + for (Iterator iterator = userNics.iterator(); iterator.hasNext(); ) { + NicVO userNic = iterator.next(); + if (userNic.getVmType() == VirtualMachine.Type.DomainRouter || userNic.getState() != Nic.State.Reserved) { + iterator.remove(); continue; } - VMInstanceVO userVm = _vmInstanceDao.findById(userNic.getInstanceId()); + VMInstanceVO userVm = _vmInstanceDao.findById(userNic.getInstanceId()); boolean defaultHasDns = getDefaultHasDns(networkHasDnsCache, userNic); VspDhcpVMOption dhcpOption = _nuageVspEntityBuilder.buildVmDhcpOption(userNic, defaultHasDns, networkHasDns); dhcpOptionsPerDomain.put(userVm.getDomainId(), dhcpOption); @@ -463,6 +477,10 @@ public class NuageVspGuestNetworkGuru extends GuestNetworkGuru { throw new InsufficientVirtualNetworkCapacityException("Failed to reserve VM in Nuage VSP.", Network.class, network.getId()); } } + + for (NicVO userNic : userNics) { + updateExtraDhcpOptionsForExistingVm(network, userNic); + } } private void checkMultipleSubnetsCombinedWithUseData(Network network) { diff --git a/plugins/network-elements/nuage-vsp/src/com/cloud/network/manager/NuageVspManagerImpl.java b/plugins/network-elements/nuage-vsp/src/com/cloud/network/manager/NuageVspManagerImpl.java index f57b788a182..1bbef4ec2c7 100644 --- a/plugins/network-elements/nuage-vsp/src/com/cloud/network/manager/NuageVspManagerImpl.java +++ b/plugins/network-elements/nuage-vsp/src/com/cloud/network/manager/NuageVspManagerImpl.java @@ -301,10 +301,10 @@ public class NuageVspManagerImpl extends ManagerBase implements NuageVspManager, if (StringUtils.isNotBlank(cmd.getApiVersion())){ - if (!clientLoader.getNuageVspManagerClient().isSupportedApiVersion(cmd.getApiVersion())){ + apiVersion = cmd.getApiVersion(); + if (!clientLoader.getNuageVspManagerClient().isSupportedApiVersion(apiVersion)){ throw new CloudRuntimeException("Unsupported API version : " + cmd.getApiVersion()); } - apiVersion = cmd.getApiVersion(); } else { List supportedVsdVersions = clientLoader.getNuageVspManagerClient().getSupportedVersionList(); supportedVsdVersions.retainAll(Arrays.asList(NuageVspApiVersion.SUPPORTED_VERSIONS)); diff --git a/plugins/network-elements/nuage-vsp/src/com/cloud/network/resource/NuageVspResource.java b/plugins/network-elements/nuage-vsp/src/com/cloud/network/resource/NuageVspResource.java index 72ff5b016b4..502b4bda7f2 100644 --- a/plugins/network-elements/nuage-vsp/src/com/cloud/network/resource/NuageVspResource.java +++ b/plugins/network-elements/nuage-vsp/src/com/cloud/network/resource/NuageVspResource.java @@ -248,7 +248,7 @@ public class NuageVspResource extends ManagerBase implements ServerResource, Vsp return wrapper.execute(cmd, this); } catch (final Exception e) { if (s_logger.isDebugEnabled()) { - s_logger.debug("Received unsupported command " + cmd.toString()); + s_logger.debug("Received unsupported command " + cmd.toString(), e); } return Answer.createUnsupportedCommandAnswer(cmd); } diff --git a/plugins/network-elements/nuage-vsp/src/com/cloud/network/vsp/resource/wrapper/NuageVspExtraDhcpOptionsCommandWrapper.java b/plugins/network-elements/nuage-vsp/src/com/cloud/network/vsp/resource/wrapper/NuageVspExtraDhcpOptionsCommandWrapper.java new file mode 100644 index 00000000000..7bd7e7c31f4 --- /dev/null +++ b/plugins/network-elements/nuage-vsp/src/com/cloud/network/vsp/resource/wrapper/NuageVspExtraDhcpOptionsCommandWrapper.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.network.vsp.resource.wrapper; + +import net.nuage.vsp.acs.client.exception.NuageVspException; + +import javax.naming.ConfigurationException; + +import com.cloud.agent.api.element.ExtraDhcpOptionsVspCommand; +import com.cloud.network.resource.NuageVspResource; +import com.cloud.resource.ResourceWrapper; + +@ResourceWrapper(handles=ExtraDhcpOptionsVspCommand.class) +public class NuageVspExtraDhcpOptionsCommandWrapper extends NuageVspCommandWrapper { + @Override + public boolean executeNuageVspCommand(ExtraDhcpOptionsVspCommand command, NuageVspResource nuageVspResource) throws ConfigurationException, NuageVspException { + nuageVspResource.getNuageVspElementClient().setDhcpOptionsNic(command.getNetwork(), command.getNicUuid(), command.getDhcpOptions()); + return true; + } + + @Override + public StringBuilder fillDetail(StringBuilder stringBuilder, ExtraDhcpOptionsVspCommand command) { + return stringBuilder; + } +} diff --git a/server/src/com/cloud/api/ApiResponseHelper.java b/server/src/com/cloud/api/ApiResponseHelper.java index 0081b9d209a..3325be1069c 100644 --- a/server/src/com/cloud/api/ApiResponseHelper.java +++ b/server/src/com/cloud/api/ApiResponseHelper.java @@ -162,18 +162,21 @@ import com.cloud.uservm.UserVm; import com.cloud.utils.Pair; import com.cloud.utils.StringUtils; import com.cloud.utils.db.EntityManager; +import com.cloud.utils.net.Dhcp; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.Ip; import com.cloud.utils.net.NetUtils; import com.cloud.vm.ConsoleProxyVO; import com.cloud.vm.InstanceGroup; import com.cloud.vm.Nic; +import com.cloud.vm.NicExtraDhcpOptionVO; import com.cloud.vm.NicProfile; import com.cloud.vm.NicSecondaryIp; import com.cloud.vm.NicVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.dao.NicExtraDhcpOptionDao; import com.cloud.vm.dao.NicSecondaryIpVO; import com.cloud.vm.snapshot.VMSnapshot; import org.apache.cloudstack.acl.ControlledEntity; @@ -233,6 +236,7 @@ import org.apache.cloudstack.api.response.NetworkACLItemResponse; import org.apache.cloudstack.api.response.NetworkACLResponse; import org.apache.cloudstack.api.response.NetworkOfferingResponse; import org.apache.cloudstack.api.response.NetworkResponse; +import org.apache.cloudstack.api.response.NicExtraDhcpOptionResponse; import org.apache.cloudstack.api.response.NicResponse; import org.apache.cloudstack.api.response.NicSecondaryIpResponse; import org.apache.cloudstack.api.response.OvsProviderResponse; @@ -317,6 +321,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; +import java.util.stream.Collectors; public class ApiResponseHelper implements ResponseGenerator { @@ -349,6 +354,8 @@ public class ApiResponseHelper implements ResponseGenerator { private ClusterDetailsDao _clusterDetailsDao; @Inject private ResourceTagDao _resourceTagDao; + @Inject + private NicExtraDhcpOptionDao _nicExtraDhcpOptionDao; @Override public UserResponse createUserResponse(User user) { @@ -3583,6 +3590,7 @@ public class ApiResponseHelper implements ResponseGenerator { NetworkVO network = _entityMgr.findById(NetworkVO.class, result.getNetworkId()); VMInstanceVO vm = _entityMgr.findById(VMInstanceVO.class, result.getInstanceId()); UserVmJoinVO userVm = _entityMgr.findById(UserVmJoinVO.class, result.getInstanceId()); + List nicExtraDhcpOptionVOs = _nicExtraDhcpOptionDao.listByNicId(result.getId()); response.setId(result.getUuid()); response.setNetworkid(network.getUuid()); @@ -3601,6 +3609,13 @@ public class ApiResponseHelper implements ResponseGenerator { } response.setIpaddress(result.getIPv4Address()); + List nicExtraDhcpOptionResponses = nicExtraDhcpOptionVOs + .stream() + .map(vo -> new NicExtraDhcpOptionResponse(Dhcp.DhcpOptionCode.valueOfInt(vo.getCode()).getName(), vo.getCode(), vo.getValue())) + .collect(Collectors.toList()); + + response.setExtraDhcpOptions(nicExtraDhcpOptionResponses); + if (result.getSecondaryIp()) { List secondaryIps = ApiDBUtils.findNicSecondaryIps(result.getId()); if (secondaryIps != null) { diff --git a/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java index 75b42c21979..c0eb222be16 100644 --- a/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java +++ b/server/src/com/cloud/api/query/dao/UserVmJoinDaoImpl.java @@ -24,12 +24,14 @@ import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import javax.inject.Inject; import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ApiConstants.VMDetails; import org.apache.cloudstack.api.ResponseObject.ResponseView; +import org.apache.cloudstack.api.response.NicExtraDhcpOptionResponse; import org.apache.cloudstack.api.response.NicResponse; import org.apache.cloudstack.api.response.NicSecondaryIpResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; @@ -49,9 +51,11 @@ import com.cloud.user.dao.UserDao; import com.cloud.uservm.UserVm; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.net.Dhcp; import com.cloud.vm.UserVmDetailVO; import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.VmStats; +import com.cloud.vm.dao.NicExtraDhcpOptionDao; import com.cloud.vm.dao.NicSecondaryIpVO; import com.cloud.vm.dao.UserVmDetailsDao; @@ -67,6 +71,8 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation VmDetailSearch; private final SearchBuilder activeVmByIsoSearch; @@ -267,6 +273,13 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation nicExtraDhcpOptionResponses = _nicExtraDhcpOptionDao.listByNicId(nic_id) + .stream() + .map(vo -> new NicExtraDhcpOptionResponse(Dhcp.DhcpOptionCode.valueOfInt(vo.getCode()).getName(), vo.getCode(), vo.getValue())) + .collect(Collectors.toList()); + nicResponse.setExtraDhcpOptions(nicExtraDhcpOptionResponses); + userVmResponse.addNic(nicResponse); } } @@ -368,6 +381,11 @@ public class UserVmJoinDaoImpl extends GenericDaoBaseWithTagInformation nicExtraDhcpOptionResponses = _nicExtraDhcpOptionDao.listByNicId(nic_id) + .stream() + .map(vo -> new NicExtraDhcpOptionResponse(Dhcp.DhcpOptionCode.valueOfInt(vo.getCode()).getName(), vo.getCode(), vo.getValue())) + .collect(Collectors.toList()); + nicResponse.setExtraDhcpOptions(nicExtraDhcpOptionResponses); userVmData.addNic(nicResponse); } diff --git a/server/src/com/cloud/network/as/AutoScaleManagerImpl.java b/server/src/com/cloud/network/as/AutoScaleManagerImpl.java index d9113607772..9d3944de29a 100644 --- a/server/src/com/cloud/network/as/AutoScaleManagerImpl.java +++ b/server/src/com/cloud/network/as/AutoScaleManagerImpl.java @@ -1325,18 +1325,18 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScale vm = _userVmService.createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, null, owner, "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), null, null, null, HypervisorType.XenServer, HTTPMethod.GET, null, null, null, - null, true, null, null, null, null); + null, true, null, null, null, null, null); } else { if (zone.isSecurityGroupEnabled()) { vm = _userVmService.createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, null, null, owner, "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), null, null, null, HypervisorType.XenServer, HTTPMethod.GET, null, null, - null, null, true, null, null, null, null); + null, null, true, null, null, null, null, null); } else { vm = _userVmService.createAdvancedVirtualMachine(zone, serviceOffering, template, null, owner, "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), "autoScaleVm-" + asGroup.getId() + "-" + getCurrentTimeStampString(), - null, null, null, HypervisorType.XenServer, HTTPMethod.GET, null, null, null, addrs, true, null, null, null, null); + null, null, null, HypervisorType.XenServer, HTTPMethod.GET, null, null, null, addrs, true, null, null, null, null, null); } } diff --git a/server/src/com/cloud/network/element/VirtualRouterElement.java b/server/src/com/cloud/network/element/VirtualRouterElement.java index 9021999da3f..896a4892920 100644 --- a/server/src/com/cloud/network/element/VirtualRouterElement.java +++ b/server/src/com/cloud/network/element/VirtualRouterElement.java @@ -930,6 +930,11 @@ NetworkMigrationResponder, AggregatedCommandExecutor, RedundantResource, DnsServ return removeDhcpSupportForSubnet(network, Service.Dhcp); } + @Override + public boolean setExtraDhcpOptions(Network network, long nicId, Map dhcpOptions) { + return false; + } + @Override public boolean removeDnsSupportForSubnet(Network network) throws ResourceUnavailableException { // Ignore if virtual router is already dhcp provider diff --git a/server/src/com/cloud/vm/UserVmManager.java b/server/src/com/cloud/vm/UserVmManager.java index 6a384f1162f..6ffc28e4403 100644 --- a/server/src/com/cloud/vm/UserVmManager.java +++ b/server/src/com/cloud/vm/UserVmManager.java @@ -106,7 +106,7 @@ public interface UserVmManager extends UserVmService { boolean setupVmForPvlan(boolean add, Long hostId, NicProfile nic); UserVm updateVirtualMachine(long id, String displayName, String group, Boolean ha, Boolean isDisplayVmEnabled, Long osTypeId, String userData, - Boolean isDynamicallyScalable, HTTPMethod httpMethod, String customId, String hostName, String instanceName, List securityGroupIdList) throws ResourceUnavailableException, InsufficientCapacityException; + Boolean isDynamicallyScalable, HTTPMethod httpMethod, String customId, String hostName, String instanceName, List securityGroupIdList, Map> extraDhcpOptionsMap) throws ResourceUnavailableException, InsufficientCapacityException; //the validateCustomParameters, save and remove CustomOfferingDetils functions can be removed from the interface once we can //find a common place for all the scaling and upgrading code of both user and systemvms. diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 791ad952fbd..f5365eb71f7 100644 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -32,10 +32,15 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; + import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.affinity.AffinityGroupService; @@ -89,9 +94,6 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.lang.StringUtils; -import org.apache.log4j.Logger; import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; @@ -296,6 +298,7 @@ import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.InstanceGroupDao; import com.cloud.vm.dao.InstanceGroupVMMapDao; import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.NicExtraDhcpOptionDao; import com.cloud.vm.dao.SecondaryStorageVmDao; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.UserVmDetailsDao; @@ -496,6 +499,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir protected IpAddressManager _ipAddrMgr; @Inject private SnapshotApiService _snapshotService; + @Inject + NicExtraDhcpOptionDao _nicExtraDhcpOptionDao; protected ScheduledExecutorService _executor = null; protected ScheduledExecutorService _vmIpFetchExecutor = null; @@ -1161,10 +1166,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } if (caller.getType() != Account.ACCOUNT_TYPE_ADMIN) { - if (!(network.getGuestType() == Network.GuestType.Shared && network.getAclType() == ACLType.Domain) - && !(network.getAclType() == ACLType.Account && network.getAccountId() == vmInstance.getAccountId())) { - throw new InvalidParameterValueException("only shared network or isolated network with the same account_id can be added to vmId: " + vmId); - } + if (!(network.getGuestType() == Network.GuestType.Shared && network.getAclType() == ACLType.Domain) + && !(network.getAclType() == ACLType.Account && network.getAccountId() == vmInstance.getAccountId())) { + throw new InvalidParameterValueException("only shared network or isolated network with the same account_id can be added to vmId: " + vmId); + } } List allNics = _nicDao.listByVmId(vmInstance.getId()); @@ -1220,6 +1225,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir try { guestNic = _itMgr.addVmToNetwork(vmInstance, network, profile); + saveExtraDhcpOptions(guestNic.getId(), cmd.getDhcpOptionsMap()); + _networkMgr.configureExtraDhcpOptions(network, guestNic.getId(), cmd.getDhcpOptionsMap()); cleanUp = false; } catch (ResourceUnavailableException e) { throw new CloudRuntimeException("Unable to add NIC to " + vmInstance + ": " + e); @@ -1245,6 +1252,17 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir return _vmDao.findById(vmInstance.getId()); } + + private void saveExtraDhcpOptions(long nicId, Map dhcpOptions) { + List nicExtraDhcpOptionVOList = dhcpOptions + .entrySet() + .stream() + .map(entry -> new NicExtraDhcpOptionVO(nicId, entry.getKey(), entry.getValue())) + .collect(Collectors.toList()); + + _nicExtraDhcpOptionDao.saveExtraDhcpOptions(nicExtraDhcpOptionVOList); + } + @Override @ActionEvent(eventType = EventTypes.EVENT_NIC_DELETE, eventDescription = "Removing Nic", async = true) public UserVm removeNicFromVirtualMachine(RemoveNicFromVMCmd cmd) throws InvalidParameterValueException, PermissionDeniedException, CloudRuntimeException { @@ -2417,7 +2435,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } return updateVirtualMachine(id, displayName, group, ha, isDisplayVm, osTypeId, userData, isDynamicallyScalable, - cmd.getHttpMethod(), cmd.getCustomId(), hostName, cmd.getInstanceName(), securityGroupIdList); + cmd.getHttpMethod(), cmd.getCustomId(), hostName, cmd.getInstanceName(), securityGroupIdList, cmd.getDhcpOptionsMap()); } private void saveUsageEvent(UserVmVO vm) { @@ -2467,7 +2485,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir @Override public UserVm updateVirtualMachine(long id, String displayName, String group, Boolean ha, Boolean isDisplayVmEnabled, Long osTypeId, String userData, - Boolean isDynamicallyScalable, HTTPMethod httpMethod, String customId, String hostName, String instanceName, List securityGroupIdList) + Boolean isDynamicallyScalable, HTTPMethod httpMethod, String customId, String hostName, String instanceName, List securityGroupIdList, Map> extraDhcpOptionsMap) throws ResourceUnavailableException, InsufficientCapacityException { UserVmVO vm = _vmDao.findById(id); if (vm == null) { @@ -2567,7 +2585,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } } } - + List nics = _nicDao.listByVmId(vm.getId()); if (hostName != null) { // Check is hostName is RFC compliant checkNameForRFCCompliance(hostName); @@ -2578,14 +2596,27 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } // Verify that vm's hostName is unique - List vmNtwks = new ArrayList(); - List nics = _nicDao.listByVmId(vm.getId()); + + List vmNtwks = new ArrayList(nics.size()); for (Nic nic : nics) { vmNtwks.add(_networkDao.findById(nic.getNetworkId())); } checkIfHostNameUniqueInNtwkDomain(hostName, vmNtwks); } + List networks = nics.stream() + .map(nic -> _networkDao.findById(nic.getNetworkId())) + .collect(Collectors.toList()); + + verifyExtraDhcpOptionsNetwork(extraDhcpOptionsMap, networks); + for (Nic nic : nics) { + _networkMgr.saveExtraDhcpOptions(networks.stream() + .filter(network -> network.getId() == nic.getNetworkId()) + .findFirst() + .get() + .getUuid(), nic.getId(), extraDhcpOptionsMap); + } + _vmDao.updateVM(id, displayName, ha, osTypeId, userData, isDisplayVmEnabled, isDynamicallyScalable, customId, hostName, instanceName); if (updateUserdata) { @@ -2911,7 +2942,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, - Map customParametes, String customId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, + Map customParametes, String customId, Map> dhcpOptionMap) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); @@ -2959,7 +2990,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, - userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParametes, customId); + userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParametes, customId, dhcpOptionMap); } @@ -2968,7 +2999,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, - List affinityGroupIdList, Map customParameters, String customId) throws InsufficientCapacityException, ConcurrentOperationException, + List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); @@ -3070,7 +3101,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, - userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParameters, customId); + userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap); } @Override @@ -3078,7 +3109,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir public UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayvm, String keyboard, List affinityGroupIdList, - Map customParametrs, String customId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, + Map customParametrs, String customId, Map> dhcpOptionsMap) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { Account caller = CallContext.current().getCallingAccount(); @@ -3171,9 +3202,28 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir networkList.add(network); } } + verifyExtraDhcpOptionsNetwork(dhcpOptionsMap, networkList); return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, null, group, httpmethod, userData, - sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayvm, keyboard, affinityGroupIdList, customParametrs, customId); + sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayvm, keyboard, affinityGroupIdList, customParametrs, customId, dhcpOptionsMap); + } + + private void verifyExtraDhcpOptionsNetwork(Map> dhcpOptionsMap, List networkList) throws InvalidParameterValueException { + if (dhcpOptionsMap != null) { + for (String networkUuid : dhcpOptionsMap.keySet()) { + boolean networkFound = false; + for (NetworkVO network : networkList) { + if (network.getUuid().equals(networkUuid)) { + networkFound = true; + break; + } + } + + if (!networkFound) { + throw new InvalidParameterValueException("VM does not has a nic in the Network (" + networkUuid + ") that is specified in the extra dhcp options."); + } + } + } } public void checkNameForRFCCompliance(String name) { @@ -3187,7 +3237,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir protected UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate tmplt, String hostName, String displayName, Account owner, Long diskOfferingId, Long diskSize, List networkList, List securityGroupIdList, String group, HTTPMethod httpmethod, String userData, String sshKeyPair, HypervisorType hypervisor, Account caller, Map requestedIps, IpAddresses defaultIps, Boolean isDisplayVm, String keyboard, - List affinityGroupIdList, Map customParameters, String customId) throws InsufficientCapacityException, ResourceUnavailableException, + List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException, StorageUnavailableException, ResourceAllocationException { _accountMgr.checkAccess(caller, null, true, owner); @@ -3520,7 +3570,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } UserVmVO vm = commitUserVm(zone, template, hostName, displayName, owner, diskOfferingId, diskSize, userData, caller, isDisplayVm, keyboard, accountId, userId, offering, - isIso, sshPublicKey, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters); + isIso, sshPublicKey, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters, dhcpOptionMap); // Assign instance to the group try { @@ -3580,7 +3630,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir private UserVmVO commitUserVm(final DataCenter zone, final VirtualMachineTemplate template, final String hostName, final String displayName, final Account owner, final Long diskOfferingId, final Long diskSize, final String userData, final Account caller, final Boolean isDisplayVm, final String keyboard, final long accountId, final long userId, final ServiceOfferingVO offering, final boolean isIso, final String sshPublicKey, final LinkedHashMap networkNicMap, - final long id, final String instanceName, final String uuidName, final HypervisorType hypervisorType, final Map customParameters) throws InsufficientCapacityException { + final long id, final String instanceName, final String uuidName, final HypervisorType hypervisorType, final Map customParameters, final Map> extraDhcpOptionMap) throws InsufficientCapacityException { return Transaction.execute(new TransactionCallbackWithException() { @Override public UserVmVO doInTransaction(TransactionStatus status) throws InsufficientCapacityException { @@ -3686,10 +3736,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir if (isIso) { _orchSrvc.createVirtualMachineFromScratch(vm.getUuid(), Long.toString(owner.getAccountId()), vm.getIsoId().toString(), hostName, displayName, hypervisorType.name(), guestOSCategory.getName(), offering.getCpu(), offering.getSpeed(), offering.getRamSize(), diskSize, computeTags, rootDiskTags, - networkNicMap, plan); + networkNicMap, plan, extraDhcpOptionMap); } else { _orchSrvc.createVirtualMachine(vm.getUuid(), Long.toString(owner.getAccountId()), Long.toString(template.getId()), hostName, displayName, hypervisorType.name(), - offering.getCpu(), offering.getSpeed(), offering.getRamSize(), diskSize, computeTags, rootDiskTags, networkNicMap, plan, rootDiskSize); + offering.getCpu(), offering.getSpeed(), offering.getRamSize(), diskSize, computeTags, rootDiskTags, networkNicMap, plan, rootDiskSize, extraDhcpOptionMap); } if (s_logger.isDebugEnabled()) { @@ -4634,7 +4684,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir Long templateId = cmd.getTemplateId(); - if(!serviceOffering.isDynamic()) { + if (!serviceOffering.isDynamic()) { for(String detail: cmd.getDetails().keySet()) { if(detail.equalsIgnoreCase(VmDetailConstants.CPU_NUMBER) || detail.equalsIgnoreCase(VmDetailConstants.CPU_SPEED) || detail.equalsIgnoreCase(VmDetailConstants.MEMORY)) { throw new InvalidParameterValueException("cpuNumber or cpuSpeed or memory should not be specified for static service offering"); @@ -4685,13 +4735,13 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } else { vm = createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, getSecurityGroupIdList(cmd), owner, name, displayName, diskOfferingId, size , group , cmd.getHypervisor(), cmd.getHttpMethod(), userData , sshKeyPairName , cmd.getIpToNetworkMap(), addrs, displayVm , keyboard , cmd.getAffinityGroupIdList(), - cmd.getDetails(), cmd.getCustomId()); + cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap()); } } else { if (zone.isSecurityGroupEnabled()) { vm = createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, cmd.getNetworkIds(), getSecurityGroupIdList(cmd), owner, name, displayName, diskOfferingId, size, group, cmd.getHypervisor(), cmd.getHttpMethod(), userData, sshKeyPairName, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard, - cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId()); + cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap()); } else { if (cmd.getSecurityGroupIdList() != null && !cmd.getSecurityGroupIdList().isEmpty()) { @@ -4699,7 +4749,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } vm = createAdvancedVirtualMachine(zone, serviceOffering, template, cmd.getNetworkIds(), owner, name, displayName, diskOfferingId, size, group, cmd.getHypervisor(), cmd.getHttpMethod(), userData, sshKeyPairName, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard, cmd.getAffinityGroupIdList(), cmd.getDetails(), - cmd.getCustomId()); + cmd.getCustomId(), cmd.getDhcpOptionsMap()); } } return vm; @@ -5546,7 +5596,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir VirtualMachine vmi = _itMgr.findById(vm.getId()); VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vmi); - _networkMgr.allocate(vmProfile, networks); + _networkMgr.allocate(vmProfile, networks, null); _securityGroupMgr.addInstanceToGroups(vm.getId(), securityGroupIdList); @@ -5668,7 +5718,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir if (applicableNetworks.isEmpty()) { throw new InvalidParameterValueException("No network is specified, please specify one when you move the vm. For now, please add a network to VM on NICs tab."); } else { - _networkMgr.allocate(vmProfile, networks); + _networkMgr.allocate(vmProfile, networks, null); } _securityGroupMgr.addInstanceToGroups(vm.getId(), @@ -5796,7 +5846,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } VirtualMachine vmi = _itMgr.findById(vm.getId()); VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vmi); - _networkMgr.allocate(vmProfile, networks); + _networkMgr.allocate(vmProfile, networks, null); s_logger.debug("AssignVM: Advance virtual, adding networks no " + networks.size() + " to " + vm.getInstanceName()); } // END IF NON SEC GRP ENABLED } // END IF ADVANCED diff --git a/server/test/com/cloud/vpc/MockNetworkManagerImpl.java b/server/test/com/cloud/vpc/MockNetworkManagerImpl.java index 9b895c25541..81f06bccfae 100644 --- a/server/test/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/test/com/cloud/vpc/MockNetworkManagerImpl.java @@ -523,11 +523,20 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches * @see com.cloud.network.NetworkManager#allocate(com.cloud.vm.VirtualMachineProfile, java.util.List) */ @Override - public void allocate(VirtualMachineProfile vm, LinkedHashMap> networks) + public void allocate(VirtualMachineProfile vm, LinkedHashMap> networks, final Map> extraDhcpOptions) throws InsufficientCapacityException, ConcurrentOperationException { // TODO Auto-generated method stub } + @Override + public void configureExtraDhcpOptions(Network network, long nicId, Map extraDhcpOptions) { + + } + + @Override public void configureExtraDhcpOptions(Network network, long nicId) { + + } + /* (non-Javadoc) * @see com.cloud.network.NetworkManager#prepare(com.cloud.vm.VirtualMachineProfile, com.cloud.deploy.DeployDestination, com.cloud.vm.ReservationContext) */ @@ -589,6 +598,11 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches return null; } + @Override + public Map getExtraDhcpOptions(long nicId) { + return null; + } + /* (non-Javadoc) * @see com.cloud.network.NetworkManager#shutdownNetwork(long, com.cloud.vm.ReservationContext, boolean) */ @@ -653,6 +667,11 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches return false; } + @Override + public void saveExtraDhcpOptions(String networkUuid, Long nicId, Map> extraDhcpOptionMap) { + + } + /* (non-Javadoc) * @see com.cloud.network.NetworkManager#allocateNic(com.cloud.vm.NicProfile, com.cloud.network.Network, java.lang.Boolean, int, com.cloud.vm.VirtualMachineProfile) */ diff --git a/setup/db/db/schema-41000to41100.sql b/setup/db/db/schema-41000to41100.sql index dbf798a92e6..0b099d6ef2e 100644 --- a/setup/db/db/schema-41000to41100.sql +++ b/setup/db/db/schema-41000to41100.sql @@ -449,3 +449,14 @@ CREATE VIEW `cloud`.`volume_view` AS `cloud`.`account` resource_tag_account ON resource_tag_account.id = resource_tags.account_id left join `cloud`.`domain` resource_tag_domain ON resource_tag_domain.id = resource_tags.domain_id; + +-- Extra Dhcp Options +CREATE TABLE `cloud`.`nic_extra_dhcp_options` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `uuid` varchar(255) UNIQUE, + `nic_id` bigint unsigned NOT NULL COMMENT ' nic id where dhcp options are applied', + `code` int(32), + `value` text, + PRIMARY KEY (`id`), + CONSTRAINT `fk_nic_extra_dhcp_options_nic_id` FOREIGN KEY (`nic_id`) REFERENCES `nics`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/test/integration/plugins/nuagevsp/nuageTestCase.py b/test/integration/plugins/nuagevsp/nuageTestCase.py index 9ab4726d95f..121921b3f82 100644 --- a/test/integration/plugins/nuagevsp/nuageTestCase.py +++ b/test/integration/plugins/nuagevsp/nuageTestCase.py @@ -78,6 +78,35 @@ class needscleanup(object): return _wrapper +class gherkin(object): + BLACK = "\033[0;30m" + BLUE = "\033[0;34m" + GREEN = "\033[0;32m" + CYAN = "\033[0;36m" + RED = "\033[0;31m" + BOLDBLUE = "\033[1;34m" + NORMAL = "\033[0m" + + def __init__(self, method): + self.method = method + + def __get__(self, obj=None, objtype=None): + @functools.wraps(self.method) + def _wrapper(*args, **kwargs): + gherkin_step = self.method.__name__.replace("_", " ").capitalize() + obj.info("=G= %s%s%s" % (self.BOLDBLUE, gherkin_step, self.NORMAL)) + try: + result = self.method(obj, *args, **kwargs) + obj.info("=G= %s%s: [SUCCESS]%s" % + (self.GREEN, gherkin_step, self.NORMAL)) + return result + except Exception as e: + obj.info("=G= %s%s: [FAILED]%s%s" % + (self.RED, gherkin_step, self.NORMAL, e)) + raise + return _wrapper + + class nuageTestCase(cloudstackTestCase): @classmethod diff --git a/test/integration/plugins/nuagevsp/test_nuage_extra_dhcp.py b/test/integration/plugins/nuagevsp/test_nuage_extra_dhcp.py new file mode 100644 index 00000000000..cc52e66d7b1 --- /dev/null +++ b/test/integration/plugins/nuagevsp/test_nuage_extra_dhcp.py @@ -0,0 +1,2037 @@ +# 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. + +""" Component tests for extra dhcp options functionality with +Nuage VSP SDN plugin +""" +# Import Local Modules +from nuageTestCase import (nuageTestCase, gherkin) +from marvin.cloudstackAPI import updateVirtualMachine, updateZone +from marvin.lib.base import (Account, + Network, + VirtualMachine, + Configurations, + NetworkOffering) +# Import System Modules +from concurrent.futures import ThreadPoolExecutor, wait +from nose.plugins.attrib import attr +import copy +import time + + +class TestNuageExtraDhcp(nuageTestCase): + """ Test basic VPC Network functionality with Nuage VSP SDN plugin + """ + + @classmethod + def setUpClass(cls, zone=None): + super(TestNuageExtraDhcp, cls).setUpClass() + cls.vmdata = cls.test_data["virtual_machine"] + cls.sharednetworkdata = cls.test_data["acl"] + + # Create an account + cls.account = Account.create(cls.api_client, + cls.test_data["account"], + admin=True, + domainid=cls.domain.id + ) + + cmd = updateZone.updateZoneCmd() + cmd.id = cls.zone.id + cmd.domain = "testvpc.com" + cls.api_client.updateZone(cmd) + cls.vpc_offering = cls.create_VpcOffering( + cls.test_data["nuagevsp"]["vpc_offering_nuage_dhcp"]) + cls.vpc1 = cls.create_Vpc(cls.vpc_offering, cidr="10.0.0.0/16", + networkDomain="testvpc.com") + + cls.vpc_network_offering = cls.create_NetworkOffering( + cls.test_data["nuagevsp"]["vpc_network_offering_nuage_dhcp"]) + + cls.vpc_network = cls.create_Network( + cls.vpc_network_offering, gateway="10.0.0.1", vpc=cls.vpc1) + + cmd.domain = "testisolated.com" + cls.api_client.updateZone(cmd) + + # create the isolated network + cls.isolated_network_offering = cls.create_NetworkOffering( + cls.test_data["nuagevsp"]["isolated_network_offering"], True) + cls.isolated_network = cls.create_Network( + cls.isolated_network_offering, gateway="10.0.0.1", + netmask="255.255.255.0") + + cmd.domain = "testshared.com" + cls.api_client.updateZone(cmd) + # Create Shared Network + cls.shared_network_offering = NetworkOffering.create( + cls.api_client, + cls.test_data["nuagevsp"]["shared_nuage_network_offering"], + conservemode=False + ) + + cls.shared_network_offering.update(cls.api_client, state='Enabled') + cls.shared_network_offering_id = cls.shared_network_offering.id + + cls.shared_network_all = Network.create( + cls.api_client, + cls.test_data["nuagevsp"]["network_all"], + networkofferingid=cls.shared_network_offering_id, + zoneid=cls.zone.id + ) + cls.dhcp_options_map = {} + cls.dhcp_options_to_verify_map = {} + cls.expected_dhcp_options_on_vm = {} + cls.dhcp_options_map_keys = [1, 16, 28, 41, 64, 93] + + cls._cleanup = [ + cls.shared_network_all, + cls.shared_network_offering, + cls.account + ] + return + + def setUp(self): + self.vmdata["displayname"] = "vm" + self.vmdata["name"] = "vm" + self.update_NuageVspGlobalDomainTemplateName(name="") + self.dhcp_options_map.update({1: {"dhcp:1": "255.255.255.0", + "dhcp:2": "10", + "dhcp:4": "10.0.0.2," + "10.0.0.3," + "10.0.0.4", + "dhcp:7": "10.0.0.5,10.0.0.6", + "dhcp:9": "10.0.0.7", + "dhcp:13": "255", + }}) + self.dhcp_options_map.update({16: {"dhcp:16": "10.0.0.8", + "dhcp:17": "/tmp/", + "dhcp:18": "/ext/", + "dhcp:19": "1", + "non-local-source-routing": "0", + "policy-filter": "10.1.2.12," + "255.255.255.0", + "max-datagram-reassembly": "1000", + "default-ttl": "255", + "mtu": "1024", + "all-subnets-local": "1"}}) + self.dhcp_options_map.update({28: {"broadcast": "10.1.2.255", + "router-discovery": "0", + "router-solicitation": "10.1.2.2", + "static-route": "10.0.0.1,10.0.0.2", + "trailer-encapsulation": "1", + "arp-timeout": "255", + "ethernet-encap": "1", + "tcp-ttl": "255", + "tcp-keepalive": "255", + "nis-domain": "www.test.com"}}) + self.dhcp_options_map.update({41: {"nis-server": "10.1.1.1,10.1.1.2", + "ntp-server": "10.1.1.3,10.1.1.4", + "netbios-ns": "10.1.1.5,10.1.1.6", + "netbios-dd": "10.1.1.8,10.1.1.7", + "netbios-nodetype": "08", + "netbios-scope": "test2", + "x-windows-fs": "10.1.2.13," + "10.1.2.16", + "x-windows-dm": "10.1.2.14," + "10.1.2.15", + "requested-address": "10.1.2.16", + "vendor-class": "01"}}) + self.dhcp_options_map.update({64: {"nis+-domain": "www.test3.com", + "nis+-server": "10.1.2.255," + "10.1.2.254", + "tftp-server": "www.test4.com", + "bootfile-name": "file.txt", + "mobile-ip-home": "10.1.2.18," + "10.1.2.19", + "smtp-server": "10.1.2.20," + "10.1.2.21", + "pop3-server": "10.1.2.2,10.1.2.3", + "nntp-server": "10.1.2.5,10.1.2.4", + "irc-server": "10.1.2.1,10.1.2.4", + "user-class": "user-class"}}) + self.dhcp_options_map.update({93: {"client-arch": "16", + "client-interface-id": "01", + "client-machine-id": "01", + "dhcp:114": + "http://www.testdhcpfeature." + "com/adfsgbfgtdhh125ki-23-fdh" + "-09", + "domain-search": "www.domain.com", + "sip-server": "www.sip.com", + "classless-static-route": + "10.1.0.0/16,10.1.0.1", + "vendor-id-encap": + "0000ACEF04CAFEBABE" + }}) + self.dhcp_options_to_verify_map.update({1: {1: "255.255.255.0", + 2: "10", + 4: ["10.0.0.2", + "10.0.0.3", + "10.0.0.4"], + 7: ["10.0.0.5", + "10.0.0.6"], + 9: "10.0.0.7", + 13: "255"}}) + self.dhcp_options_to_verify_map.update({16: {16: "10.0.0.8", + 17: "/tmp/", + 18: "/ext/", + 19: "1", + 20: "0", + 21: ["10.1.2.12", + "255.255.255.0"], + 22: "1000", + 23: "255", + 26: "1024", + 27: "1"}}) + self.dhcp_options_to_verify_map.update({28: {28: "10.1.2.255", + 31: "0", + 32: "10.1.2.2", + 33: ["10.0.0.1", + "10.0.0.2"], + 34: "1", + 35: "255", + 36: "1", + 37: "255", + 38: "255", + 40: "www.test.com"}}) + self.dhcp_options_to_verify_map.update({41: {41: ["10.1.1.1", + "10.1.1.2"], + 42: ["10.1.1.3", + "10.1.1.4"], + 44: ["10.1.1.5", + "10.1.1.6"], + 45: ["10.1.1.8", + "10.1.1.7"], + 46: "H-node", + 47: "test2", + 48: ["10.1.2.13", + "10.1.2.16"], + 49: ["10.1.2.14", + "10.1.2.15"], + 50: "10.1.2.16", + 60: "01"}}) + self.dhcp_options_to_verify_map.update({64: {64: "www.test3.com", + 65: ["10.1.2.255", + "10.1.2.254"], + 66: "www.test4.com", + 67: "file.txt", + 68: ["10.1.2.18", + "10.1.2.19"], + 69: ["10.1.2.20", + "10.1.2.21"], + 70: ["10.1.2.2", + "10.1.2.3"], + 71: ["10.1.2.5", + "10.1.2.4"], + 74: ["10.1.2.1", + "10.1.2.4"], + 77: "user-class"}}) + self.dhcp_options_to_verify_map.update({93: {93: "16", + 94: "01", + 97: "01", + 114: "http://www.testdhcp" + "feature.com/adfsgbf" + "gtdhh125ki-23-fdh-" + "09", + 119: "www.domain.com", + 120: "www.sip.com", + 121: ["10.1.0.0/16", + "10.1.0.1"], + 125: "0000acef04cafebabe" + }}) + self.expected_dhcp_options_on_vm.update({46: "netbios-node-type 8", + 60: "vendor-class-identifier" + " 1", + 94: "unknown-94 1", + 93: "unknown-93", + 97: "unknown-97 1", + 119: "unknown-119", + 120: "unknown-120", + 121: "unknown-121", + 125: "0:0:ac:ef:4:ca:fe:ba:be" + }) + self.cleanup = [] + return + + def tearDown(self): + super(TestNuageExtraDhcp, self).tearDown() + # Cleanup resources used + self.debug("Cleaning up the resources") + self.update_NuageVspGlobalDomainTemplateName(name="") + for obj in reversed(self.cleanup): + try: + if isinstance(obj, VirtualMachine): + obj.delete(self.api_client, expunge=True) + else: + obj.delete(self.api_client) + except Exception as e: + self.error("Failed to cleanup %s, got %s" % (obj, e)) + # cleanup_resources(self.api_client, self.cleanup) + self.cleanup = [] + self.debug("Cleanup complete!") + return + + def retrieve_dhcp_values_to_verify_on_vm_based_on(self, dhcp_map): + vm_dhcp_map = copy.deepcopy(dhcp_map) + for dhcpcode, dhcpval in self.expected_dhcp_options_on_vm.iteritems(): + if dhcpcode in dhcp_map: + vm_dhcp_map[dhcpcode] = dhcpval + + return vm_dhcp_map + + def verify_vsd_dhcp_option_subnet(self, dhcp_type, value, subnet): + self.debug("Verifying the creation and value of DHCP option type -" + " %s in VSD" % dhcp_type) + found_dhcp_type = False + dhcp_options = self.vsd.get_subnet_dhcpoptions( + filter=self.get_externalID_filter(subnet.id)) + for dhcp_option in dhcp_options: + self.debug("dhcptype option in vsd is : %s" + % dhcp_option.actual_type) + self.debug("dhcptype expected value is: %s" % value) + if dhcp_option.actual_type == dhcp_type: + found_dhcp_type = True + if isinstance(dhcp_option.actual_values, list): + self.debug("dhcptype actual value on vsd is %s:" + "" % dhcp_option.actual_values) + if value in dhcp_option.actual_values: + self.debug("Excepted DHCP option value found in" + " VSD") + else: + self.fail("Excepted DHCP option value not found" + " in VSD") + else: + self.debug("dhcptype actual value on vsd is %s:" + % dhcp_option.actual_values) + self.assertEqual(dhcp_option.actual_values, value, + "Expected DHCP option value is not same" + " in both CloudStack and VSD") + if not found_dhcp_type: + self.fail("Expected DHCP option type and value not found" + " in the VSD") + self.debug("Successfully verified the creation and value of DHCP" + " option type - %s in VSD" % dhcp_type) + + def verify_vsd_dhcp_option(self, dhcp_type, value, vm_interface): + self.debug("Verifying the creation and value of DHCP option type -" + " %s in VSD" % dhcp_type) + self.debug("Expected value for this dhcp option is - %s in VSD" + % value) + found_dhcp_type = False + dhcp_options = self.vsd.get_vm_interface_dhcpoptions( + filter=self.get_externalID_filter(vm_interface.id)) + for dhcp_option in dhcp_options: + self.debug("dhcptype on vsd is %s:" % dhcp_option.actual_type) + self.debug("dhcp value on vsd is: %s:" % dhcp_option.actual_values) + if dhcp_option.actual_type == dhcp_type: + found_dhcp_type = True + if isinstance(dhcp_option.actual_values, list): + self.debug("dhcptype actual value is %s:" % + dhcp_option.actual_values) + if type(value) is list: + for val in value: + self.is_value_in_options(dhcp_option.actual_values, + val) + else: + self.is_value_in_options(dhcp_option.actual_values, + value) + else: + self.assertEqual(dhcp_option.actual_values, value, + "Expected DHCP option value is not same" + " in both CloudStack and VSD") + if not found_dhcp_type: + self.fail("Expected DHCP option type and value not found in " + "the VSD for dhcp type %s " % dhcp_type) + self.debug("Successfully verified the creation and value of DHCP" + " option type - %s in VSD" % dhcp_type) + + def is_value_in_options(self, actual_options, value): + if value in actual_options: + self.debug("Excepted DHCP option value found in VSD") + else: + self.fail("Excepted DHCP option value not found in VSD") + + def verify_vsd_dhcp_option_empty(self, dhcp_type, vm_interface): + self.debug("Verifying the creation and value of DHCP option" + " type - %s in VSD" % dhcp_type) + self.debug("Expected value is empty string") + dhcp_options = self.vsd.get_vm_interface_dhcpoptions( + filter=self.get_externalID_filter(vm_interface.id)) + for dhcp_option in dhcp_options: + self.debug("dhcptype on vsd is %s:" % dhcp_option.actual_type) + self.debug("dhcp value on vsd is: %s:" % dhcp_option.value) + if dhcp_option.actual_type == dhcp_type: + if dhcp_type == 15: + self.assertEqual(dhcp_option.value, "\x00", + "Expected DHCP option value is not" + " same in both CloudStack and VSD") + else: + self.assertEqual(dhcp_option.value, "00", + "Expected DHCP option value is not" + " same in both CloudStack and VSD") + self.debug("Successfully verified the creation and value of" + " DHCP option type - %s in VSD" % dhcp_type) + + def verify_vsd_dhcp_value_notpresent(self, value, vm_interface): + self.debug("Verifying that on vminterface value is not present- %s" + % value) + dhcp_options = self.vsd.get_vm_interface_dhcpoptions( + filter=self.get_externalID_filter(vm_interface.id)) + for dhcp_option in dhcp_options: + self.debug("dhcptype option is %s:" % dhcp_option.actual_type) + if isinstance(dhcp_option.actual_values, list): + self.debug("dhcptype actual value is %s:" + % dhcp_option.actual_values) + if value in dhcp_option.actual_values: + self.fail("This value is not expected on vminterface but " + "present as dhcp_type %s" + % dhcp_option.actual_type) + else: + self.debug("As Excepted DHCP value not found in VSD") + else: + try: + self.assertEqual(dhcp_option.actual_values, value, + "Expected DHCP option value is not same " + "in both CloudStack and VSD") + self.fail("This value is not expected on vm interface but " + "present as dhcp_type %s" + % dhcp_option.actual_type) + except Exception: + self.debug("As Expected DHCP value not found in VSD") + self.debug("Successfully verified dhcp value is not present - %s " + "in VSD" % value) + + def verify_vsd_dhcp_type_notpresent(self, dhcp_types, vm_interface): + if type(dhcp_types) is not list: + dhcp_types = [dhcp_types] + + for dhcp_type in dhcp_types: + self.debug("Verifying that DHCP option type - %s not present" + " in VSD" % dhcp_type) + dhcp_options = self.vsd.get_vm_interface_dhcpoptions( + filter=self.get_externalID_filter(vm_interface.id)) + for dhcp_option in dhcp_options: + self.debug("dhcptype on vsd is %s:" % dhcp_option.actual_type) + if dhcp_option.actual_type == dhcp_type: + self.fail("Expected DHCP option type is not expected in " + "the VSD: %s" % dhcp_type) + self.debug("Successfully verified DHCP option type - %s " + "not present in the VSD" % dhcp_type) + + def verify_dhcp_on_vm( + self, dhcpleasefile, dhcp_option_map, ssh_client, cleanlease=True): + cmd = 'cat /var/lib/dhclient/'+dhcpleasefile + self.debug("get content of dhcp lease file " + cmd) + outputlist = ssh_client.execute(cmd) + self.debug("command is executed properly " + cmd) + completeoutput = str(outputlist).strip('[]') + self.debug("complete output is " + completeoutput) + + for key, value in dhcp_option_map.iteritems(): + if type(value) is list: + for val in value: + self.check_if_value_contains(completeoutput, val) + else: + self.check_if_value_contains(completeoutput, value) + if cleanlease: + self.remove_lease_file(ssh_client, dhcpleasefile) + + def check_if_value_contains(self, completeoutput, value): + if value in completeoutput: + self.debug("excepted value found in vm: " + value) + else: + self.fail("excepted value not found in vm: " + value) + + def remove_lease_file(self, ssh_client, dhcpleasefile): + cmd = 'rm -rf /var/lib/dhclient/'+dhcpleasefile + outputlist = ssh_client.execute(cmd) + completeoutput = str(outputlist).strip('[]') + self.debug("clear lease is done properly:" + completeoutput) + + def update_zone_details(self, value): + """Updates the VM data""" + # update Network Domain at zone level + cmd = updateZone.updateZoneCmd() + cmd.id = self.zone.id + cmd.domain = value + self.api_client.updateZone(cmd) + + def update_NuageVspGlobalDomainTemplateName(self, name): + self.debug("Updating global setting nuagevsp.vpc.domaintemplate.name " + "with value - %s" % name) + Configurations.update(self.api_client, + name="nuagevsp.vpc.domaintemplate.name", + value=name) + self.debug("Successfully updated global setting " + "nuagevsp.vpc.domaintemplate.name with value - %s" % name) + + def create_isolated_network( + self, network_offering=None, gateway="10.1.1.1", + netmask="255.255.255.0"): + # create a isolated network + self.debug("Creating an Isolated network...") + if not network_offering: + network_offering = self.create_isolated_network_offering() + + network = self.create_Network(network_offering, gateway, netmask) + + return network + + def validate_isolated_network( + self, network_offering, network): + self.debug("Validating network...") + self.validate_NetworkOffering(network_offering, state="Enabled") + self.validate_Network(network) + + def validate_vpc(self, vpc, vpc_offering): + self.debug("Validating vpc...") + self.validate_Vpc(vpc) + self.validate_VpcOffering(vpc_offering) + + def verify_dhcp_options_on_vm( + self, vm, network, vpc, dhcp_options, remove_lease_file=True, + lease_file="dhclient-eth0.leases", verify_on_vsd=True): + # Adding Ingress Firewall/Network ACL rule + self.debug("Adding Ingress Firewall/Network ACL rule to make the " + "created Static NAT rule (wget) accessible...") + public_ip = self.acquire_PublicIPAddress(network, vpc=vpc) + self.create_StaticNatRule_For_VM( + vm, public_ip, network) + + if vpc: + public_http_rule = self.create_NetworkAclRule( + self.test_data["ingress_rule"], network=network) + else: + public_http_rule = self.create_FirewallRule(public_ip) + + # VSD verification + if verify_on_vsd: + self.verify_vsd_firewall_rule(public_http_rule) + + ssh_client = self.ssh_into_VM(vm, public_ip) + dhcp_options_to_verify_on_vm =\ + self.retrieve_dhcp_values_to_verify_on_vm_based_on( + dhcp_options) + self.verify_dhcp_on_vm( + lease_file, dhcp_options_to_verify_on_vm, + ssh_client, remove_lease_file) + + # Removing Ingress Firewall/Network ACL rule + self.debug("Removing the created Ingress Firewall/Network ACL " + "rule in the network...") + public_http_rule.delete(self.api_client) + + public_ip.delete(self.api_client) + with self.assertRaises(Exception): + self.validate_PublicIPAddress(public_ip, network) + + def verify_dhcp_options_on_vsd( + self, vm, dhcp_options, + networks_with_options=None, + verify_vm_on_vsd=True): + + if verify_vm_on_vsd: + self.verify_vsd_vm(vm) + + if networks_with_options: + for nic in vm.nic: + if self.is_nic_in_network_list(nic, networks_with_options): + for key, value in dhcp_options.iteritems(): + self.verify_vsd_dhcp_option(key, value, nic) + else: + for nic in vm.nic: + for key, value in dhcp_options.iteritems(): + self.verify_vsd_dhcp_option(key, value, nic) + + @staticmethod + def is_nic_in_network_list(nic, network_list): + if type(network_list) is list: + for network in network_list: + if network.id == nic.networkid: + return True + return False + elif network_list.id == nic.networkid: + return True + return False + + def validate_network_on_vsd_based_on_networktype( + self, network, vpc=None, is_shared_network=False): + if is_shared_network: + self.verify_vsd_shared_network( + self.domain.id, network, + gateway=self.test_data["nuagevsp"]["network_all"]["gateway"]) + else: + self.verify_vsd_network(self.domain.id, network, vpc) + + def create_vpc_offering_with_nuage_dhcp(self): + # Creating a VPC offering + self.debug("Creating Nuage VSP VPC offering without dhcp...") + vpc_offering = self.create_VpcOffering( + self.test_data["nuagevsp"]["vpc_offering_nuage_dhcp"]) + self.validate_VpcOffering(vpc_offering, state="Enabled") + + return vpc_offering + + def create_isolated_network_offering(self): + network_offering = self.create_NetworkOffering( + self.test_data["nuagevsp"]["isolated_network_offering"]) + self.validate_NetworkOffering(network_offering, state="Enabled") + return network_offering + + def create_vpc_network_offering(self): + network_offering = self.create_NetworkOffering( + self.test_data["nuagevsp"]["vpc_network_offering_nuage_dhcp"]) + self.validate_NetworkOffering(network_offering, state="Enabled") + return network_offering + + def create_vpc(self, vpc_offering, cidr="10.0.0.0/16"): + # Creating a VPC + self.debug("Creating a VPC with Nuage VSP VPC offering...") + vpc = self.create_Vpc(vpc_offering, cidr=cidr, + networkDomain="testvpc.com") + self.validate_Vpc(vpc, state="Enabled") + + return vpc + + def create_vpc_with_tier(self, domain_name="testvpc.com"): + vpc_offering = self.create_vpc_offering_with_nuage_dhcp() + vpc = self.create_vpc(vpc_offering) + + vpc_network_offering = self.create_vpc_network_offering() + acl_list = self.create_acl_list_with_item(vpc) + vpc_first_tier = \ + self.when_i_create_a_first_vpc_network_with_nuage_dhcp( + vpc, vpc_network_offering, acl_list) + + self.verify_vsd_dhcp_option_subnet(15, domain_name, vpc_first_tier) + + return {"vpc": vpc, "tier": vpc_first_tier} + + def create_acl_list_with_item(self, vpc): + # Creating an ACL list + acl_list = self.create_NetworkAclList(name="acl", description="acl", + vpc=vpc) + + # Creating an ACL item + self.create_NetworkAclRule(self.test_data["ingress_rule"], + acl_list=acl_list) + + return acl_list + + @staticmethod + def add_extra_dhcp_options_to_check(dhcp_options_to_verify, domain_name, + remove_dns_options=False): + if not remove_dns_options: + dhcp_options_to_verify[12] = "vm1" + dhcp_options_to_verify[15] = domain_name + return dhcp_options_to_verify + + def get_extra_dhcp_options_starting_with(self, dhcp_option_code, + network=None): + dhcp_options =\ + copy.deepcopy(self.dhcp_options_map.get(dhcp_option_code)) + if network: + dhcp_options["networkid"] = network.id + return dhcp_options + + def get_extra_dhcp_options_to_verify_starting_with( + self, number, domain_name, remove_dns_options=False): + dhcp_options_to_verify = copy.deepcopy( + self.dhcp_options_to_verify_map.get(number)) + self.add_extra_dhcp_options_to_check( + dhcp_options_to_verify, domain_name, remove_dns_options) + return dhcp_options_to_verify + + @gherkin + def when_i_update_the_zone_details_and_restart_a_vpc(self, vpc): + self.update_zone_details("testvpc.com") + vpc.restart(self.api_client) + + @gherkin + def when_i_create_a_first_vpc_network_with_nuage_dhcp( + self, vpc, network_offering, acl_list, gateway="10.0.0.1"): + # Creating a VPC network in the VPC + self.debug( + "Creating a VPC network with Nuage VSP VPC Network offering...") + vpc_network = self.create_Network(network_offering, gateway=gateway, + vpc=vpc, acl_list=acl_list) + self.validate_Network(vpc_network, state="Implemented") + + return vpc_network + + @gherkin + def when_i_create_a_second_vpc_network_with_nuage_dhcp( + self, vpc, network_offering, acl_list): + vpc_network_1 = self.create_Network( + network_offering, gateway='10.1.2.1', vpc=vpc, acl_list=acl_list) + self.validate_Network(vpc_network_1, state="Implemented") + + return vpc_network_1 + + @gherkin + def when_i_stop_and_start_a_vm(self, vm): + vm.stop(self.api_client) + vm.start(self.api_client) + + @gherkin + def when_i_add_an_extra_nic_to_a_vm(self, vm, network, dhcp_options=None): + dhcp_options_list = None + + if dhcp_options: + if type(dhcp_options) is list: + dhcp_options_list = [] + for item in dhcp_options: + dhcp_options_list.extend([item]) + else: + dhcp_options_list = [dhcp_options] + + return vm.add_nic(self.api_client, network.id, + dhcpoptions=dhcp_options_list) + + @gherkin + def when_i_restart_a_network(self, network): + network.restart(self.api_client, cleanup=True) + + @gherkin + def when_i_create_a_vm( + self, network, vpc, vm_name, dhcp_options, + start_vm=True, is_shared_network=False, + ip_address=None): + vm_data = copy.deepcopy(self.vmdata) + if dhcp_options: + if type(dhcp_options) is list: + dhcp_options_list = [] + for item in dhcp_options: + dhcp_options_list.extend([item]) + else: + dhcp_options_list = [dhcp_options] + + vm_data["dhcpoptionsnetworklist"] = dhcp_options_list + elif "dhcpoptionsnetworklist" in vm_data: + del vm_data["dhcpoptionsnetworklist"] + + if ip_address: + vm_data["ipaddress"] = ip_address + if vm_name: + vm_data["displayname"] = vm_name + vm_data["name"] = vm_name + + vm = self.create_VM(network, start_vm=start_vm, testdata=vm_data) + if start_vm: + self.check_VM_state(vm, state="Running") + else: + self.check_VM_state(vm, state="Stopped") + # VSD verification + if type(network) is not list: + self.validate_network_on_vsd_based_on_networktype( + network, vpc, is_shared_network) + + return vm + + @gherkin + def when_i_update_extra_dhcp_options_on_a_vm(self, vm, dhcp_options): + """Updates the VM data""" + if type(dhcp_options) is list: + dhcp_options_list = [] + for item in dhcp_options: + dhcp_options_list.extend([item]) + else: + dhcp_options_list = [dhcp_options] + + cmd = updateVirtualMachine.updateVirtualMachineCmd() + cmd.id = vm.id + cmd.dhcpoptionsnetworklist = dhcp_options_list + self.api_client.updateVirtualMachine(cmd) + + @gherkin + def then_verify_domain_name_and_router_options_multi_nic_set( + self, multinic_vm, primary_network, domain_name="testvpc.com"): + for nic in multinic_vm.nic: + if nic.networkid != primary_network.id: + self.verify_vsd_dhcp_option(3, "0.0.0.0", nic) + self.verify_vsd_dhcp_option(15, "\x00", nic) + else: + self.verify_vsd_dhcp_option(15, domain_name, nic) + + @gherkin + def then_verify_dhcp_options_on_vsd_and_vm(self, vm, network, + dhcp_options_to_verify, + network_with_options=None, + is_shared_network=False, + verify_on_vm=False, + default_network=None, + vpc=None, + remove_lease_file=False, + verify_on_vsd=True): + + if verify_on_vsd: + self.verify_dhcp_options_on_vsd( + vm, dhcp_options_to_verify, network_with_options, + not is_shared_network) + if verify_on_vm and not self.isSimulator and not is_shared_network: + if default_network: + network = default_network + lease_file = "dhclient-eth1.leases" + else: + lease_file = "dhclient-eth0.leases" + + self.verify_dhcp_options_on_vm( + vm=vm, + network=network, + vpc=vpc, + dhcp_options=dhcp_options_to_verify, + lease_file=lease_file, + remove_lease_file=remove_lease_file, + verify_on_vsd=verify_on_vsd) + + @gherkin + def then_no_dhcp_options_present_on_vsd(self, dhcp_options_map, vm, + excluded_nics=None): + for nic in vm.nic: + if not excluded_nics or nic not in excluded_nics: + self.verify_vsd_dhcp_type_notpresent( + dhcp_options_map.keys(), nic) + self.verify_vsd_dhcp_value_notpresent( + dhcp_options_map.values(), nic) + + def validate_all_extra_dhcp_for_add_remove_nic_after_migrate( + self, network, domain_name="testisolated.com", + is_shared_network=False, verify_all_options=False): + # 1 - create an extra isolated network + # 2 - for each extra dhc option: + # a - deploy a vm + # b - migrate vm + # c - plug nic + # d - verify the dhcp options are correctly set on the nic + # e - remove nic + # f - verify the dhcp options are no longer present on the vsd + # and vm + # 3 - try to remove the default nic which has extra dhcp options set + # (this should fail) + + isolated_network2 =\ + self.create_isolated_network(gateway="10.0.1.1") + + if verify_all_options: + options_to_verify = self.dhcp_options_map_keys + else: + options_to_verify = [1] + + for number in options_to_verify: + vm1 = self.when_i_create_a_vm( + isolated_network2, None, "vm1", + dhcp_options=None, + is_shared_network=False) + + if not self.isSimulator: + self.migrate_VM(vm1) + + result = self.when_i_add_an_extra_nic_to_a_vm(vm1, network, None) + dhcp_options_network = self.get_extra_dhcp_options_starting_with( + number, network) + self.when_i_update_extra_dhcp_options_on_a_vm( + vm1, dhcp_options_network) + self.when_i_stop_and_start_a_vm(vm1) + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, remove_dns_options=True) + + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + network_with_options=network, + is_shared_network=is_shared_network, + verify_on_vm=True, + default_network=isolated_network2, + vpc=None) + + vm1.remove_nic( + self.api_client, + [nic for nic in result.nic + if nic.networkid == network.id][0].id) + + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, remove_dns_options=True) + self.then_no_dhcp_options_present_on_vsd(dhcp_options_to_verify, + vm1) + self.delete_VM(vm1) + + def validate_vm_deploy_concurrency( + self, network, + vpc=None, + domain_name="testisolated.com", + is_shared_network=False): + + old_dhcp_options =\ + self.get_extra_dhcp_options_starting_with(1, network) + old_dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + 1, domain_name, True) + + new_dhcp_options =\ + self.get_extra_dhcp_options_starting_with(16, network) + new_dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + 16, domain_name, True) + + def deploy_update_validate_vm(number): + vm = self.when_i_create_a_vm( + [network], vpc, "vm-%02d" % number, + old_dhcp_options, + is_shared_network=is_shared_network) + + self.then_verify_dhcp_options_on_vsd_and_vm( + vm, network, old_dhcp_options_to_verify, + is_shared_network=is_shared_network, + verify_on_vm=True, + vpc=vpc, + verify_on_vsd=False) + + self.when_i_update_extra_dhcp_options_on_a_vm(vm, new_dhcp_options) + self.when_i_stop_and_start_a_vm(vm) + + self.then_verify_dhcp_options_on_vsd_and_vm( + vm, network, new_dhcp_options_to_verify, + is_shared_network=is_shared_network, + verify_on_vm=True, + vpc=vpc, + verify_on_vsd=False) + + self.delete_VM(vm) + + try: + executor = ThreadPoolExecutor(max_workers=10) + + vm_futures = [executor.submit( + deploy_update_validate_vm, i) + for i in range(10)] + + wait(vm_futures) + + [f.result() + for f in vm_futures] + + finally: + executor.shutdown(wait=True) + + def validate_all_extra_dhcp_for_network_actions_in_network( + self, network, + vpc=None, + domain_name="testisolated.com", + is_shared_network=False, + verify_all_options=False): + # 1 - for each extra dhcp option: + # a - deploy a vm with dhcp options + # b - restart the network + # c - check if the extra dhcp options are still correct + # d - restart the network with clean up = false + # e - check if the extra dhcp options are still correct + # f - if the network is a vpc, restart the vpc + # g - check if the extra dhcp options are still correct + # h - delete the vm + # i - create a vm + # j - stop the vm + # k - start the vm in a seperate thread + # l - add an extra nic while the vn is still in starting state + # m - delete the the vm + # 2 - deploy a vm + # 3 - wait for the network to go into allocated state + # 4 - deploy a new vm in the network + # 5 - check if all options are set correctly + # 6 - delete the network + + if verify_all_options: + options_to_verify = self.dhcp_options_map_keys + else: + options_to_verify = [1] + + for number in options_to_verify: + dhcp_options_network =\ + self.get_extra_dhcp_options_starting_with(number, network) + vm1 = self.when_i_create_a_vm( + network, vpc, "vm1", dhcp_options_network, + is_shared_network=is_shared_network) + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, is_shared_network) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, verify_on_vm=True, + vpc=vpc, remove_lease_file=False) + + network.restart(self.api_client, True) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, verify_on_vm=True, + vpc=vpc, remove_lease_file=False) + + network.restart(self.api_client, False) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, verify_on_vm=True, + vpc=vpc, remove_lease_file=False) + + if vpc: + self.restart_Vpc(vpc) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, verify_on_vm=True, + vpc=vpc, remove_lease_file=False) + + self.restart_Vpc(vpc, True) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, verify_on_vm=True, + vpc=vpc) + + self.delete_VM(vm1) + + dhcp_options_network = \ + self.get_extra_dhcp_options_starting_with(number, network) + + vm2 = self.when_i_create_a_vm( + network, vpc, "vm2", dhcp_options_network, + is_shared_network=is_shared_network) + + isolated_network2 =\ + self.create_isolated_network(gateway="10.0.1.1") + + dhcp_options_network =\ + self.get_extra_dhcp_options_starting_with( + number, None) + vm_nic = self.when_i_add_an_extra_nic_to_a_vm( + vm2, isolated_network2, dhcp_options=dhcp_options_network) + + dhcp_options_to_verify = \ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, remove_dns_options=True) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm2, isolated_network2, dhcp_options_to_verify, + is_shared_network=is_shared_network, verify_on_vm=False) + + if not is_shared_network: + self.then_verify_domain_name_and_router_options_multi_nic_set( + vm2, network, domain_name) + + vm2.update_default_nic( + self.api_client, + [nic + for nic in vm_nic.nic + if not nic.isdefault][0].id) + self.when_i_stop_and_start_a_vm(vm2) + + if not is_shared_network: + self.then_verify_domain_name_and_router_options_multi_nic_set( + vm2, isolated_network2, domain_name) + + self.then_verify_dhcp_options_on_vsd_and_vm( + vm2, isolated_network2, dhcp_options_to_verify, + is_shared_network=is_shared_network, verify_on_vm=True) + self.delete_VM(vm2) + + def validate_all_extra_dhcp_for_network_in_allocated( + self, network, + vpc=None, + domain_name="testisolated.com", + is_shared_network=False): + dhcp_options_network =\ + self.get_extra_dhcp_options_starting_with(1, network) + vm3 = self.when_i_create_a_vm( + network, vpc, "vm3", dhcp_options_network, + is_shared_network=is_shared_network) + vm3.stop(self.api_client) + # wait 1 min for network to go into allocated state + time.sleep(60) + dhcp_options_network =\ + self.get_extra_dhcp_options_starting_with(64, network) + vm4 = self.when_i_create_a_vm( + network, vpc, "vm1", dhcp_options_network, + is_shared_network=is_shared_network) + dhcp_options_to_verify = \ + self.get_extra_dhcp_options_to_verify_starting_with( + 64, domain_name, is_shared_network) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm4, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, verify_on_vm=True, vpc=vpc) + + self.delete_VM(vm4) + self.delete_VM(vm3) + self.delete_Network(network) + if vpc: + vpc.delete(self.api_client) + + def validate_all_extra_dhcp_for_vm_actions_in_network( + self, network, + vpc=None, + domain_name="testisolated.com", + is_shared_network=False, + verify_all_options=False): + # 1 - for each extra dhcp options: + # a - create a vm with dhcp options + # b - start and stop the vm + # c - check if the dhcp options are set correctly + # d - reboot the vm + # e - check if the dhcp options are set correctly + # f - delete a vm without expunging it + # g - recover the vm + # h - start the vm + # i - check if the dhcp options are set correctly + # j - delete the vm + # 2 - create a vm with extra dhcp options set + # 3 - check if the dhcp options are set correctly + # 4 - update the vm with new extra dhcp options + # 5 - reboot the vm + # 6 - verify the dhcp options on the vm and the vsd are not updated + # 7 - delete the vm + + if verify_all_options: + options_to_verify = self.dhcp_options_map_keys + else: + options_to_verify = [1] + + for number in options_to_verify: + dhcp_options_network =\ + self.get_extra_dhcp_options_starting_with(number, network) + vm1 = self.when_i_create_a_vm( + network, vpc, "vm1", dhcp_options_network, + is_shared_network=is_shared_network) + + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, is_shared_network) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, + verify_on_vm=True, + vpc=vpc) + + self.when_i_stop_and_start_a_vm(vm1) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, + verify_on_vm=True, + vpc=vpc) + + vm1.reboot(self.api_client) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, + verify_on_vm=True, + vpc=vpc) + + vm1.delete(self.api_client, False) + vm1.recover(self.api_client) + vm1.start(self.api_client) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, + verify_on_vm=True, + vpc=vpc) + + vm1.restore(self.api_client) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, + verify_on_vm=True, + vpc=vpc) + + if not self.isSimulator: + self.migrate_VM(vm1) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, verify_on_vm=True, + vpc=vpc) + + vm1.delete(self.api_client, True) + + dhcp_options_network = self.get_extra_dhcp_options_starting_with( + 1, network) + vm1 = self.when_i_create_a_vm( + network, vpc, "vm1", dhcp_options_network, + is_shared_network=is_shared_network) + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + 1, domain_name, is_shared_network) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, + verify_on_vm=True, + vpc=vpc) + + dhcp_options_network_not_present =\ + self.get_extra_dhcp_options_starting_with(93, network) + dhcp_options_to_verify_network_not_present =\ + self.get_extra_dhcp_options_to_verify_starting_with( + 93, domain_name, True) + self.when_i_update_extra_dhcp_options_on_a_vm( + vm1, dhcp_options_network_not_present) + vm1.reboot(self.api_client) + + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, + verify_on_vm=True, + vpc=vpc) + self.then_no_dhcp_options_present_on_vsd( + dhcp_options_to_verify_network_not_present, vm1) + + if not self.isSimulator: + self.migrate_VM(vm1) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, + verify_on_vm=True, + vpc=vpc) + + dhcp_options_network = self.get_extra_dhcp_options_starting_with( + 64, network) + dhcp_options_to_verify = \ + self.get_extra_dhcp_options_to_verify_starting_with( + 64, domain_name, is_shared_network) + self.when_i_update_extra_dhcp_options_on_a_vm( + vm1, dhcp_options_network) + vm1.restore(self.api_client) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, + verify_on_vm=True, + vpc=vpc) + + self.delete_VM(vm1) + + def validate_all_extra_dhcp_for_remove_nic_from_vm( + self, network, + vpc=None, + domain_name="testisolated.com", + is_shared_network=False, + verify_all_options=False): + # 1 - create an extra isolated network + # 2 - for each extra dhc option: + # a - deploy a vm + # b - plug nic + # c - verify the dhcp options are correctly set on the nic + # d - remove nic + # e - verify the dhcp options are no longer present on the vsd + # and vm + # 3 - try to remove the default nic which has extra dhcp options set + # (this should fail) + + isolated_network2 =\ + self.create_isolated_network(gateway="10.0.1.1") + + if verify_all_options: + options_to_verify = self.dhcp_options_map_keys + else: + options_to_verify = [1] + + for number in options_to_verify: + vm1 = self.when_i_create_a_vm( + isolated_network2, None, "vm1", dhcp_options=None, is_shared_network=False) + result = self.when_i_add_an_extra_nic_to_a_vm(vm1, network, None) + dhcp_options_network = self.get_extra_dhcp_options_starting_with( + number, network) + self.when_i_update_extra_dhcp_options_on_a_vm( + vm1, dhcp_options_network) + self.when_i_stop_and_start_a_vm(vm1) + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, True) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify=dhcp_options_to_verify, + network_with_options=network, + is_shared_network=is_shared_network, verify_on_vm=True, + default_network=isolated_network2, vpc=None) + vm1.remove_nic( + self.api_client, + [nic for nic in result.nic + if nic.networkid == network.id][0].id) + + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, True) + self.then_no_dhcp_options_present_on_vsd(dhcp_options_to_verify, + vm1) + self.delete_VM(vm1) + + # invalid remove option + vm1 = self.when_i_create_a_vm( + network, vpc, "vm1", None, is_shared_network=is_shared_network) + result = self.when_i_add_an_extra_nic_to_a_vm( + vm1, isolated_network2, None) + self.when_i_update_extra_dhcp_options_on_a_vm( + vm1, dhcp_options_network) + self.when_i_stop_and_start_a_vm(vm1) + with self.assertRaises(Exception): + vm1.remove_nic( + self.api_client, [nic for nic in result.nic + if nic.networkid == network.id][0]) + + def validate_all_extra_dhcp_for_update_multinic( + self, network, + vpc=None, + domain_name="testisolated.com", + is_shared_network=False, + verify_all_options=False): + # 1 - create an extra isolated network + # 2 - for each extra dhcp option: + # a - deploy a vm and ad an extra nic + # b - update the dhcp options on both nics + # c - verify the dhcp options are not yet set on the vsd and vm + # d - start and stop the vm + # e - verify the new dhcp options are set on the vsd and vm + # 3 - try to update a multi nic vm with invalid options + # (this should fail) + + isolated_network2 =\ + self.create_isolated_network(gateway="10.0.1.1") + + if verify_all_options: + options_to_verify = self.dhcp_options_map_keys + else: + options_to_verify = [1] + + for number in options_to_verify: + vm1 = self.when_i_create_a_vm( + isolated_network2, None, "vm1", + dhcp_options=None, + is_shared_network=False) + self.when_i_add_an_extra_nic_to_a_vm(vm1, network, None) + + dhcp_options_network = self.get_extra_dhcp_options_starting_with( + number, network) + dhcp_options_network2 = self.get_extra_dhcp_options_starting_with( + number, isolated_network2) + dhcp_options_list = [dhcp_options_network, dhcp_options_network2] + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, True) + self.when_i_update_extra_dhcp_options_on_a_vm(vm1, + dhcp_options_list) + self.then_no_dhcp_options_present_on_vsd(dhcp_options_to_verify, + vm1) + + self.when_i_stop_and_start_a_vm(vm1) + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, True) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify=dhcp_options_to_verify, + is_shared_network=is_shared_network, verify_on_vm=True, + default_network=isolated_network2, vpc=None) + self.delete_VM(vm1) + + invalid_dhcp_options_list = [{"networkid": network.id, + "dhcp:": + "http://www.testdhcpfeature.com/" + "adfsgbfgtdhh125ki-23-fdh-09"}, + {"networkid": network.id, + "dhcp:241": + "http://www.testdhcpfeature.com/" + "adfsgbfgtdhh125ki-23-fdh-09"}, + {"networkid": network.id, + "unknownvalue": + "http://www.testdhcpfeature.com/" + "adfsgbfgtdhh125ki-23-fdh-09"}, + {"networkid": "invalidnetworkid", + "dhcp:114": + "http://www.testdhcpfeature.com/" + "adfsgbfgtdhh125ki-23-fdh-09"}] + valid_dhcp_option = {"networkid": isolated_network2.id, + "dhcp:124": + "http://www.testdhcpfeature.com/" + "adfsgbfgtdhh125ki-23-fdh-09"} + for invalid_dhcp_option in invalid_dhcp_options_list: + vm1 = self.when_i_create_a_vm( + isolated_network2, vpc, "vm1", + dhcp_options=None, + is_shared_network=False) + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + 93, domain_name, True) + self.then_no_dhcp_options_present_on_vsd(dhcp_options_to_verify, + vm1) + combined_options = [invalid_dhcp_option, valid_dhcp_option] + with self.assertRaises(Exception): + self.when_i_update_extra_dhcp_options_on_a_vm( + vm1, combined_options) + self.delete_VM(vm1) + + def validate_all_extra_dhcp_for_multi_nic( + self, network, + vpc=None, + domain_name="testisolated.com", + is_shared_network=False, + verify_all_options=False): + # 1 - create an extra isolated network + # 2 - for each extra dhcp option: + # a - deploy a vm with a nic in two networks + # b - verify that the dhcp options are correctly set on the vsd + # and vn + # c - deploy a vm with a nic in two networks but now, let the other + # network be the default network of the vm + # d - verify that the dhcp options are correctly set on the vsd + # and vm + # 3 - try to deploy a multi nic vm with invalid dhcp options + # (should fail) + + isolated_network2 =\ + self.create_isolated_network(gateway="10.0.1.1") + + if verify_all_options: + options_to_verify = self.dhcp_options_map_keys + else: + options_to_verify = [1] + + for number in options_to_verify: + dhcp_options_network = self.get_extra_dhcp_options_starting_with( + number, network) + dhcp_options_network2 = self.get_extra_dhcp_options_starting_with( + number, isolated_network2) + dhcp_options = [dhcp_options_network, dhcp_options_network2] + + # default nic is the network provided + multinic_vm = self.when_i_create_a_vm( + [network, isolated_network2], vpc, "vm1", dhcp_options, + is_shared_network=is_shared_network) + + if not is_shared_network: + self.then_verify_domain_name_and_router_options_multi_nic_set( + multinic_vm, network, domain_name) + + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, is_shared_network) + self.then_verify_dhcp_options_on_vsd_and_vm( + multinic_vm, network, dhcp_options_to_verify, + network_with_options=network, + is_shared_network=is_shared_network, verify_on_vm=True, + vpc=vpc) + + # is not primary nic so no option 12 + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, remove_dns_options=True) + self.then_verify_dhcp_options_on_vsd_and_vm( + multinic_vm, network, dhcp_options_to_verify, + network_with_options=isolated_network2, + is_shared_network=is_shared_network, verify_on_vm=False, + default_network=network, vpc=vpc) + self.delete_VM(multinic_vm) + + # default nic is isolated_network2 + multinic_vm = self.when_i_create_a_vm( + [isolated_network2, network], vpc, "vm1", dhcp_options, + is_shared_network=is_shared_network) + + if not is_shared_network: + self.then_verify_domain_name_and_router_options_multi_nic_set( + multinic_vm, isolated_network2, domain_name) + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, is_shared_network) + self.then_verify_dhcp_options_on_vsd_and_vm( + multinic_vm, network, dhcp_options_to_verify, + network_with_options=isolated_network2, + is_shared_network=is_shared_network, verify_on_vm=False, + vpc=vpc) + + # is not primary nic so no option 12 + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, True) + self.then_verify_dhcp_options_on_vsd_and_vm( + multinic_vm, network, + dhcp_options_to_verify=dhcp_options_to_verify, + network_with_options=network, + is_shared_network=is_shared_network, verify_on_vm=True, + default_network=isolated_network2, vpc=None) + self.delete_VM(multinic_vm) + + invalid_dhcp_options_list = [{"networkid": network.id, + "dhcp:": + "http://www.testdhcpfeature.com" + "/adfsgbfgtdhh125ki-23-fdh-09"}, + {"networkid": network.id, + "dhcp:241": + "http://www.testdhcpfeature.com" + "/adfsgbfgtdhh125ki-23-fdh-09"}, + {"networkid": network.id, + "unknownvalue": + "http://www.testdhcpfeature.com" + "/adfsgbfgtdhh125ki-23-fdh-09"}, + {"networkid": "invalidnetworkid", + "dhcp:114": + "http://www.testdhcpfeature.com" + "/adfsgbfgtdhh125ki-23-fdh-09"}] + for invalid_dhcp_option in invalid_dhcp_options_list: + with self.assertRaises(Exception): + self.when_i_create_a_vm( + [isolated_network2, network], vpc, "vm1", + dhcp_options=invalid_dhcp_option, + is_shared_network=is_shared_network) + + def validate_all_extra_dhcp_after_plug_nic( + self, network, + vpc=None, + domain_name="testisolated.com", + is_shared_network=False, + verify_all_options=False): + # 1 - create an extra isolated network + # 2 - for each extra dchp options: + # a - deploy a vm in the created isolated network + # b - add an extra nic + # c - verify if the dhcp options are correctly set + # 3 - try to add a nic with invalid dhcp options (this should fail) + + isolated_network2 =\ + self.create_isolated_network(gateway="10.0.1.1") + if verify_all_options: + options_to_verify = self.dhcp_options_map_keys + else: + options_to_verify = [1] + + for number in options_to_verify: + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, True) + vm1 = self.when_i_create_a_vm( + isolated_network2, vpc, "vm1", None, + is_shared_network=False) + + self.then_no_dhcp_options_present_on_vsd( + dhcp_options_to_verify, vm1) + + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, True) + + dhcp_options = self.get_extra_dhcp_options_starting_with(number) + self.when_i_add_an_extra_nic_to_a_vm(vm1, network, + dhcp_options=dhcp_options) + vm1.reboot(self.api_client) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify=dhcp_options_to_verify, + network_with_options=network, + is_shared_network=is_shared_network, verify_on_vm=True, + default_network=isolated_network2, vpc=None) + self.delete_VM(vm1) + + invalid_dhcp_options_list = [ + {"dhcp:": "http://www.testdhcpfeature.com/" + "adfsgbfgtdhh125ki-23-fdh-09"}, + {"dhcp:241": "http://www.testdhcpfeature.com/" + "adfsgbfgtdhh125ki-23-fdh-09"}, + {"unknownvalue": "http://www.testdhcpfeature.com/" + "adfsgbfgtdhh125ki-23-fdh-09"}] + for invalid_dhcp_option in invalid_dhcp_options_list: + vm1 = self.when_i_create_a_vm( + isolated_network2, vpc, "vm1", None, + is_shared_network=False) + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + 93, domain_name, remove_dns_options=True) + self.then_no_dhcp_options_present_on_vsd(dhcp_options_to_verify, + vm1) + with self.assertRaises(Exception): + self.when_i_add_an_extra_nic_to_a_vm( + vm1, network, dhcp_options=invalid_dhcp_option) + self.delete_VM(vm1) + + def validate_all_extra_dhcp_after_vm_update( + self, network, + vpc=None, + domain_name="testisolated.com", + is_shared_network=False, + verify_all_options=False): + # 1 - deploy a vm without extra dhcp options + # 2 - verify no dhcp options are present + # 3 - For each extra dhcp options + # a - update the vm with extra dhcp options + # b - check that the vm options are yet not updated on the vsd + # and vm + # c - stop and start the vm + # d - check that the dhcp options are set on the vsd an vm + # 4 - update a vm zith invalid dhcp options (this should fail) + + # option 1 to 13 is special because we start a vm here + # instead of update + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with(1, domain_name, + True) + vm1 = self.when_i_create_a_vm( + network, vpc, "vm1", None, + is_shared_network=is_shared_network) + self.then_no_dhcp_options_present_on_vsd(dhcp_options_to_verify, vm1) + + if verify_all_options: + options_to_verify = self.dhcp_options_map_keys + else: + options_to_verify = [1] + + for number in options_to_verify: + dhcp_options = self.get_extra_dhcp_options_starting_with(number, + network) + self.when_i_update_extra_dhcp_options_on_a_vm(vm1, dhcp_options) + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, True) + self.then_no_dhcp_options_present_on_vsd(dhcp_options_to_verify, + vm1) + + # dhcp options get applied after start stop vm + self.when_i_stop_and_start_a_vm(vm1) + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, is_shared_network) + + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, verify_on_vm=True, + vpc=vpc) + + invalid_dhcp_options_list = [{"networkid": network.id, + "dhcp:": "http://www.testdhcpfeature.com" + "/adfsgbfgtdhh125ki-23-fdh-09"}, + {"networkid": network.id, + "dhcp:241": "http://www.testdhcpfeature" + ".com/adfsgbfgtdhh125ki-23" + "-fdh-09"}, + {"networkid": network.id, + "unknownvalue": "http://www.testdhcp" + "feature.com/" + "adfsgbfgtdhh125ki-23-" + "fdh-09"}, + {"networkid": "invalidnetworkid", + "dhcp:114": "http://www.testdhcpfeature" + ".com/adfsgbfgtdhh125ki-23-" + "fdh-09"}] + for invalid_dhcp_option in invalid_dhcp_options_list: + with self.assertRaises(Exception): + self.when_i_update_extra_dhcp_options_on_a_vm( + vm1, invalid_dhcp_option) + + def validate_all_extra_dhcp_deploy_vm( + self, network, + vpc=None, + domain_name="testisolated.com", + is_shared_network=False, + verify_all_options=False): + # 1 - For each extra dhcp option: + # a - deploy a vm with extra dhcp options + # b - verify if the options are present on the vsd and vm + # c - delete the VM + # 2 - create a vm with different invalid dhcp options + # (this should fail) + + if verify_all_options: + options_to_verify = self.dhcp_options_map_keys + else: + options_to_verify = [1] + + for number in options_to_verify: + dhcp_options = self.get_extra_dhcp_options_starting_with( + number, network) + + dhcp_options_to_verify =\ + self.get_extra_dhcp_options_to_verify_starting_with( + number, domain_name, is_shared_network) + vm1 = self.when_i_create_a_vm( + network, vpc, "vm1", dhcp_options, + is_shared_network=is_shared_network) + + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + is_shared_network=is_shared_network, + verify_on_vm=True, + vpc=vpc) + self.delete_VM(vm1) + + invalid_dhcp_options_list = [{"networkid": network.id, + "dhcp:": "http://www.testdhcpfeature.com" + "/adfsgbfgtdhh125ki-23-fdh" + "-09"}, + {"networkid": network.id, + "dhcp:241": + "http://www.testdhcpfeature.com" + "/adfsgbfgtdhh125ki-23-fdh-09"}, + {"networkid": network.id, + "unknownvalue": + "http://www.testdhcpfeature" + ".com/adfsgbfgtdhh125ki-23-fdh-09"}, + {"networkid": "invalidnetworkid", + "dhcp:114": + "http://www.testdhcpfeature.com" + "/adfsgbfgtdhh125ki-23-fdh-09"}] + for invalid_dhcp_option in invalid_dhcp_options_list: + with self.assertRaises(Exception): + self.when_i_create_a_vm( + network, vpc, "vm2", invalid_dhcp_option, + is_shared_network=is_shared_network) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_01_nuage_extra_dhcp_single_nic_in_isolated_network(self): + self.update_zone_details("testisolated.com") + self.validate_isolated_network( + self.isolated_network_offering, self.isolated_network) + self.validate_all_extra_dhcp_deploy_vm(self.isolated_network, + verify_all_options=True) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_02_nuage_extra_dhcp_single_nic_in_vpc(self): + self.update_zone_details("testvpc.com") + self.validate_vpc(self.vpc1, self.vpc_offering) + self.validate_Network(self.vpc_network) + + self.validate_all_extra_dhcp_deploy_vm( + self.vpc_network, + self.vpc1, + domain_name="testvpc.com") + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_03_nuage_extra_dhcp_single_nic_in_shared_network(self): + self.update_zone_details("testshared.com") + self.validate_all_extra_dhcp_deploy_vm( + self.shared_network_all, + domain_name="testshared.com", + is_shared_network=True) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_04_nuage_extra_dhcp_update_vm_in_isoltated_network(self): + self.update_zone_details("testisolated.com") + self.validate_isolated_network( + self.isolated_network_offering, self.isolated_network) + self.validate_all_extra_dhcp_after_vm_update( + self.isolated_network) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_05_nuage_extra_dhcp_update_vm_in_vpc(self): + self.update_zone_details("testvpc.com") + self.validate_vpc(self.vpc1, self.vpc_offering) + self.validate_Network(self.vpc_network) + + self.validate_all_extra_dhcp_after_vm_update( + self.vpc_network, self.vpc1, + domain_name="testvpc.com") + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_06_nuage_extra_dhcp_update_vm_in_shared_network(self): + self.update_zone_details("testshared.com") + self.validate_all_extra_dhcp_after_vm_update( + self.shared_network_all, + domain_name="testshared.com", + is_shared_network=True) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_07_nuage_extra_dhcp_add_nic_in_isolated_network(self): + self.update_zone_details("testisolated.com") + self.validate_isolated_network( + self.isolated_network_offering, self.isolated_network) + self.validate_all_extra_dhcp_after_plug_nic( + self.isolated_network) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_08_nuage_extra_dhcp_add_nic_in_vpc(self): + self.update_zone_details("testvpc.com") + self.validate_vpc(self.vpc1, self.vpc_offering) + self.validate_Network(self.vpc_network) + + self.validate_all_extra_dhcp_after_plug_nic( + self.vpc_network, self.vpc1, + domain_name="testvpc.com") + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_09_nuage_extra_dhcp_add_nic_in_shared_network(self): + self.update_zone_details("testshared.com") + self.validate_all_extra_dhcp_after_plug_nic( + self.shared_network_all, + domain_name="testshared.com", + is_shared_network=True) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_10_nuage_extra_dhcp_deploy_multi_nic_vm_in_isolated_network(self): + self.update_zone_details("testisolated.com") + self.validate_isolated_network( + self.isolated_network_offering, self.isolated_network) + self.validate_all_extra_dhcp_for_multi_nic( + self.isolated_network) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_11_nuage_extra_dhcp_deploy_multi_nic_vm_in_vpc(self): + self.update_zone_details("testvpc.com") + self.validate_vpc(self.vpc1, self.vpc_offering) + self.validate_Network(self.vpc_network) + + self.validate_all_extra_dhcp_for_multi_nic( + self.vpc_network, self.vpc1, + domain_name="testvpc.com") + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_12_nuage_extra_dhcp_deploy_multi_nic_vm_in_shared_network(self): + self.update_zone_details("testshared.com") + self.validate_all_extra_dhcp_for_multi_nic( + self.shared_network_all, + domain_name="testshared.com", + is_shared_network=True) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_13_nuage_extra_dhcp_update_multi_nic_in_isolated_network(self): + self.update_zone_details("testisolated.com") + self.validate_isolated_network( + self.isolated_network_offering, self.isolated_network) + self.validate_all_extra_dhcp_for_update_multinic( + self.isolated_network) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_14_nuage_extra_dhcp_update_multi_nic_in_vpc(self): + self.update_zone_details("testvpc.com") + self.validate_vpc(self.vpc1, self.vpc_offering) + self.validate_Network(self.vpc_network) + + self.validate_all_extra_dhcp_for_update_multinic( + self.vpc_network, self.vpc1, + domain_name="testvpc.com") + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_15_nuage_extra_dhcp_update_multi_nic_in_shared_network(self): + self.update_zone_details("testshared.com") + self.validate_all_extra_dhcp_for_update_multinic( + self.shared_network_all, + domain_name="testshared.com", + is_shared_network=True) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_16_nuage_extra_dhcp_remove_nic_in_isolated_network(self): + self.update_zone_details("testisolated.com") + self.validate_isolated_network( + self.isolated_network_offering, self.isolated_network) + self.validate_all_extra_dhcp_for_remove_nic_from_vm( + self.isolated_network) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_17_nuage_extra_dhcp_remove_nic_in_vpc(self): + self.update_zone_details("testvpc.com") + self.validate_vpc(self.vpc1, self.vpc_offering) + self.validate_Network(self.vpc_network) + + self.validate_all_extra_dhcp_for_remove_nic_from_vm( + network=self.vpc_network, + vpc=self.vpc1, + domain_name="testvpc.com") + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_18_nuage_extra_dhcp_remove_nic_in_shared_network(self): + self.update_zone_details("testshared.com") + self.validate_all_extra_dhcp_for_remove_nic_from_vm( + self.shared_network_all, + domain_name="testshared.com", + is_shared_network=True) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_19_nuage_extra_dhcp_vm_actions_in_isolated_network(self): + self.update_zone_details("testisolated.com") + self.validate_isolated_network( + self.isolated_network_offering, self.isolated_network) + self.validate_all_extra_dhcp_for_vm_actions_in_network( + self.isolated_network) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_20_nuage_nuage_extra_dhcp_vm_actions_in_vpc(self): + self.update_zone_details("testvpc.com") + self.validate_vpc(self.vpc1, self.vpc_offering) + self.validate_Network(self.vpc_network) + + self.validate_all_extra_dhcp_for_vm_actions_in_network( + network=self.vpc_network, + vpc=self.vpc1, + domain_name="testvpc.com") + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_21_nuage_extra_dhcp_vm_actions_in_shared_network(self): + self.update_zone_details("testshared.com") + self.validate_all_extra_dhcp_for_vm_actions_in_network( + self.shared_network_all, + domain_name="testshared.com", + is_shared_network=True) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_22_nuage_extra_dhcp_network_actions_in_isolated_network(self): + self.update_zone_details("testisolated.com") + self.validate_isolated_network( + self.isolated_network_offering, self.isolated_network) + self.validate_all_extra_dhcp_for_network_actions_in_network( + self.isolated_network) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_23_nuage_nuage_extra_dhcp_network_actions_in_vpc(self): + self.update_zone_details("testvpc.com") + self.validate_vpc(self.vpc1, self.vpc_offering) + self.validate_Network(self.vpc_network) + + self.validate_all_extra_dhcp_for_network_actions_in_network( + self.vpc_network, + vpc=self.vpc1, + domain_name="testvpc.com") + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_24_nuage_extra_dhcp_network_actions_in_shared_network(self): + self.update_zone_details("testshared.com") + self.validate_all_extra_dhcp_for_network_actions_in_network( + self.shared_network_all, + domain_name="testshared.com", + is_shared_network=True) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_25_nuage_extra_dhcp_nic_after_migrate_in_isolated_network(self): + self.update_zone_details("testisolated.com") + self.validate_isolated_network( + self.isolated_network_offering, self.isolated_network) + self.validate_all_extra_dhcp_for_add_remove_nic_after_migrate( + self.isolated_network) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_26_nuage_nuage_extra_dhcp_nic_after_migrate_in_vpc(self): + self.update_zone_details("testvpc.com") + self.validate_vpc(self.vpc1, self.vpc_offering) + self.validate_Network(self.vpc_network) + + self.validate_all_extra_dhcp_for_add_remove_nic_after_migrate( + self.vpc_network, + domain_name="testvpc.com") + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_27_nuage_extra_dhcp_nic_after_migrate_in_shared_network(self): + self.update_zone_details("testshared.com") + self.validate_all_extra_dhcp_for_add_remove_nic_after_migrate( + self.shared_network_all, + domain_name="testshared.com", + is_shared_network=True) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_28_nuage_extra_dhcp_deploy_multiple_vms(self): + self.update_zone_details("testisolated.com") + isolated_network =\ + self.create_isolated_network(gateway="10.0.0.1") + self.validate_vm_deploy_concurrency( + isolated_network) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_29_nuage_extra_dhcp_allocated_isolated_network(self): + self.update_zone_details("testisolated.com") + self.validate_isolated_network( + self.isolated_network_offering, self.isolated_network,) + self.validate_all_extra_dhcp_for_network_in_allocated( + self.isolated_network) + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_30_nuage_extra_dhcp_allocated_vpc(self): + self.update_zone_details("testvpc.com") + self.validate_vpc(self.vpc1, self.vpc_offering) + self.validate_Network(self.vpc_network) + + self.validate_all_extra_dhcp_for_network_in_allocated( + self.vpc_network, self.vpc1, + domain_name="testvpc.com") + + @attr(tags=["advanced", "nuagevsp"], required_hardware="false") + def test_31_nuage_extra_dhcp_allocated_shared_network(self): + self.update_zone_details("testshared.com") + self.validate_all_extra_dhcp_for_network_in_allocated( + self.shared_network_all, + domain_name="testshared.com", + is_shared_network=True) + + @attr(tags=["advanced", "nuagevsp", "smoke"], required_hardware="false") + def smoke_test(self): + # This test does basic sanity checks to see if basic + # DHCP options still work. + # 1 - deploy vm in an isolated network + # 2 - verify dhcp options + # 3 - update dhcp options + # 4 - add nic to a vpc_network with different dhcp options + # 5 - restart the vm + # 6 - check if dhcp options are on the extra nic and the default nic + # 7 - restart the network + # 8 - verify if the dhcp options are set correctly + # 9 - remove the vm + + network = self.isolated_network + domain_name = "testisolated.com" + self.update_zone_details(domain_name) + + dhcp_options_isolated_network =\ + self.get_extra_dhcp_options_starting_with(1, network) + + dhcp_options_to_verify = \ + self.get_extra_dhcp_options_to_verify_starting_with( + 1, domain_name, remove_dns_options=False) + vm1 = self.when_i_create_a_vm( + network, + vpc=None, + vm_name="vm1", + dhcp_options=dhcp_options_isolated_network) + + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify, + verify_on_vm=True) + + dhcp_options_isolated_network =\ + self.get_extra_dhcp_options_starting_with(16, network) + self.when_i_update_extra_dhcp_options_on_a_vm( + vm1, dhcp_options_isolated_network) + + dhcp_options_vpc_network = self.get_extra_dhcp_options_starting_with( + 28, None) + self.when_i_add_an_extra_nic_to_a_vm( + vm1, self.vpc_network, dhcp_options_vpc_network) + + self.when_i_stop_and_start_a_vm(vm1) + + dhcp_options_to_verify_on_default_nic = \ + self.get_extra_dhcp_options_to_verify_starting_with( + 16, domain_name, False) + + dhcp_options_to_verify_on_second_nic = \ + self.get_extra_dhcp_options_to_verify_starting_with( + 28, domain_name, remove_dns_options=True) + + # dhcp options get applied after start stop vm + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify_on_default_nic, + verify_on_vm=True) + + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify_on_second_nic, + network_with_options=self.vpc_network, + verify_on_vm=True, + default_network=network, + vpc=None) + + network.restart(self.api_client, True) + self.vpc_network.restart(self.api_client, True) + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify_on_default_nic, + verify_on_vm=True) + + self.then_verify_dhcp_options_on_vsd_and_vm( + vm1, network, dhcp_options_to_verify_on_second_nic, + network_with_options=self.vpc_network, + verify_on_vm=True, + default_network=network, + vpc=None) + + self.delete_VM(vm1) diff --git a/tools/marvin/marvin/config/test_data.py b/tools/marvin/marvin/config/test_data.py index 13cd063bfa1..7d8b6266766 100644 --- a/tools/marvin/marvin/config/test_data.py +++ b/tools/marvin/marvin/config/test_data.py @@ -1952,6 +1952,42 @@ test_data = { "Dns": "VpcVirtualRouter" } }, + # Services supported by the Nuage VSP plugin for VPC without userdata + "vpc_network_offering_nuage_dhcp": { + "name": 'nuage_vpc_marvin', + "displaytext": 'nuage_vpc_marvin', + "guestiptype": 'Isolated', + "supportedservices": 'Dhcp,StaticNat,SourceNat,NetworkACL,Connectivity,Dns', + "traffictype": 'GUEST', + "availability": 'Optional', + "useVpc": 'on', + "ispersistent": 'True', + "serviceProviderList": { + "Dhcp": "NuageVsp", + "StaticNat": "NuageVsp", + "SourceNat": "NuageVsp", + "NetworkACL": "NuageVsp", + "Connectivity": "NuageVsp", + "Dns": "VpcVirtualRouter", + }, + "serviceCapabilityList": { + "SourceNat": {"SupportedSourceNatTypes": "perzone"} + } + }, + # Services supported by the Nuage VSP plugin for VPCs + "vpc_offering_nuage_dhcp": { + "name": 'Nuage VSP VPC offering', + "displaytext": 'Nuage VSP VPC offering', + "supportedservices": 'Dhcp,StaticNat,SourceNat,NetworkACL,Connectivity,Dns', + "serviceProviderList": { + "Dhcp": "NuageVsp", + "StaticNat": "NuageVsp", + "SourceNat": "NuageVsp", + "NetworkACL": "NuageVsp", + "Connectivity": "NuageVsp", + "Dns": "VpcVirtualRouter", + } + }, "shared_nuage_network_offering": { "name": 'nuage_marvin', "displaytext": 'nuage_marvin', @@ -2048,3 +2084,4 @@ test_data = { } } } + diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 4bac4e0cd46..340419a65d2 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -539,6 +539,9 @@ class VirtualMachine: if "userdata" in services: cmd.userdata = base64.urlsafe_b64encode(services["userdata"]) + if "dhcpoptionsnetworklist" in services: + cmd.dhcpoptionsnetworklist = services["dhcpoptionsnetworklist"] + cmd.details = [{}] if customcpunumber: @@ -780,7 +783,7 @@ class VirtualMachine: cmd.id = volume.id return apiclient.detachVolume(cmd) - def add_nic(self, apiclient, networkId, ipaddress=None, macaddress=None): + def add_nic(self, apiclient, networkId, ipaddress=None, macaddress=None, dhcpoptions=None): """Add a NIC to a VM""" cmd = addNicToVirtualMachine.addNicToVirtualMachineCmd() cmd.virtualmachineid = self.id @@ -788,6 +791,8 @@ class VirtualMachine: if ipaddress: cmd.ipaddress = ipaddress + if dhcpoptions: + cmd.dhcpoptions = dhcpoptions if macaddress: cmd.macaddress = macaddress diff --git a/utils/src/main/java/com/cloud/utils/net/Dhcp.java b/utils/src/main/java/com/cloud/utils/net/Dhcp.java new file mode 100644 index 00000000000..33a7db67fa1 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/net/Dhcp.java @@ -0,0 +1,125 @@ +// +// 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.utils.net; + +import java.util.Arrays; + +public class Dhcp { + public enum DhcpOptionCode { + NETMASK(1, "netmask"), + TIME_OFFSET(2, "time-offset"), + ROUTER(3, "router"), + TIME_SERVER(4, "time-server"), + DNS_SERVER(6, "dns-server"), + LOG_SERVER(7, "log-server"), + LPR_SERVER(9, "lpr-server"), + HOSTNAME(12, "hostname"), + BOOT_FILE_SIZE(13, "boot-file-size"), + DOMAIN_NAME(15, "domain-name"), + SWAP_SERVER(16, "swap-server"), + ROOT_PATH(17, "root-path"), + EXTENSION_PATH(18, "extension-path"), + IP_FORWARD_ENABLE(19, "ip-forward-enable"), + NON_LOCAL_SOURCE_ROUTING(20, "non-local-source-routing"), + POLICY_FILTER(21, "policy-filter"), + MAX_DATAGRAM_REASSEMBLY(22, "max-datagram-reassembly"), + DEFAULT_TTL(23, "default-ttl"), + MTU(26, "mtu"), + ALL_SUBNETS_LOCAL(27, "all-subnets-local"), + BROADCAST(28, "broadcast"), + ROUTER_DISCOVERY(31, "router-discovery"), + ROUTER_SOLICITATION(32, "router-solicitation"), + STATIC_ROUTE(33, "static-route"), + TRAILER_ENCAPSULATION(34, "trailer-encapsulation"), + ARP_TIMEOUT(35, "arp-timeout"), + ETHERNET_ENCAP(36, "ethernet-encap"), + TCP_TTL(37, "tcp-ttl"), + TCP_KEEPALIVE(38, "tcp-keepalive"), + NIS_DOMAIN(40, "nis-domain"), + NIS_SERVER(41, "nis-server"), + NTP_SERVER(42, "ntp-server"), + VENDOR_ENCAP(43, "vendor-encap"), + NETBIOS_NS(44, "netbios-ns"), + NETBIOS_DD(45, "netbios-dd"), + NETBIOS_NODETYPE(46, "netbios-nodetype"), + NETBIOS_SCOPE(47, "netbios-scope"), + X_WINDOWS_FS(48, "x-windows-fs"), + X_WINDOWS_DM(49, "x-windows-dm"), + REQUESTED_ADDRESS(50, "requested-address"), + LEASE_TIME(51, "lease-time"), + OPTION_OVERLOAD(52, "option-overload"), + MESSAGE_TYPE(53, "message-type"), + SERVER_IDENTIFIER(54, "server-identifier"), + PARAMETER_REQUEST(55, "parameter-request"), + MESSAGE(56, "message"), + MAX_MESSAGE_SIZE(57, "max-message-size"), + T1(58, "T1"), + T2(59, "T2"), + VENDOR_CLASS(60, "vendor-class"), + CLIENT_ID(61, "client-id"), + NISPLUS_DOMAIN(64, "nis+-domain"), + NISPLUS_SERVER(65, "nis+-server"), + TFTP_SERVER(66, "tftp-server"), + BOOTFILE_NAME(67, "bootfile-name"), + MOBILE_IP_HOME(68, "mobile-ip-home"), + SMTP_SERVER(69, "smtp-server"), + POP3_SERVER(70, "pop3-server"), + NNTP_SERVER(71, "nntp-server"), + IRC_SERVER(74, "irc-server"), + USER_CLASS(77, "user-class"), + CLIENT_ARCH(93, "client-arch"), + CLIENT_INTERFACE_ID(94, "client-interface-id"), + CLIENT_MACHINE_ID(97, "client-machine-id"), + URL(114, "url"), + DOMAIN_SEARCH(119, "domain-search"), + SIP_SERVER(120, "sip-server"), + CLASSLESS_STATIC_ROUTE(121, "classless-static-route"), + VENDOR_ID_ENCAP(125, "vendor-id-encap"), + SERVER_IP_ADDRESS(255, "server-ip-address"); + + private int code; + private String name; + + DhcpOptionCode(int code, String name){ + this.code = code; + this.name = name; + } + + public int getCode() { + return code; + } + + public String getName() { return name; } + + public static DhcpOptionCode valueOfInt(int code) { + return Arrays.stream(DhcpOptionCode.values()) + .filter(option -> option.getCode() == code) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Dhcp option code " + code + " not supported.")); + } + + public static DhcpOptionCode valueOfString(String name) { + return Arrays.stream(DhcpOptionCode.values()) + .filter(option -> option.getName().equals(name)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Dhcp option " + name + " not supported.")); + } + } +}