From 482e7ebf9a2fed69ea7cb75dfd0fc2c5149aade5 Mon Sep 17 00:00:00 2001 From: Rakesh Date: Tue, 24 Dec 2019 10:08:53 +0100 Subject: [PATCH] New feature: Acquire specific public IP for network (#3775) Currently in cloudstack, when we click on "Acquire New Ip", it will randomly acquire IP from the pool. With this enhancement, it is possible to select the IP from the drop down IP list of that network. Same thing applies for a VPC as well. --- .../com/cloud/network/NetworkService.java | 2 +- .../user/address/AssociateIPAddrCmd.java | 9 +- .../com/cloud/network/IpAddressManager.java | 2 +- .../network/element/CiscoVnmcElement.java | 2 +- .../cloud/network/IpAddressManagerImpl.java | 6 +- .../com/cloud/network/NetworkServiceImpl.java | 6 +- .../com/cloud/vpc/MockNetworkManagerImpl.java | 2 +- .../test_acquire_specified_public_ip.py | 486 ++++++++++++++++++ tools/marvin/marvin/lib/base.py | 11 +- ui/scripts/network.js | 49 ++ 10 files changed, 562 insertions(+), 13 deletions(-) create mode 100644 test/integration/component/test_acquire_specified_public_ip.py diff --git a/api/src/main/java/com/cloud/network/NetworkService.java b/api/src/main/java/com/cloud/network/NetworkService.java index b228c59e52f..04f240b5e13 100644 --- a/api/src/main/java/com/cloud/network/NetworkService.java +++ b/api/src/main/java/com/cloud/network/NetworkService.java @@ -56,7 +56,7 @@ public interface NetworkService { List getIsolatedNetworksOwnedByAccountInZone(long zoneId, Account owner); - IpAddress allocateIP(Account ipOwner, long zoneId, Long networkId, Boolean displayIp) throws ResourceAllocationException, InsufficientAddressCapacityException, + IpAddress allocateIP(Account ipOwner, long zoneId, Long networkId, Boolean displayIp, String ipaddress) throws ResourceAllocationException, InsufficientAddressCapacityException, ConcurrentOperationException; boolean releaseIpAddress(long ipAddressId) throws InsufficientAddressCapacityException; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/address/AssociateIPAddrCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/address/AssociateIPAddrCmd.java index 5b662b78237..5beba26d0f5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/address/AssociateIPAddrCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/address/AssociateIPAddrCmd.java @@ -125,6 +125,9 @@ public class AssociateIPAddrCmd extends BaseAsyncCreateCmd implements UserCmd { authorized = {RoleType.Admin}) private Boolean display; + @Parameter(name=ApiConstants.IP_ADDRESS, type=CommandType.STRING, description="IP Address to be associated") + private String ipAddress; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -178,6 +181,10 @@ public class AssociateIPAddrCmd extends BaseAsyncCreateCmd implements UserCmd { return regionId; } + public String getIpAddress() { + return ipAddress; + } + public Long getNetworkId() { if (vpcId != null) { return null; @@ -306,7 +313,7 @@ public class AssociateIPAddrCmd extends BaseAsyncCreateCmd implements UserCmd { IpAddress ip = null; if (!isPortable()) { - ip = _networkService.allocateIP(_accountService.getAccount(getEntityOwnerId()), getZoneId(), getNetworkId(), getDisplayIp()); + ip = _networkService.allocateIP(_accountService.getAccount(getEntityOwnerId()), getZoneId(), getNetworkId(), getDisplayIp(), ipAddress); } else { ip = _networkService.allocatePortableIP(_accountService.getAccount(getEntityOwnerId()), 1, getZoneId(), getNetworkId(), getVpcId()); } diff --git a/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java b/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java index a5512786bcd..229248ac3f2 100644 --- a/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java +++ b/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java @@ -169,7 +169,7 @@ public interface IpAddressManager { PublicIp assignDedicateIpAddress(Account owner, Long guestNtwkId, Long vpcId, long dcId, boolean isSourceNat) throws ConcurrentOperationException, InsufficientAddressCapacityException; - IpAddress allocateIp(Account ipOwner, boolean isSystem, Account caller, long callerId, DataCenter zone, Boolean displayIp) + IpAddress allocateIp(Account ipOwner, boolean isSystem, Account caller, long callerId, DataCenter zone, Boolean displayIp, String ipaddress) throws ConcurrentOperationException, ResourceAllocationException, InsufficientAddressCapacityException; PublicIp assignPublicIpAddressFromVlans(long dcId, Long podId, Account owner, VlanType type, List vlanDbIds, Long networkId, String requestedIp, boolean isSystem) diff --git a/plugins/network-elements/cisco-vnmc/src/main/java/com/cloud/network/element/CiscoVnmcElement.java b/plugins/network-elements/cisco-vnmc/src/main/java/com/cloud/network/element/CiscoVnmcElement.java index 23c9c29b4b3..ed650022d0c 100644 --- a/plugins/network-elements/cisco-vnmc/src/main/java/com/cloud/network/element/CiscoVnmcElement.java +++ b/plugins/network-elements/cisco-vnmc/src/main/java/com/cloud/network/element/CiscoVnmcElement.java @@ -353,7 +353,7 @@ public class CiscoVnmcElement extends AdapterBase implements SourceNatServicePro try { Account caller = CallContext.current().getCallingAccount(); long callerUserId = CallContext.current().getCallingUserId(); - outsideIp = _ipAddrMgr.allocateIp(owner, false, caller, callerUserId, zone, true); + outsideIp = _ipAddrMgr.allocateIp(owner, false, caller, callerUserId, zone, true, null); } catch (ResourceAllocationException e) { s_logger.error("Unable to allocate additional public Ip address. Exception details " + e); throw new CloudRuntimeException("Unable to allocate additional public Ip address. Exception details " + e); diff --git a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java index 817efcccb5e..55981e80954 100644 --- a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java +++ b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java @@ -444,7 +444,7 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage DataCenter zone = _entityMgr.findById(DataCenter.class, zoneId); - return allocateIp(ipOwner, isSystem, caller, callerUserId, zone, null); + return allocateIp(ipOwner, isSystem, caller, callerUserId, zone, null, null); } // An IP association is required in below cases @@ -1132,7 +1132,7 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage @DB @Override - public IpAddress allocateIp(final Account ipOwner, final boolean isSystem, Account caller, long callerUserId, final DataCenter zone, final Boolean displayIp) + public IpAddress allocateIp(final Account ipOwner, final boolean isSystem, Account caller, long callerUserId, final DataCenter zone, final Boolean displayIp, final String ipaddress) throws ConcurrentOperationException, ResourceAllocationException, InsufficientAddressCapacityException { @@ -1166,7 +1166,7 @@ public class IpAddressManagerImpl extends ManagerBase implements IpAddressManage ip = Transaction.execute(new TransactionCallbackWithException() { @Override public PublicIp doInTransaction(TransactionStatus status) throws InsufficientAddressCapacityException { - PublicIp ip = fetchNewPublicIp(zone.getId(), null, null, ipOwner, vlanType, null, false, assign, null, isSystem, null, displayIp, false); + PublicIp ip = fetchNewPublicIp(zone.getId(), null, null, ipOwner, vlanType, null, false, assign, ipaddress, isSystem, null, displayIp, false); if (ip == null) { InsufficientAddressCapacityException ex = new InsufficientAddressCapacityException("Unable to find available public IP addresses", DataCenter.class, zone diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index f440ced13b2..1e9eb2d325f 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -520,7 +520,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService { @Override @ActionEvent(eventType = EventTypes.EVENT_NET_IP_ASSIGN, eventDescription = "allocating Ip", create = true) - public IpAddress allocateIP(Account ipOwner, long zoneId, Long networkId, Boolean displayIp) + public IpAddress allocateIP(Account ipOwner, long zoneId, Long networkId, Boolean displayIp, String ipaddress) throws ResourceAllocationException, InsufficientAddressCapacityException, ConcurrentOperationException { Account caller = CallContext.current().getCallingAccount(); @@ -544,7 +544,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService { if (s_logger.isDebugEnabled()) { s_logger.debug("Associate IP address called by the user " + callerUserId + " account " + ipOwner.getId()); } - return _ipAddrMgr.allocateIp(ipOwner, false, caller, callerUserId, zone, displayIp); + return _ipAddrMgr.allocateIp(ipOwner, false, caller, callerUserId, zone, displayIp, ipaddress); } else { throw new InvalidParameterValueException("Associate IP address can only be called on the shared networks in the advanced zone" + " with Firewall/Source Nat/Static Nat/Port Forwarding/Load balancing services enabled"); @@ -555,7 +555,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService { _accountMgr.checkAccess(caller, null, false, ipOwner); } - return _ipAddrMgr.allocateIp(ipOwner, false, caller, callerUserId, zone, displayIp); + return _ipAddrMgr.allocateIp(ipOwner, false, caller, callerUserId, zone, displayIp, ipaddress); } @Override diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java index e99c2a864cb..09fd997f618 100644 --- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java @@ -160,7 +160,7 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches * @see com.cloud.network.NetworkService#allocateIP(com.cloud.user.Account, long, java.lang.Long) */ @Override - public IpAddress allocateIP(Account ipOwner, long zoneId, Long networkId, Boolean displayIp) throws ResourceAllocationException, InsufficientAddressCapacityException, + public IpAddress allocateIP(Account ipOwner, long zoneId, Long networkId, Boolean displayIp, String ipaddress) throws ResourceAllocationException, InsufficientAddressCapacityException, ConcurrentOperationException { // TODO Auto-generated method stub return null; diff --git a/test/integration/component/test_acquire_specified_public_ip.py b/test/integration/component/test_acquire_specified_public_ip.py new file mode 100644 index 00000000000..01a4470f161 --- /dev/null +++ b/test/integration/component/test_acquire_specified_public_ip.py @@ -0,0 +1,486 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +""" +Tests of acquiring a specified public IP for isolated network or vpc +""" + +# Import Local Modules +from nose.plugins.attrib import attr +from marvin.cloudstackTestCase import cloudstackTestCase, unittest +from marvin.cloudstackAPI import createVlanIpRange +from marvin.lib.utils import (validateList, + cleanup_resources) +from marvin.lib.base import (Account, + Domain, + Configurations, + VirtualMachine, + ServiceOffering, + VpcOffering, + Zone, + Network, + VPC, + PublicIPAddress, + PublicIpRange) +from marvin.lib.common import (get_domain, + get_zone, + get_free_vlan, + get_template) +import logging +import random + +class TestAcquireSpecifiedPublicIp(cloudstackTestCase): + @classmethod + def setUpClass(cls): + cls.testClient = super( + TestAcquireSpecifiedPublicIp, + cls).getClsTestClient() + cls.apiclient = cls.testClient.getApiClient() + cls.services = cls.testClient.getParsedTestDataConfig() + + zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.zone = Zone(zone.__dict__) + cls.template = get_template(cls.apiclient, cls.zone.id) + cls._cleanup = [] + + if str(cls.zone.securitygroupsenabled) == "True": + sys.exit(1) + + cls.logger = logging.getLogger("TestAcquireSpecifiedPublicIp") + cls.stream_handler = logging.StreamHandler() + cls.logger.setLevel(logging.DEBUG) + cls.logger.addHandler(cls.stream_handler) + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + + # Create new domain1 + cls.domain1 = Domain.create( + cls.apiclient, + services=cls.services["acl"]["domain1"], + parentdomainid=cls.domain.id) + + # Create account1 + cls.account1 = Account.create( + cls.apiclient, + cls.services["acl"]["accountD1"], + domainid=cls.domain1.id + ) + + # Create domain2 + cls.domain2 = Domain.create( + cls.apiclient, + services=cls.services["acl"]["domain2"], + parentdomainid=cls.domain.id) + + # Create account2 + cls.account2 = Account.create( + cls.apiclient, + cls.services["acl"]["accountD2"], + domainid=cls.domain2.id + ) + + cls.services["publiciprange"]["zoneid"] = cls.zone.id + cls.services["publiciprange"]["forvirtualnetwork"] = "true" + + # Create public ip range 1 + cls.services["publiciprange"]["vlan"] = get_free_vlan( + cls.apiclient, + cls.zone.id)[1] + random_subnet_number = random.randrange(10,20) + cls.services["publiciprange"]["gateway"] = "172.16." + \ + str(random_subnet_number) + ".1" + cls.services["publiciprange"]["startip"] = "172.16." + \ + str(random_subnet_number) + ".2" + cls.services["publiciprange"]["endip"] = "172.16." + \ + str(random_subnet_number) + ".10" + cls.services["publiciprange"]["netmask"] = "255.255.255.0" + cls.public_ip_range1 = PublicIpRange.create( + cls.apiclient, + cls.services["publiciprange"] + ) + PublicIpRange.dedicate( + cls.apiclient, + cls.public_ip_range1.vlan.id, + domainid=cls.account1.domainid + ) + + # Create public ip range 2 + cls.services["publiciprange"]["vlan"] = get_free_vlan( + cls.apiclient, + cls.zone.id)[1] + cls.services["publiciprange"]["gateway"] = "172.16." + \ + str(random_subnet_number + 1) + ".1" + cls.services["publiciprange"]["startip"] = "172.16." + \ + str(random_subnet_number + 1) + ".2" + cls.services["publiciprange"]["endip"] = "172.16." + \ + str(random_subnet_number + 1) + ".10" + cls.services["publiciprange"]["netmask"] = "255.255.255.0" + cls.public_ip_range2 = PublicIpRange.create( + cls.apiclient, + cls.services["publiciprange"] + ) + PublicIpRange.dedicate( + cls.apiclient, + cls.public_ip_range2.vlan.id, + account=cls.account1.name, + domainid=cls.account1.domainid + ) + + # Create public ip range 3 + cls.services["publiciprange"]["vlan"] = get_free_vlan( + cls.apiclient, + cls.zone.id)[1] + cls.services["publiciprange"]["gateway"] = "172.16." + \ + str(random_subnet_number + 2) + ".1" + cls.services["publiciprange"]["startip"] = "172.16." + \ + str(random_subnet_number + 2) + ".2" + cls.services["publiciprange"]["endip"] = "172.16." + \ + str(random_subnet_number + 2) + ".10" + cls.services["publiciprange"]["netmask"] = "255.255.255.0" + cls.public_ip_range3 = PublicIpRange.create( + cls.apiclient, + cls.services["publiciprange"] + ) + PublicIpRange.dedicate( + cls.apiclient, + cls.public_ip_range3.vlan.id, + domainid=cls.account2.domainid + ) + + # Create public ip range 4 + cls.services["publiciprange"]["vlan"] = get_free_vlan( + cls.apiclient, + cls.zone.id)[1] + cls.services["publiciprange"]["gateway"] = "172.16." + \ + str(random_subnet_number + 3) + ".1" + cls.services["publiciprange"]["startip"] = "172.16." + \ + str(random_subnet_number + 3) + ".2" + cls.services["publiciprange"]["endip"] = "172.16." + \ + str(random_subnet_number + 3) + ".10" + cls.services["publiciprange"]["netmask"] = "255.255.255.0" + cls.public_ip_range4 = PublicIpRange.create( + cls.apiclient, + cls.services["publiciprange"] + ) + PublicIpRange.dedicate( + cls.apiclient, + cls.public_ip_range4.vlan.id, + account=cls.account2.name, + domainid=cls.account2.domainid + ) + + # Create public ip range 5 + cls.services["publiciprange"]["vlan"] = get_free_vlan( + cls.apiclient, + cls.zone.id)[1] + cls.services["publiciprange"]["gateway"] = "172.16." + \ + str(random_subnet_number + 4) + ".1" + cls.services["publiciprange"]["startip"] = "172.16." + \ + str(random_subnet_number + 4) + ".2" + cls.services["publiciprange"]["endip"] = "172.16." + \ + str(random_subnet_number + 4) + ".10" + cls.services["publiciprange"]["netmask"] = "255.255.255.0" + cls.public_ip_range5 = PublicIpRange.create( + cls.apiclient, + cls.services["publiciprange"] + ) + + cls._cleanup.append(cls.account1) + cls._cleanup.append(cls.domain1) + cls._cleanup.append(cls.account2) + cls._cleanup.append(cls.domain2) + cls._cleanup.append(cls.public_ip_range1) + cls._cleanup.append(cls.public_ip_range2) + cls._cleanup.append(cls.public_ip_range3) + cls._cleanup.append(cls.public_ip_range4) + cls._cleanup.append(cls.public_ip_range5) + + @classmethod + def tearDownClass(cls): + try: + cleanup_resources(cls.apiclient, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(cls): + cls.apiclient = cls.testClient.getApiClient() + cls.cleanup = [] + return + + def tearDown(cls): + try: + cleanup_resources(cls.apiclient, cls.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(tags=["advanced"], required_hardware="false") + def test_01_acquire_public_ip_in_isolated_network(self): + # Validate the following + # 1. create a vm . it will create a network as well. + # 2. assign a specified IP from subnet which is dedicated to domain1, it should succeed + # 3. assign a specified IP from subnet which is dedicated to domain1/account1, it should succeed + # 4. assign a specified IP from subnet which is dedicated to domain2, it should fail + # 5. assign a specified IP from subnet which is dedicated to domain2/account2, it should fail + # 6. update account setting use.system.public.ips to false, assign a specified IP from subnet which is public, it should fail + # 7. update account setting use.system.public.ips to true, assign a specified IP from subnet which is public, it should succeed + + self.service_offering = ServiceOffering.create( + self.apiclient, + self.services["service_offering"] + ) + self.cleanup.append(self.service_offering) + + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["small"], + templateid=self.template.id, + accountid=self.account1.name, + domainid=self.account1.domainid, + serviceofferingid=self.service_offering.id, + zoneid=self.zone.id + ) + + networks = Network.list( + self.apiclient, + account=self.account1.name, + domainid=self.account1.domainid, + listall=True + ) + self.assertEqual( + isinstance(networks, list), + True, + "List networks should return a valid response for created network" + ) + network = networks[0] + + # Associate IP in range dedicated to domain1 + ip_address_1 = self.get_free_ipaddress(self.public_ip_range1.vlan.id) + ipaddress = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + networkid=network.id, + ipaddress=ip_address_1 + ) + self.assertIsNotNone( + ipaddress, + "Failed to Associate IP Address" + ) + self.cleanup.append(ipaddress) + self.assertEqual(ipaddress.ipaddress.ipaddress, ip_address_1, "Associated IP is not same as specified") + + # Associate IP in range dedicated to domain1/account1 + ip_address_2 = self.get_free_ipaddress(self.public_ip_range2.vlan.id) + ipaddress = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + networkid=network.id, + ipaddress=ip_address_2 + ) + self.assertIsNotNone( + ipaddress, + "Failed to Associate IP Address" + ) + self.cleanup.append(ipaddress) + self.assertEqual(ipaddress.ipaddress.ipaddress, ip_address_2, "Associated IP is not same as specified") + + # Associate IP in range dedicated to domain2 + ip_address_3 = self.get_free_ipaddress(self.public_ip_range3.vlan.id) + with self.assertRaises(Exception): + ipaddress = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + networkid=network.id, + ipaddress=ip_address_3 + ) + + # Associate IP in range dedicated to domain2/account2 + ip_address_4 = self.get_free_ipaddress(self.public_ip_range4.vlan.id) + with self.assertRaises(Exception): + ipaddress = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + networkid=network.id, + ipaddress=ip_address_4 + ) + + # Associate IP in public IP pool + Configurations.update( + self.apiclient, + name="use.system.public.ips", + value="false", + accountid=self.account1.id + ) + + ip_address_5 = self.get_free_ipaddress(self.public_ip_range5.vlan.id) + with self.assertRaises(Exception): + ipaddress = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + networkid=network.id, + ipaddress=ip_address_5 + ) + + Configurations.update( + self.apiclient, + name="use.system.public.ips", + value="true", + accountid=self.account1.id + ) + ipaddress = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + networkid=network.id, + ipaddress=ip_address_5 + ) + self.assertIsNotNone( + ipaddress, + "Failed to Associate IP Address" + ) + self.cleanup.append(ipaddress) + self.assertEqual(ipaddress.ipaddress.ipaddress, ip_address_5, "Associated IP is not same as specified") + + self.cleanup.append(self.virtual_machine) + self.cleanup.append(self.service_offering) + + return + + @attr(tags=["advanced"], required_hardware="false") + def test_02_acquire_public_ip_in_vpc(self): + # Validate the following + # 1. create a VPC with default offering. + # 2. assign a specified IP from subnet which is dedicated to domain1, it should succeed + # 3. assign a specified IP from subnet which is dedicated to domain1/account1, it should succeed + # 4. assign a specified IP from subnet which is dedicated to domain2, it should fail + # 5. assign a specified IP from subnet which is dedicated to domain2/account2, it should fail + # 6. update account setting use.system.public.ips to false, assign a specified IP from subnet which is public, it should fail + # 7. update account setting use.system.public.ips to true, assign a specified IP from subnet which is public, it should succeed + + vpcOffering = VpcOffering.list(self.apiclient, name="Default VPC offering") + vpc = VPC.create( + apiclient=self.apiclient, + services=self.services["vpc"], + vpcofferingid=vpcOffering[0].id, + zoneid=self.zone.id, + account=self.account1.name, + domainid=self.account1.domainid + ) + + # Associate IP in range dedicated to domain1 + ip_address_1 = self.get_free_ipaddress(self.public_ip_range1.vlan.id) + ipaddress = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + vpcid=vpc.id, + ipaddress=ip_address_1 + ) + self.assertIsNotNone( + ipaddress, + "Failed to Associate IP Address" + ) + self.cleanup.append(ipaddress) + self.assertEqual(ipaddress.ipaddress.ipaddress, ip_address_1, "Associated IP is not same as specified") + + # Associate IP in range dedicated to domain1/account1 + ip_address_2 = self.get_free_ipaddress(self.public_ip_range2.vlan.id) + ipaddress = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + vpcid=vpc.id, + ipaddress=ip_address_2 + ) + self.assertIsNotNone( + ipaddress, + "Failed to Associate IP Address" + ) + self.cleanup.append(ipaddress) + self.assertEqual(ipaddress.ipaddress.ipaddress, ip_address_2, "Associated IP is not same as specified") + + # Associate IP in range dedicated to domain2 + ip_address_3 = self.get_free_ipaddress(self.public_ip_range3.vlan.id) + with self.assertRaises(Exception): + ipaddress = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + vpcid=vpc.id, + ipaddress=ip_address_3 + ) + + # Associate IP in range dedicated to domain2/account2 + ip_address_4 = self.get_free_ipaddress(self.public_ip_range4.vlan.id) + with self.assertRaises(Exception): + ipaddress = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + vpcid=vpc.id, + ipaddress=ip_address_4 + ) + + # Associate IP in public IP pool + Configurations.update( + self.apiclient, + name="use.system.public.ips", + value="false", + accountid=self.account1.id + ) + + ip_address_5 = self.get_free_ipaddress(self.public_ip_range5.vlan.id) + with self.assertRaises(Exception): + ipaddress = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + vpcid=vpc.id, + ipaddress=ip_address_5 + ) + + Configurations.update( + self.apiclient, + name="use.system.public.ips", + value="true", + accountid=self.account1.id + ) + ipaddress = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + vpcid=vpc.id, + ipaddress=ip_address_5 + ) + self.assertIsNotNone( + ipaddress, + "Failed to Associate IP Address" + ) + self.cleanup.append(ipaddress) + self.assertEqual(ipaddress.ipaddress.ipaddress, ip_address_5, "Associated IP is not same as specified") + + self.cleanup.append(vpc) + return + + def get_free_ipaddress(self, vlanId): + ipaddresses = PublicIPAddress.list( + self.apiclient, + vlanid=vlanId, + state='Free' + ) + self.assertEqual( + isinstance(ipaddresses, list), + True, + "List ipaddresses should return a valid response for Free ipaddresses" + ) + random.shuffle(ipaddresses) + return ipaddresses[0].ipaddress diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index e7572e91801..409fd325c3e 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -1667,7 +1667,7 @@ class PublicIPAddress: @classmethod def create(cls, apiclient, accountid=None, zoneid=None, domainid=None, services=None, networkid=None, projectid=None, vpcid=None, - isportable=False): + isportable=False, ipaddress=None): """Associate Public IP address""" cmd = associateIpAddress.associateIpAddressCmd() @@ -1697,6 +1697,9 @@ class PublicIPAddress: if vpcid: cmd.vpcid = vpcid + + if ipaddress: + cmd.ipaddress = ipaddress return PublicIPAddress(apiclient.associateIpAddress(cmd).__dict__) def delete(self, apiclient): @@ -4054,7 +4057,7 @@ class Configurations: """Manage Configuration""" @classmethod - def update(cls, apiclient, name, value=None, zoneid=None, clusterid=None, storageid=None): + def update(cls, apiclient, name, value=None, zoneid=None, clusterid=None, storageid=None, domainid=None, accountid=None): """Updates the specified configuration""" cmd = updateConfiguration.updateConfigurationCmd() @@ -4067,6 +4070,10 @@ class Configurations: cmd.clusterid = clusterid if storageid: cmd.storageid = storageid + if domainid: + cmd.domainid = domainid + if accountid: + cmd.accountid = accountid apiclient.updateConfiguration(cmd) @classmethod diff --git a/ui/scripts/network.js b/ui/scripts/network.js index aa6af96afd4..86f9fa105e5 100644 --- a/ui/scripts/network.js +++ b/ui/scripts/network.js @@ -2121,6 +2121,50 @@ }); }, isHidden: true + }, + ipaddress: { + label: 'label.ip.address', + select: function(args) { + var data = { + forvirtualnetwork : true, + allocatedonly: false + }; + if ('vpc' in args.context) { //from VPC section + $.extend(data, { + zoneid: args.context.vpc[0].zoneid, + domainid: args.context.vpc[0].domainid, + account: args.context.vpc[0].account + }); + } else if ('networks' in args.context) { //from Guest Network section + $.extend(data, { + zoneid: args.context.networks[0].zoneid, + domainid: args.context.networks[0].domainid, + account: args.context.networks[0].account + }); + } + $.ajax({ + url: createURL('listPublicIpAddresses'), + data: data, + success: function(json) { + var ips = json.listpublicipaddressesresponse.publicipaddress; + var items = [{ + id: -1, + description: '' + }]; + $(ips).each(function() { + if (this.state == "Free") { + items.push({ + id: this.ipaddress, + description: this.ipaddress + }); + } + }); + args.response.success({ + data: items + }); + } + }) + } } } }, @@ -2149,6 +2193,11 @@ } } + if (args.data.ipaddress != null && args.data.ipaddress.length > 0) { + $.extend(dataObj, { + ipaddress: args.data.ipaddress + }); + } $.ajax({ url: createURL('associateIpAddress'), data: dataObj,