mirror of https://github.com/apache/cloudstack.git
315 lines
12 KiB
Python
315 lines
12 KiB
Python
# Licensed to the Apache Software Foundation (ASF) under one
|
||
# or more contributor license agreements. See the NOTICE file
|
||
# distributed with this work for additional information
|
||
# regarding copyright ownership. The ASF licenses this file
|
||
# to you under the Apache License, Version 2.0 (the
|
||
# "License"); you may not use this file except in compliance
|
||
# with the License. You may obtain a copy of the License at
|
||
#
|
||
# http://www.apache.org/licenses/LICENSE-2.0
|
||
#
|
||
# Unless required by applicable law or agreed to in writing,
|
||
# software distributed under the License is distributed on an
|
||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||
# KIND, either express or implied. See the License for the
|
||
# specific language governing permissions and limitations
|
||
# under the License.
|
||
|
||
"""Tests for VPC Conserve Mode (since 4.23.0)
|
||
|
||
Conserve mode allows public IP services (LB, Port Forwarding, Static NAT) to be
|
||
shared across multiple VPC tiers using the same public IP address.
|
||
|
||
When conserve mode is ON:
|
||
- A single public IP can have rules targeting VMs in different VPC tiers
|
||
- FirewallManagerImpl skips the cross-network conflict check for that VPC
|
||
|
||
When conserve mode is OFF (default before 4.23.0):
|
||
- Rules on a given public IP must all belong to the same VPC tier (network)
|
||
- Attempting to create a rule on a different tier than an existing rule raises
|
||
a NetworkRuleConflictException
|
||
"""
|
||
|
||
from marvin.cloudstackException import CloudstackAPIException
|
||
from marvin.cloudstackTestCase import cloudstackTestCase
|
||
from marvin.codes import FAILED
|
||
from marvin.lib.base import (
|
||
Account,
|
||
LoadBalancerRule,
|
||
NATRule,
|
||
Network,
|
||
NetworkOffering,
|
||
PublicIPAddress,
|
||
ServiceOffering,
|
||
VirtualMachine,
|
||
VPC,
|
||
VpcOffering,
|
||
)
|
||
from marvin.lib.common import (
|
||
get_domain,
|
||
get_test_template,
|
||
get_zone,
|
||
list_publicIP
|
||
)
|
||
from marvin.lib.utils import cleanup_resources
|
||
from nose.plugins.attrib import attr
|
||
import logging
|
||
|
||
class TestVPCConserveModeRules(cloudstackTestCase):
|
||
"""Tests that conserve mode for VPC controls whether rules on the same public IP are allowed in multiple VPC tiers.
|
||
"""
|
||
|
||
@classmethod
|
||
def setUpClass(cls):
|
||
cls.testClient = super(TestVPCConserveModeRules, cls).getClsTestClient()
|
||
cls.apiclient = cls.testClient.getApiClient()
|
||
cls.services = cls.testClient.getParsedTestDataConfig()
|
||
cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests())
|
||
cls.domain = get_domain(cls.apiclient)
|
||
cls.hypervisor = cls.testClient.getHypervisorInfo()
|
||
cls.logger = logging.getLogger("TestVPCConserveModeRules")
|
||
cls._cleanup = []
|
||
|
||
cls.account = Account.create(
|
||
cls.apiclient,
|
||
cls.services["account"],
|
||
admin=True,
|
||
domainid=cls.domain.id)
|
||
cls._cleanup.append(cls.account)
|
||
|
||
cls.template = get_test_template(
|
||
cls.apiclient,
|
||
cls.zone.id,
|
||
cls.hypervisor)
|
||
if cls.template == FAILED:
|
||
assert False, "get_test_template() failed to return template"
|
||
|
||
cls.service_offering = ServiceOffering.create(
|
||
cls.apiclient,
|
||
cls.services["service_offerings"]["tiny"]
|
||
)
|
||
cls._cleanup.append(cls.service_offering)
|
||
|
||
cls.services["vpc_offering"]["supportedservices"] = 'Vpn,Dhcp,Dns,SourceNat,Lb,UserData,StaticNat,NetworkACL,PortForwarding'
|
||
cls.services["vpc_offering"]["conservemode"] = True
|
||
cls.vpc_offering_conserve_mode = VpcOffering.create(
|
||
cls.apiclient,
|
||
cls.services["vpc_offering"]
|
||
)
|
||
cls.vpc_offering_conserve_mode.update(cls.apiclient, state="Enabled")
|
||
cls._cleanup.append(cls.vpc_offering_conserve_mode)
|
||
|
||
cls.services["network_offering"]["supportedservices"] = 'Vpn,Dhcp,Dns,SourceNat,Lb,UserData,StaticNat,NetworkACL,PortForwarding'
|
||
cls.services["network_offering"]["serviceProviderList"] = {
|
||
"Vpn": 'VpcVirtualRouter',
|
||
"Dhcp": 'VpcVirtualRouter',
|
||
"Dns": 'VpcVirtualRouter',
|
||
"SourceNat": 'VpcVirtualRouter',
|
||
"Lb": 'VpcVirtualRouter',
|
||
"UserData": 'VpcVirtualRouter',
|
||
"StaticNat": 'VpcVirtualRouter',
|
||
"NetworkACL": 'VpcVirtualRouter',
|
||
"PortForwarding": 'VpcVirtualRouter'
|
||
}
|
||
cls.network_offering = NetworkOffering.create(
|
||
cls.apiclient,
|
||
cls.services["network_offering"],
|
||
conservemode=True
|
||
)
|
||
cls.network_offering.update(cls.apiclient, state="Enabled")
|
||
cls._cleanup.append(cls.network_offering)
|
||
|
||
cls.services["vpc"]["cidr"] = "10.10.20.0/24"
|
||
|
||
cls.vpc = VPC.create(
|
||
cls.apiclient,
|
||
cls.services["vpc"],
|
||
vpcofferingid=cls.vpc_offering_conserve_mode.id,
|
||
zoneid=cls.zone.id,
|
||
account=cls.account.name,
|
||
domainid=cls.account.domainid,
|
||
)
|
||
cls._cleanup.append(cls.vpc)
|
||
|
||
gateway_tier1 = "10.10.20.1"
|
||
netmask_tiers = "255.255.255.240"
|
||
|
||
cls.services["network_offering"]["name"] = "tier1-" + cls.vpc.id
|
||
cls.services["network_offering"]["displayname"] = "tier1-" + cls.vpc.id
|
||
cls.tier1 = Network.create(
|
||
cls.apiclient,
|
||
services=cls.services["network_offering"],
|
||
accountid=cls.account.name,
|
||
domainid=cls.account.domainid,
|
||
networkofferingid=cls.network_offering.id,
|
||
zoneid=cls.zone.id,
|
||
vpcid=cls.vpc.id,
|
||
gateway=gateway_tier1,
|
||
netmask=netmask_tiers,
|
||
)
|
||
cls._cleanup.append(cls.tier1)
|
||
|
||
gateway_tier2 = "10.10.20.17"
|
||
cls.services["network_offering"]["name"] = "tier2-" + cls.vpc.id
|
||
cls.services["network_offering"]["displayname"] = "tier2-" + cls.vpc.id
|
||
cls.tier2 = Network.create(
|
||
cls.apiclient,
|
||
services=cls.services["network_offering"],
|
||
accountid=cls.account.name,
|
||
domainid=cls.account.domainid,
|
||
networkofferingid=cls.network_offering.id,
|
||
zoneid=cls.zone.id,
|
||
vpcid=cls.vpc.id,
|
||
gateway=gateway_tier2,
|
||
netmask=netmask_tiers,
|
||
)
|
||
cls._cleanup.append(cls.tier2)
|
||
|
||
cls.services["virtual_machine"]["displayname"] = "vm1" + cls.vpc.id
|
||
cls.vm1 = VirtualMachine.create(
|
||
cls.apiclient,
|
||
services=cls.services["virtual_machine"],
|
||
templateid=cls.template.id,
|
||
zoneid=cls.zone.id,
|
||
accountid=cls.account.name,
|
||
domainid=cls.account.domainid,
|
||
serviceofferingid=cls.service_offering.id,
|
||
networkids=[cls.tier1.id],
|
||
)
|
||
cls.services["virtual_machine"]["displayname"] = "vm2" + cls.vpc.id
|
||
cls.vm2 = VirtualMachine.create(
|
||
cls.apiclient,
|
||
services=cls.services["virtual_machine"],
|
||
templateid=cls.template.id,
|
||
zoneid=cls.zone.id,
|
||
accountid=cls.account.name,
|
||
domainid=cls.account.domainid,
|
||
serviceofferingid=cls.service_offering.id,
|
||
networkids=[cls.tier2.id],
|
||
)
|
||
cls._cleanup.append(cls.vm1)
|
||
cls._cleanup.append(cls.vm2)
|
||
|
||
@classmethod
|
||
def tearDownClass(cls):
|
||
super(TestVPCConserveModeRules, cls).tearDownClass()
|
||
|
||
def setUp(self):
|
||
self.apiclient = self.testClient.getApiClient()
|
||
self.cleanup = []
|
||
|
||
def tearDown(self):
|
||
super(TestVPCConserveModeRules, self).tearDown()
|
||
|
||
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false")
|
||
def test_01_vpc_conserve_mode_cross_tier_rules_allowed(self):
|
||
"""With conserveMode=True, LB rule on VPC Tier 1 and Port Forwarding rule on VPC Tier 2 can
|
||
share the same public IP without a NetworkRuleConflictException.
|
||
"""
|
||
|
||
public_ip = PublicIPAddress.create(
|
||
self.apiclient,
|
||
zoneid=self.zone.id,
|
||
accountid=self.account.name,
|
||
domainid=self.account.domainid,
|
||
vpcid=self.vpc.id,
|
||
)
|
||
|
||
self.logger.debug(
|
||
"Creating LB rule on tier-1 (networkid=%s) using public IP %s",
|
||
self.tier1.id,
|
||
public_ip.ipaddress.ipaddress,
|
||
)
|
||
lb_rule_tier1 = LoadBalancerRule.create(
|
||
self.apiclient,
|
||
self.services["lbrule"],
|
||
ipaddressid=public_ip.ipaddress.id,
|
||
accountid=self.account.name,
|
||
vpcid=self.vpc.id,
|
||
networkid=self.tier1.id,
|
||
domainid=self.account.domainid,
|
||
)
|
||
self.assertIsNotNone(lb_rule_tier1, "LB rule creation on tier-1 failed")
|
||
lb_rule_tier1.assign(self.apiclient, [self.vm1])
|
||
|
||
self.logger.debug(
|
||
"Creating Port Forwarding rule on tier-2 (networkid=%s) "
|
||
"using the same public IP %s – should succeed with conserve mode",
|
||
self.tier2.id,
|
||
public_ip.ipaddress.ipaddress,
|
||
)
|
||
try:
|
||
nat_rule = NATRule.create(
|
||
self.apiclient,
|
||
self.vm2,
|
||
self.services["natrule"],
|
||
ipaddressid=public_ip.ipaddress.id,
|
||
vpcid=self.vpc.id,
|
||
networkid=self.tier2.id,
|
||
)
|
||
self.assertIsNotNone(
|
||
nat_rule,
|
||
"Port Forwarding rule creation on tier-2 failed unexpectedly",
|
||
)
|
||
except CloudstackAPIException as e:
|
||
self.fail(
|
||
"Expected cross-tier Port Forwarding rule to succeed with "
|
||
"conserveMode=True, but got exception: %s" % e
|
||
)
|
||
|
||
@attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false")
|
||
def test_02_vpc_conserve_mode_reuse_source_nat_ip_address(self):
|
||
"""With VPC conserve mode enabled, a NAT rule can be created on a VPC tier (conserve mode enabled)
|
||
with a source NAT IP address
|
||
"""
|
||
source_nat_ip_resp = list_publicIP(
|
||
self.apiclient,
|
||
vpcid=self.vpc.id,
|
||
listall=True,
|
||
issourcenat=True
|
||
)
|
||
|
||
source_nat_ip = source_nat_ip_resp[0]
|
||
|
||
self.logger.debug(
|
||
"Creating Port Forwarding rule on tier-2 (networkid=%s) "
|
||
"using the source NAT public IP %s – should succeed with conserve mode",
|
||
self.tier1.id,
|
||
source_nat_ip.ipaddress,
|
||
)
|
||
try:
|
||
nat_rule = NATRule.create(
|
||
self.apiclient,
|
||
self.vm2,
|
||
self.services["natrule"],
|
||
ipaddressid=source_nat_ip.id,
|
||
vpcid=self.vpc.id,
|
||
networkid=self.tier2.id,
|
||
)
|
||
self.assertIsNotNone(
|
||
nat_rule,
|
||
"Port Forwarding rule creation on tier-2 failed unexpectedly",
|
||
)
|
||
self.logger.debug(
|
||
"Creating LB rule on tier-1 (networkid=%s) "
|
||
"using the source NAT public IP %s – should succeed with conserve mode",
|
||
self.tier1.id,
|
||
source_nat_ip.ipaddress,
|
||
)
|
||
lb_rule_tier1 = LoadBalancerRule.create(
|
||
self.apiclient,
|
||
self.services["lbrule"],
|
||
ipaddressid=source_nat_ip.id,
|
||
accountid=self.account.name,
|
||
vpcid=self.vpc.id,
|
||
networkid=self.tier2.id,
|
||
domainid=self.account.domainid,
|
||
)
|
||
self.assertIsNotNone(lb_rule_tier1, "LB rule creation on tier-2 failed")
|
||
lb_rule_tier1.assign(self.apiclient, [self.vm2])
|
||
except CloudstackAPIException as e:
|
||
self.fail(
|
||
"Expected multiple rules on VPC Source NAT IP to succeed with "
|
||
"conserveMode=True, but got exception: %s" % e
|
||
)
|