From f89f96205796f88c646ac68deb82c4e0edb272df Mon Sep 17 00:00:00 2001 From: Andrey Kurilin Date: Tue, 28 Apr 2020 00:30:49 +0300 Subject: [PATCH] Introduce NeutronService helper This helper is a way to unify the way of usage Neutron API and remove code duplication. - NeutronWrapper was developed with a compatible to NovaNetworkWrapper interface. This means a lot of limitations that was introduced from the beggining - NeutronUtils was build on top of Scenario plugin class that makes it hard to be reusable from context objects Change-Id: Ie87d5145c3b58f961fc56fe6375860919416318d --- .../common/services/network/__init__.py | 0 .../common/services/network/net_utils.py | 57 + .../common/services/network/neutron.py | 1358 +++++++++++++++++ rally_openstack/common/wrappers/network.py | 244 ++- rally_openstack/task/cleanup/resources.py | 61 +- .../task/scenarios/neutron/utils.py | 308 ++-- rally_openstack/task/scenarios/nova/utils.py | 78 +- .../unit/common/services/network/__init__.py | 0 .../common/services/network/test_net_utils.py | 42 + .../common/services/network/test_neutron.py | 1237 +++++++++++++++ tests/unit/common/wrappers/test_network.py | 439 +++--- tests/unit/task/cleanup/test_resources.py | 24 +- .../unit/task/scenarios/neutron/test_utils.py | 550 +++---- tests/unit/task/scenarios/nova/test_utils.py | 28 +- 14 files changed, 3481 insertions(+), 945 deletions(-) create mode 100644 rally_openstack/common/services/network/__init__.py create mode 100644 rally_openstack/common/services/network/net_utils.py create mode 100644 rally_openstack/common/services/network/neutron.py create mode 100644 tests/unit/common/services/network/__init__.py create mode 100644 tests/unit/common/services/network/test_net_utils.py create mode 100644 tests/unit/common/services/network/test_neutron.py diff --git a/rally_openstack/common/services/network/__init__.py b/rally_openstack/common/services/network/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/rally_openstack/common/services/network/net_utils.py b/rally_openstack/common/services/network/net_utils.py new file mode 100644 index 00000000..56fbb98e --- /dev/null +++ b/rally_openstack/common/services/network/net_utils.py @@ -0,0 +1,57 @@ +# All Rights Reserved. +# +# Licensed 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. + +import netaddr + +from rally.common import logging +from rally.common import utils + + +LOG = logging.getLogger(__name__) + + +_IPv4_START_CIDR = "10.2.0.0/24" +_IPv6_START_CIDR = "dead:beaf::/64" + +_IPv4_CIDR_INCR = utils.RAMInt() +_IPv6_CIDR_INCR = utils.RAMInt() + + +def get_ip_version(ip): + return netaddr.IPNetwork(ip).version + + +def generate_cidr(ip_version=None, start_cidr=None): + """Generate next CIDR for network or subnet, without IP overlapping. + + This is process and thread safe, because `cidr_incr' points to + value stored directly in RAM. This guarantees that CIDRs will be + serial and unique even under hard multiprocessing/threading load. + + :param ip_version: version of IP to take default value for start_cidr + :param start_cidr: start CIDR str + """ + if start_cidr is None: + if ip_version == 6: + start_cidr = _IPv6_START_CIDR + else: + start_cidr = _IPv4_START_CIDR + + ip_version = get_ip_version(start_cidr) + if ip_version == 4: + cidr = str(netaddr.IPNetwork(start_cidr).next(next(_IPv4_CIDR_INCR))) + else: + cidr = str(netaddr.IPNetwork(start_cidr).next(next(_IPv6_CIDR_INCR))) + LOG.debug("CIDR generated: %s" % cidr) + return ip_version, cidr diff --git a/rally_openstack/common/services/network/neutron.py b/rally_openstack/common/services/network/neutron.py new file mode 100644 index 00000000..e06005a3 --- /dev/null +++ b/rally_openstack/common/services/network/neutron.py @@ -0,0 +1,1358 @@ +# All Rights Reserved. +# +# Licensed 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. + +import itertools + +from rally.common import cfg +from rally.common import logging +from rally import exceptions +from rally.task import atomic +from rally.task import service + +from rally_openstack.common.services.network import net_utils + + +CONF = cfg.CONF +LOG = logging.getLogger(__name__) + + +def _args_adapter(arguments_map): + def wrapper(func): + def decorator(*args, **kwargs): + for source, dest in arguments_map.items(): + if source in kwargs: + if dest in kwargs: + raise TypeError( + f"{func.__name__}() accepts either {dest} keyword " + f"argument or {source} but both were specified.") + kwargs[dest] = kwargs.pop(source) + return func(*args, **kwargs) + return decorator + return wrapper + + +_NETWORK_ARGS_MAP = { + "provider:network_type": "provider_network_type", + "provider:physical_network": "provider_physical_network", + "provider:segmentation_id": "provider_segmentation_id", + "router:external": "router_external" +} + + +def _create_network_arg_adapter(): + """A decorator for converting neutron's create kwargs to look pythonic.""" + return _args_adapter(_NETWORK_ARGS_MAP) + + +class _NoneObj(object): + def __len__(self): + return 0 + + +_NONE = _NoneObj() + + +def _clean_dict(**kwargs): + """Builds a dict object from keyword arguments ignoring nullable values.""" + return dict((k, v) for k, v in kwargs.items() if v != _NONE) + + +@service.service(service_name="neutron", service_type="network", version="2.0") +class NeutronService(service.Service): + """A helper class for Neutron API""" + + def __init__(self, *args, **kwargs): + super(NeutronService, self).__init__(*args, **kwargs) + self._cached_supported_extensions = None + self._client = None + + @property + def client(self): + if self._client is None: + self._client = self._clients.neutron() + return self._client + + def create_network_topology( + self, network_create_args=None, + router_create_args=None, router_per_subnet=False, + subnet_create_args=None, subnets_count=1, subnets_dualstack=False + ): + """Create net infrastructure(network, router, subnets). + + :param network_create_args: A dict with creation arguments for a + network. The format is equal to the create_network method + :param router_create_args: A dict with creation arguments for an + external router that will add an interface to each created subnet. + The format is equal to the create_subnet method + In case of None value (default behaviour), no router is created. + :param router_per_subnet: whether or not to create router per subnet + or use one router for all subnets. + :param subnet_create_args: A dict with creation arguments for + subnets. The format is equal to the create_subnet method. + :param subnets_count: Number of subnets to create per network. + Defaults to 1 + :param subnets_dualstack: Whether subnets should be of both IPv4 and + IPv6 (i.e first subnet will be created for IPv4, the second for + IPv6, the third for IPv4,..). If subnet_create_args includes one of + ('cidr', 'start_cidr', 'ip_version') keys, subnets_dualstack + parameter will be ignored. + """ + subnet_create_args = dict(subnet_create_args or {}) + + network = self.create_network(**(network_create_args or {})) + subnet_create_args["network_id"] = network["id"] + + routers = [] + if router_create_args is not None: + for i in range(subnets_count if router_per_subnet else 1): + routers.append(self.create_router(**router_create_args)) + + subnets = [] + ip_versions = itertools.cycle([4, 6] if subnets_dualstack else [4]) + use_subnets_dualstack = ( + "cidr" not in subnet_create_args + and "start_cidr" not in subnet_create_args + and "ip_version" not in subnet_create_args + ) + + for i in range(subnets_count): + if use_subnets_dualstack: + subnet_create_args["ip_version"] = next(ip_versions) + if routers: + if router_per_subnet: + router = routers[i] + else: + router = routers[0] + subnet_create_args["router_id"] = router["id"] + + subnets.append(self.create_subnet(**subnet_create_args)) + + network["subnets"] = [s["id"] for s in subnets] + + return { + "network": network, + "subnets": subnets, + "routers": routers + } + + def delete_network_topology(self, topo): + """Delete network topology + + This method was developed to provide a backward compatibility with old + neutron helpers. It is not recommended way and we suggest to use + cleanup manager instead. + + :param topo: Network topology as create_network_topology returned + """ + for router in topo["routers"]: + self.remove_gateway_from_router(router["id"]) + + network_id = topo["network"]["id"] + + for port in self.list_ports(network_id=network_id): + self.delete_port(port) + + for subnet in self.list_subnets(network_id=network_id): + self.delete_subnet(subnet["id"]) + + self.delete_network(network_id) + + for router in topo["routers"]: + self.delete_router(router["id"]) + + @atomic.action_timer("neutron.create_network") + @_create_network_arg_adapter() + def create_network(self, + project_id=_NONE, + admin_state_up=_NONE, + dns_domain=_NONE, + mtu=_NONE, + port_security_enabled=_NONE, + provider_network_type=_NONE, + provider_physical_network=_NONE, + provider_segmentation_id=_NONE, + qos_policy_id=_NONE, + router_external=_NONE, + segments=_NONE, + shared=_NONE, + vlan_transparent=_NONE, + description=_NONE, + availability_zone_hints=_NONE): + """Create neutron network. + + :param project_id: The ID of the project that owns the resource. Only + administrative and users with advsvc role can specify a project ID + other than their own. You cannot change this value through + authorization policies. + :param admin_state_up: The administrative state of the network, + which is up (true) or down (false). + :param dns_domain: A valid DNS domain. + :param mtu: The maximum transmission unit (MTU) value to address + fragmentation. Minimum value is 68 for IPv4, and 1280 for IPv6. + :param port_security_enabled: The port security status of the network. + Valid values are enabled (true) and disabled (false). This value is + used as the default value of port_security_enabled field of a + newly created port. + :param provider_network_type: The type of physical network that this + network should be mapped to. For example, flat, vlan, vxlan, + or gre. Valid values depend on a networking back-end. + :param provider_physical_network: The physical network where this + network should be implemented. The Networking API v2.0 does not + provide a way to list available physical networks. + For example, the Open vSwitch plug-in configuration file defines + a symbolic name that maps to specific bridges on each compute host. + :param provider_segmentation_id: The ID of the isolated segment on the + physical network. The network_type attribute defines the + segmentation model. For example, if the network_type value is vlan, + this ID is a vlan identifier. If the network_type value is gre, + this ID is a gre key. + :param qos_policy_id: The ID of the QoS policy associated with the + network. + :param router_external: Indicates whether the network has an external + routing facility that’s not managed by the networking service. + :param segments: A list of provider segment objects. + :param shared: Indicates whether this resource is shared across all + projects. By default, only administrative users can change + this value. + :param vlan_transparent: Indicates the VLAN transparency mode of the + network, which is VLAN transparent (true) or not VLAN + transparent (false). + :param description: A human-readable description for the resource. + Default is an empty string. + :param availability_zone_hints: The availability zone candidate for + the network. + :returns: neutron network dict + """ + body = _clean_dict( + name=self.generate_random_name(), + tenant_id=project_id, + admin_state_up=admin_state_up, + dns_domain=dns_domain, + mtu=mtu, + port_security_enabled=port_security_enabled, + qos_policy_id=qos_policy_id, + segments=segments, + shared=shared, + vlan_transparent=vlan_transparent, + description=description, + availability_zone_hints=availability_zone_hints, + **{ + "provider:network_type": provider_network_type, + "provider:physical_network": provider_physical_network, + "provider:segmentation_id": provider_segmentation_id, + "router:external": router_external + } + ) + resp = self.client.create_network({"network": body}) + return resp["network"] + + @atomic.action_timer("neutron.show_network") + def get_network(self, network_id, fields=_NONE): + """Get network by ID + + :param network_id: Network ID to fetch data for + :param fields: The fields that you want the server to return. If no + fields list is specified, the networking API returns all + attributes allowed by the policy settings. By using fields + parameter, the API returns only the requested set of attributes. + """ + body = _clean_dict(fields=fields) + resp = self.client.show_network(network_id, **body) + return resp["network"] + + def find_network(self, network_id_or_name): + """Find network by identifier (id or name) + + :param network_id_or_name: Network ID or name + """ + for net in self.list_networks(): + if network_id_or_name in (net["name"], net["id"]): + return net + raise exceptions.GetResourceFailure( + resource="network", + err=f"no name or id matches {network_id_or_name}") + + @atomic.action_timer("neutron.update_network") + @_create_network_arg_adapter() + def update_network(self, + network_id, + name=_NONE, + admin_state_up=_NONE, + dns_domain=_NONE, + mtu=_NONE, + port_security_enabled=_NONE, + provider_network_type=_NONE, + provider_physical_network=_NONE, + provider_segmentation_id=_NONE, + qos_policy_id=_NONE, + router_external=_NONE, + segments=_NONE, + shared=_NONE, + description=_NONE, + is_default=_NONE): + """Update neutron network. + + :param network_id: ID of the network to update + :param name: Human-readable name of the network. + :param admin_state_up: The administrative state of the network, + which is up (true) or down (false). + :param dns_domain: A valid DNS domain. + :param mtu: The maximum transmission unit (MTU) value to address + fragmentation. Minimum value is 68 for IPv4, and 1280 for IPv6. + :param port_security_enabled: The port security status of the network. + Valid values are enabled (true) and disabled (false). This value is + used as the default value of port_security_enabled field of a + newly created port. + :param provider_network_type: The type of physical network that this + network should be mapped to. For example, flat, vlan, vxlan, + or gre. Valid values depend on a networking back-end. + :param provider_physical_network: The physical network where this + network should be implemented. The Networking API v2.0 does not + provide a way to list available physical networks. + For example, the Open vSwitch plug-in configuration file defines + a symbolic name that maps to specific bridges on each compute host. + :param provider_segmentation_id: The ID of the isolated segment on the + physical network. The network_type attribute defines the + segmentation model. For example, if the network_type value is vlan, + this ID is a vlan identifier. If the network_type value is gre, + this ID is a gre key. + :param qos_policy_id: The ID of the QoS policy associated with the + network. + :param router_external: Indicates whether the network has an external + routing facility that’s not managed by the networking service. + :param segments: A list of provider segment objects. + :param shared: Indicates whether this resource is shared across all + projects. By default, only administrative users can change + this value. + :param description: A human-readable description for the resource. + Default is an empty string. + :param is_default: The network is default or not. + :returns: neutron network dict + """ + body = _clean_dict( + name=name, + admin_state_up=admin_state_up, + dns_domain=dns_domain, + mtu=mtu, + port_security_enabled=port_security_enabled, + qos_policy_id=qos_policy_id, + segments=segments, + shared=shared, + description=description, + is_default=is_default, + **{ + "provider:network_type": provider_network_type, + "provider:physical_network": provider_physical_network, + "provider:segmentation_id": provider_segmentation_id, + "router:external": router_external + } + ) + if not body: + raise TypeError("No updates for a network.") + resp = self.client.update_network(network_id, {"network": body}) + return resp["network"] + + @atomic.action_timer("neutron.delete_network") + def delete_network(self, network_id): + """Delete network + + :param network_id: Network ID + """ + self.client.delete_network(network_id) + + @atomic.action_timer("neutron.list_networks") + def list_networks(self, name=_NONE, router_external=_NONE, status=_NONE, + **kwargs): + """List networks. + + :param name: Filter the list result by the human-readable name of the + resource. + :param router_external: Filter the network list result based on whether + the network has an external routing facility that’s not managed by + the networking service. + :param status: Filter the network list result by network status. + Values are ACTIVE, DOWN, BUILD or ERROR. + :param kwargs: additional network list filters + """ + kwargs["router:external"] = router_external + filters = _clean_dict(name=name, status=status, **kwargs) + return self.client.list_networks(**filters)["networks"] + + IPv4_DEFAULT_DNS_NAMESERVERS = ["8.8.8.8", "8.8.4.4"] + IPv6_DEFAULT_DNS_NAMESERVERS = ["dead:beaf::1", "dead:beaf::2"] + + @atomic.action_timer("neutron.create_subnet") + def create_subnet(self, network_id, router_id=_NONE, project_id=_NONE, + enable_dhcp=_NONE, + dns_nameservers=_NONE, allocation_pools=_NONE, + host_routes=_NONE, ip_version=_NONE, gateway_ip=_NONE, + cidr=_NONE, start_cidr=_NONE, prefixlen=_NONE, + ipv6_address_mode=_NONE, ipv6_ra_mode=_NONE, + segment_id=_NONE, subnetpool_id=_NONE, + use_default_subnetpool=_NONE, service_types=_NONE, + dns_publish_fixed_ip=_NONE): + """Create neutron subnet. + + :param network_id: The ID of the network to which the subnet belongs. + :param router_id: An external router and add as an interface to subnet. + :param project_id: The ID of the project that owns the resource. + Only administrative and users with advsvc role can specify a + project ID other than their own. You cannot change this value + through authorization policies. + :param enable_dhcp: Indicates whether dhcp is enabled or disabled for + the subnet. Default is true. + :param dns_nameservers: List of dns name servers associated with the + subnet. Default is a list of Google DNS + :param allocation_pools: Allocation pools with start and end IP + addresses for this subnet. If allocation_pools are not specified, + OpenStack Networking automatically allocates pools for covering + all IP addresses in the CIDR, excluding the address reserved for + the subnet gateway by default. + :param host_routes: Additional routes for the subnet. A list of + dictionaries with destination and nexthop parameters. Default + value is an empty list. + :param gateway_ip: Gateway IP of this subnet. If the value is null that + implies no gateway is associated with the subnet. If the gateway_ip + is not specified, OpenStack Networking allocates an address from + the CIDR for the gateway for the subnet by default. + :param ip_version: The IP protocol version. Value is 4 or 6. If CIDR + is specified, the value automatically can be detected from it, + otherwise defaults to 4. + Also, check start_cidr param description. + :param cidr: The CIDR of the subnet. If not specified, it will be + auto-generated based on start_cidr and ip_version parameters. + :param start_cidr: + :param prefixlen: he prefix length to use for subnet allocation from a + subnet pool. If not specified, the default_prefixlen value of the + subnet pool will be used. + :param ipv6_address_mode: The IPv6 address modes specifies mechanisms + for assigning IP addresses. Value is slaac, dhcpv6-stateful, + dhcpv6-stateless. + :param ipv6_ra_mode: The IPv6 router advertisement specifies whether + the networking service should transmit ICMPv6 packets, for a + subnet. Value is slaac, dhcpv6-stateful, dhcpv6-stateless. + :param segment_id: The ID of a network segment the subnet is + associated with. It is available when segment extension is enabled. + :param subnetpool_id: The ID of the subnet pool associated with the + subnet. + :param use_default_subnetpool: Whether to allocate this subnet from + the default subnet pool. + :param service_types: The service types associated with the subnet. + :param dns_publish_fixed_ip: Whether to publish DNS records for IPs + from this subnet. Default is false. + """ + + if cidr == _NONE: + ip_version, cidr = net_utils.generate_cidr( + ip_version=ip_version, start_cidr=(start_cidr or None)) + if ip_version == _NONE: + ip_version = net_utils.get_ip_version(cidr) + + if dns_nameservers == _NONE: + if ip_version == 4: + dns_nameservers = self.IPv4_DEFAULT_DNS_NAMESERVERS + else: + dns_nameservers = self.IPv6_DEFAULT_DNS_NAMESERVERS + + body = _clean_dict( + name=self.generate_random_name(), + network_id=network_id, + tenant_id=project_id, + enable_dhcp=enable_dhcp, + dns_nameservers=dns_nameservers, + allocation_pools=allocation_pools, + host_routes=host_routes, + ip_version=ip_version, + gateway_ip=gateway_ip, + cidr=cidr, + prefixlen=prefixlen, + ipv6_address_mode=ipv6_address_mode, + ipv6_ra_mode=ipv6_ra_mode, + segment_id=segment_id, + subnetpool_id=subnetpool_id, + use_default_subnetpool=use_default_subnetpool, + service_types=service_types, + dns_publish_fixed_ip=dns_publish_fixed_ip + ) + + subnet = self.client.create_subnet({"subnet": body})["subnet"] + if router_id: + self.add_interface_to_router(router_id=router_id, + subnet_id=subnet["id"]) + return subnet + + @atomic.action_timer("neutron.show_subnet") + def get_subnet(self, subnet_id): + """Get subnet + + :param subnet_id: Subnet ID + """ + return self.client.show_subnet(subnet_id)["subnet"] + + @atomic.action_timer("neutron.update_subnet") + def update_subnet(self, subnet_id, name=_NONE, enable_dhcp=_NONE, + dns_nameservers=_NONE, allocation_pools=_NONE, + host_routes=_NONE, gateway_ip=_NONE, description=_NONE, + service_types=_NONE, segment_id=_NONE, + dns_publish_fixed_ip=_NONE): + """Update neutron subnet. + + :param subnet_id: The ID of the subnet to update. + :param name: Human-readable name of the resource. + :param description: A human-readable description for the resource. + Default is an empty string. + :param enable_dhcp: Indicates whether dhcp is enabled or disabled for + the subnet. Default is true. + :param dns_nameservers: List of dns name servers associated with the + subnet. Default is a list of Google DNS + :param allocation_pools: Allocation pools with start and end IP + addresses for this subnet. If allocation_pools are not specified, + OpenStack Networking automatically allocates pools for covering + all IP addresses in the CIDR, excluding the address reserved for + the subnet gateway by default. + :param host_routes: Additional routes for the subnet. A list of + dictionaries with destination and nexthop parameters. Default + value is an empty list. + :param gateway_ip: Gateway IP of this subnet. If the value is null that + implies no gateway is associated with the subnet. If the gateway_ip + is not specified, OpenStack Networking allocates an address from + the CIDR for the gateway for the subnet by default. + :param segment_id: The ID of a network segment the subnet is + associated with. It is available when segment extension is enabled. + :param service_types: The service types associated with the subnet. + :param dns_publish_fixed_ip: Whether to publish DNS records for IPs + from this subnet. Default is false. + """ + + body = _clean_dict( + name=name, + enable_dhcp=enable_dhcp, + dns_nameservers=dns_nameservers, + allocation_pools=allocation_pools, + host_routes=host_routes, + gateway_ip=gateway_ip, + segment_id=segment_id, + service_types=service_types, + dns_publish_fixed_ip=dns_publish_fixed_ip, + description=description + ) + + if not body: + raise TypeError("No updates for a subnet.") + + resp = self.client.update_subnet(subnet_id, {"subnet": body})["subnet"] + return resp + + @atomic.action_timer("neutron.delete_subnet") + def delete_subnet(self, subnet_id): + """Delete subnet + + :param subnet_id: Subnet ID + """ + self.client.delete_subnet(subnet_id) + + @atomic.action_timer("neutron.list_subnets") + def list_subnets(self, network_id=_NONE, **filters): + """List subnets. + + :param network_id: Filter the subnet list result by the ID of the + network to which the subnet belongs. + :param filters: additional subnet list filters + """ + if network_id: + filters["network_id"] = network_id + return self.client.list_subnets(**filters)["subnets"] + + @atomic.action_timer("neutron.create_router") + def create_router(self, project_id=_NONE, admin_state_up=_NONE, + description=_NONE, discover_external_gw=False, + external_gateway_info=_NONE, distributed=_NONE, ha=_NONE, + availability_zone_hints=_NONE, service_type_id=_NONE, + flavor_id=_NONE): + """Create router. + + :param project_id: The ID of the project that owns the resource. Only + administrative and users with advsvc role can specify a project ID + other than their own. You cannot change this value through + authorization policies. + :param admin_state_up: The administrative state of the resource, which + is up (true) or down (false). Default is true. + :param description: A human-readable description for the resource. + :param discover_external_gw: Take one of available external networks + and use it as external gateway. The parameter can not be used in + combination of external_gateway_info parameter. + :param external_gateway_info: The external gateway information of + the router. If the router has an external gateway, this would be + a dict with network_id, enable_snat and external_fixed_ips. + :param distributed: true indicates a distributed router. It is + available when dvr extension is enabled. + :param ha: true indicates a highly-available router. It is available + when l3-ha extension is enabled. + :param availability_zone_hints: The availability zone candidates for + the router. It is available when router_availability_zone extension + is enabled. + :param service_type_id: The ID of the service type associated with + the router. + :param flavor_id: The ID of the flavor associated with the router. + """ + + if external_gateway_info is _NONE and discover_external_gw: + for external_network in self.list_networks(router_external=True): + external_gateway_info = {"network_id": external_network["id"]} + if self.supports_extension("ext-gw-mode", silent=True): + external_gateway_info["enable_snat"] = True + break + + body = _clean_dict( + name=self.generate_random_name(), + # tenant_id should work for both new and old neutron instances + tenant_id=project_id, + external_gateway_info=external_gateway_info, + description=description, + distributed=distributed, + ha=ha, + availability_zone_hints=availability_zone_hints, + service_type_id=service_type_id, + flavor_id=flavor_id, + admin_state_up=admin_state_up + ) + + resp = self.client.create_router({"router": body}) + return resp["router"] + + @atomic.action_timer("neutron.show_router") + def get_router(self, router_id, fields=_NONE): + """Get router details + + :param router_id: Router ID + :param fields: The fields that you want the server to return. If no + fields list is specified, the networking API returns all + attributes allowed by the policy settings. By using fields + parameter, the API returns only the requested set of attributes. + """ + body = _clean_dict(fields=fields) + return self.client.show_router(router_id, **body)["router"] + + @atomic.action_timer("neutron.add_interface_router") + def add_interface_to_router(self, router_id, subnet_id=_NONE, + port_id=_NONE): + """Add interface to router. + + :param router_id: The ID of the router. + :param subnet_id: The ID of the subnet. One of subnet_id or port_id + must be specified. + :param port_id: The ID of the port. One of subnet_id or port_id must + be specified. + """ + if (subnet_id and port_id) or (not subnet_id and not port_id): + raise TypeError("One of subnet_id or port_id must be specified " + "while adding interface to router.") + body = _clean_dict(subnet_id=subnet_id, port_id=port_id) + return self.client.add_interface_router(router_id, body) + + @atomic.action_timer("neutron.remove_interface_router") + def remove_interface_from_router(self, router_id, subnet_id=_NONE, + port_id=_NONE): + """Remove interface from router + + :param router_id: The ID of the router. + :param subnet_id: The ID of the subnet. One of subnet_id or port_id + must be specified. + :param port_id: The ID of the port. One of subnet_id or port_id must + be specified. + """ + from neutronclient.common import exceptions as neutron_exceptions + + if (subnet_id and port_id) or (not subnet_id and not port_id): + raise TypeError("One of subnet_id or port_id must be specified " + "to remove interface from router.") + + body = _clean_dict(subnet_id=subnet_id, port_id=port_id) + + try: + self.client.remove_interface_router(router_id, body) + except (neutron_exceptions.BadRequest, + neutron_exceptions.NotFound): + # Some neutron plugins don't use router as + # the device ID. Also, some plugin doesn't allow + # to update the ha router interface as there is + # an internal logic to update the interface/data model + # instead. + LOG.exception("Failed to remove an interface from a router.") + + @atomic.action_timer("neutron.add_gateway_router") + def add_gateway_to_router(self, router_id, network_id, enable_snat=None, + external_fixed_ips=None): + """Adds an external network gateway to the specified router. + + :param router_id: Router ID + :param enable_snat: whether SNAT should occur on the external gateway + or not + """ + gw_info = {"network_id": network_id} + if enable_snat is not None: + if self.supports_extension("ext-gw-mode", silent=True): + gw_info["enable_snat"] = enable_snat + if external_fixed_ips is not None: + gw_info["external_fixed_ips"] = external_fixed_ips + self.client.add_gateway_router(router_id, gw_info) + + @atomic.action_timer("neutron.remove_gateway_router") + def remove_gateway_from_router(self, router_id): + """Removes an external network gateway from the specified router. + + :param router_id: Router ID + """ + self.client.remove_gateway_router(router_id) + + @atomic.action_timer("neutron.update_router") + def update_router(self, router_id, name=_NONE, admin_state_up=_NONE, + description=_NONE, external_gateway_info=_NONE, + distributed=_NONE, ha=_NONE): + """Update router. + + :param router_id: The ID of the router to update. + :param name: Human-readable name of the resource. + :param admin_state_up: The administrative state of the resource, which + is up (true) or down (false). Default is true. + :param description: A human-readable description for the resource. + :param external_gateway_info: The external gateway information of + the router. If the router has an external gateway, this would be + a dict with network_id, enable_snat and external_fixed_ips. + :param distributed: true indicates a distributed router. It is + available when dvr extension is enabled. + :param ha: true indicates a highly-available router. It is available + when l3-ha extension is enabled. + """ + body = _clean_dict( + name=name, + external_gateway_info=external_gateway_info, + description=description, + distributed=distributed, + ha=ha, + admin_state_up=admin_state_up + ) + + if not body: + raise TypeError("No updates for a router.") + + return self.client.update_router(router_id, {"router": body})["router"] + + @atomic.action_timer("neutron.delete_router") + def delete_router(self, router_id): + """Delete router + + :param router_id: Router ID + """ + self.client.delete_router(router_id) + + @staticmethod + def _filter_routers(routers, subnet_ids): + for router in routers: + gtw_info = router["external_gateway_info"] + if gtw_info is None: + continue + if any(fixed_ip["subnet_id"] in subnet_ids + for fixed_ip in gtw_info["external_fixed_ips"]): + yield router + + @atomic.action_timer("neutron.list_routers") + def list_routers(self, subnet_ids=_NONE, **kwargs): + """List routers. + + :param subnet_ids: Filter routers by attached subnet(s). Can be a + string or and an array with strings. + :param kwargs: additional router list filters + """ + routers = self.client.list_routers(**kwargs)["routers"] + if subnet_ids != _NONE: + routers = list(self._filter_routers(routers, + subnet_ids=subnet_ids)) + return routers + + @atomic.action_timer("neutron.create_port") + def create_port(self, network_id, **kwargs): + """Create neutron port. + + :param network_id: neutron network dict + :param kwargs: other optional neutron port creation params + (name is restricted param) + :returns: neutron port dict + """ + kwargs["name"] = self.generate_random_name() + body = _clean_dict( + network_id=network_id, + **kwargs + ) + return self.client.create_port({"port": body})["port"] + + @atomic.action_timer("neutron.show_port") + def get_port(self, port_id, fields=_NONE): + """Get port details + + :param port_id: Port ID + :param fields: The fields that you want the server to return. If no + fields list is specified, the networking API returns all + attributes allowed by the policy settings. By using fields + parameter, the API returns only the requested set of attributes. + """ + body = _clean_dict(fields=fields) + return self.client.show_port(port_id, **body)["port"] + + @atomic.action_timer("neutron.update_port") + def update_port(self, port_id, **kwargs): + """Update neutron port. + + :param port_id: The ID of the port to update. + :param kwargs: other optional neutron port creation params + (name is restricted param) + :returns: neutron port dict + """ + body = _clean_dict(**kwargs) + if not body: + raise TypeError("No updates for a port.") + return self.client.update_port(port_id, {"port": body})["port"] + + ROUTER_INTERFACE_OWNERS = ("network:router_interface", + "network:router_interface_distributed", + "network:ha_router_replicated_interface") + + ROUTER_GATEWAY_OWNER = "network:router_gateway" + + @atomic.action_timer("neutron.delete_port") + def delete_port(self, port): + """Delete port. + + :param port: Port ID or object + :returns bool: False if neutron returns NotFound error on port delete + """ + + from neutronclient.common import exceptions as neutron_exceptions + + if not isinstance(port, dict): + port = {"id": port, "device_owner": False} + + if (port["device_owner"] in self.ROUTER_INTERFACE_OWNERS + or port["device_owner"] == self.ROUTER_GATEWAY_OWNER): + + if port["device_owner"] == self.ROUTER_GATEWAY_OWNER: + self.remove_gateway_from_router(port["device_id"]) + + self.remove_interface_from_router( + router_id=port["device_id"], port_id=port["id"]) + else: + try: + self.client.delete_port(port["id"]) + except neutron_exceptions.PortNotFoundClient: + # port is auto-removed + pass + + @atomic.action_timer("neutron.list_ports") + def list_ports(self, network_id=_NONE, device_id=_NONE, device_owner=_NONE, + status=_NONE, **kwargs): + """List ports. + + :param network_id: Filter the list result by the ID of the attached + network. + :param device_id: Filter the port list result by the ID of the device + that uses this port. For example, a server instance or a logical + router. + :param device_owner: Filter the port result list by the entity type + that uses this port. For example, compute:nova (server instance), + network:dhcp (DHCP agent) or network:router_interface + (router interface). + :param status: Filter the port list result by the port status. + Values are ACTIVE, DOWN, BUILD and ERROR. + :param kwargs: additional port list filters + """ + filters = _clean_dict( + network_id=network_id, + device_id=device_id, + device_owner=device_owner, + status=status, + **kwargs + ) + return self.client.list_ports(**filters)["ports"] + + @atomic.action_timer("neutron.create_floating_ip") + def create_floatingip(self, floating_network=None, project_id=_NONE, + fixed_ip_address=_NONE, floating_ip_address=_NONE, + port_id=_NONE, subnet_id=_NONE, dns_domain=_NONE, + dns_name=_NONE): + """Create floating IP with floating_network. + + :param floating_network: external network associated with floating IP. + :param project_id: The ID of the project. + :param fixed_ip_address: The fixed IP address that is associated with + the floating IP. If an internal port has multiple associated IP + addresses, the service chooses the first IP address unless you + explicitly define a fixed IP address in the fixed_ip_address + parameter. + :param floating_ip_address: The floating IP address. Default policy + settings enable only administrative users to set floating IP + addresses and some non-administrative users might require a + floating IP address. If you do not specify a floating IP address + in the request, the operation automatically allocates one. + :param port_id: The ID of a port associated with the floating IP. + To associate the floating IP with a fixed IP at creation time, + you must specify the identifier of the internal port. + :param subnet_id: The subnet ID on which you want to create the + floating IP. + :param dns_domain: A valid DNS domain. + :param dns_name: A valid DNS name. + """ + + from neutronclient.common import exceptions as neutron_exceptions + + if isinstance(floating_network, dict): + net_id = floating_network["id"] + elif floating_network: + network = self.find_network(floating_network) + if not network.get("router:external", False): + raise exceptions.NotFoundException( + f"Network '{network['name']} (id={network['id']})' is not " + f"external.") + net_id = network["id"] + else: + ext_networks = self.list_networks(router_external=True) + if not ext_networks: + raise exceptions.NotFoundException( + "Failed to allocate floating IP since no external " + "networks found.") + net_id = ext_networks[0]["id"] + + description = None + if not CONF.openstack.pre_newton_neutron: + description = self.generate_random_name() + + body = _clean_dict( + tenant_id=project_id, + description=description, + floating_network_id=net_id, + fixed_ip_address=fixed_ip_address, + floating_ip_address=floating_ip_address, + port_id=port_id, + subnet_id=subnet_id, + dns_domain=dns_domain, + dns_name=dns_name + ) + + try: + resp = self.client.create_floatingip({"floatingip": body}) + return resp["floatingip"] + except neutron_exceptions.BadRequest as e: + error = "%s" % e + if "Unrecognized attribute" in error and "'description'" in error: + LOG.info("It looks like you have Neutron API of pre-Newton " + "OpenStack release. Setting " + "openstack.pre_newton_neutron option via Rally " + "configuration should fix an issue.") + raise + + @atomic.action_timer("neutron.show_floating_ip") + def get_floatingip(self, floatingip_id, fields=_NONE): + """Get floating IP details + + :param floatingip_id: Floating IP ID + :param fields: The fields that you want the server to return. If no + fields list is specified, the networking API returns all + attributes allowed by the policy settings. By using fields + parameter, the API returns only the requested set of attributes. + """ + body = _clean_dict(fields=fields) + resp = self.client.show_floatingip(floatingip_id, **body) + return resp["floatingip"] + + @atomic.action_timer("neutron.update_floating_ip") + def update_floatingip(self, floating_ip_id, fixed_ip_address=_NONE, + port_id=_NONE, description=_NONE): + """Update floating IP. + + :param floating_ip_id: The ID of the floating IP to update. + :param fixed_ip_address: The fixed IP address that is associated with + the floating IP. If an internal port has multiple associated IP + addresses, the service chooses the first IP address unless you + explicitly define a fixed IP address in the fixed_ip_address + parameter. + :param port_id: The ID of a port associated with the floating IP. + To associate the floating IP with a fixed IP at creation time, + you must specify the identifier of the internal port. + :param description: A human-readable description for the resource. + Default is an empty string. + """ + + body = _clean_dict( + description=description, + fixed_ip_address=fixed_ip_address, + port_id=port_id + ) + + if not body: + raise TypeError("No updates for a floating ip.") + + return self.client.update_floatingip( + floating_ip_id, {"floatingip": body})["floatingip"] + + @atomic.action_timer("neutron.delete_floating_ip") + def delete_floatingip(self, floatingip_id): + """Delete floating IP. + + :param floatingip_id: floating IP id + """ + self.client.delete_floatingip(floatingip_id) + + @atomic.action_timer("neutron.associate_floating_ip") + def associate_floatingip(self, port_id=None, device_id=None, + floatingip_id=None, floating_ip_address=None, + fixed_ip_address=None): + """Add floating IP to an instance + + :param port_id: ID of the port to associate floating IP with + :param device_id: ID of the device to find port to use + :param floatingip_id: ID of the floating IP + :param floating_ip_address: IP address to find floating IP to use + :param fixed_ip_address: The fixed IP address to associate with the + floating ip + """ + if (device_id is None and port_id is None) or (device_id and port_id): + raise TypeError("One of device_id or port_id must be specified.") + + if ((floating_ip_address is None and floatingip_id is None) + or (floating_ip_address and floatingip_id)): + raise TypeError("One of floating_ip_address or floatingip_id " + "must be specified.") + + if port_id is None: + ports = self.list_ports(device_id=device_id) + if not ports: + raise exceptions.GetResourceFailure( + resource="port", + err=f"device '{device_id}' have no ports associated.") + port_id = ports[0]["id"] + + if floatingip_id is None: + filtered_fips = self.list_floatingips( + floating_ip_address=floating_ip_address) + if not filtered_fips: + raise exceptions.GetResourceFailure( + resource="floating ip", + err=f"There is no floating ip with '{floating_ip_address}'" + f" address.") + + floatingip_id = filtered_fips[0]["id"] + + additional = {} + if fixed_ip_address: + additional["fixed_ip_address"] = fixed_ip_address + return self.update_floatingip(floatingip_id, port_id=port_id, + **additional) + + @atomic.action_timer("neutron.dissociate_floating_ip") + def dissociate_floatingip(self, floatingip_id=None, + floating_ip_address=None): + """Remove floating IP from an instance + + :param floatingip_id: ID of the floating IP + :param floating_ip_address: IP address to find floating IP to use + """ + if ((floating_ip_address is None and floatingip_id is None) + or (floating_ip_address and floatingip_id)): + raise TypeError("One of floating_ip_address or floatingip_id " + "must be specified.") + + if floatingip_id is None: + filtered_fips = self.list_floatingips( + floating_ip_address=floating_ip_address) + if not filtered_fips: + raise exceptions.GetResourceFailure( + resource="floating ip", + err=f"There is no floating ip with '{floating_ip_address}'" + f" address.") + + floatingip_id = filtered_fips[0]["id"] + + return self.update_floatingip(floatingip_id, port_id=None) + + @atomic.action_timer("neutron.list_floating_ips") + def list_floatingips(self, router_id=_NONE, port_id=_NONE, status=_NONE, + description=_NONE, floating_network_id=_NONE, + floating_ip_address=_NONE, fixed_ip_address=_NONE, + **kwargs): + """List floating IPs. + + :param router_id: Filter the floating IP list result by the ID of the + router for the floating IP. + :param port_id: Filter the floating IP list result by the ID of a port + associated with the floating IP. + :param status: Filter the floating IP list result by the status of the + floating IP. Values are ACTIVE, DOWN and ERROR. + :param description: Filter the list result by the human-readable + description of the resource. (available only for OpenStack Newton+) + :param floating_network_id: Filter the floating IP list result by the + ID of the network associated with the floating IP. + :param fixed_ip_address: Filter the floating IP list result by the + fixed IP address that is associated with the floating IP address. + :param floating_ip_address: Filter the floating IP list result by the + floating IP address. + :param kwargs: additional floating IP list filters + """ + filters = _clean_dict( + router_id=router_id, + port_id=port_id, + status=status, + description=description, + floating_network_id=floating_network_id, + floating_ip_address=floating_ip_address, + fixed_ip_address=fixed_ip_address, + **kwargs + ) + resp = self.client.list_floatingips(**filters) + return resp["floatingips"] + + @atomic.action_timer("neutron.create_security_group") + def create_security_group(self, name=None, project_id=_NONE, + description=_NONE, stateful=_NONE): + """Create a security group + + :param name: Human-readable name of the resource. + :param project_id: The ID of the project. + :param description: A human-readable description for the resource. + Default is an empty string. + :param stateful: Indicates if the security group is stateful or + stateless. + """ + body = _clean_dict( + name=name or self.generate_random_name(), + tenant_id=project_id, + description=description, + stateful=stateful + ) + resp = self.client.create_security_group({"security_group": body}) + return resp["security_group"] + + @atomic.action_timer("neutron.show_security_group") + def get_security_group(self, security_group_id, fields=_NONE): + """Get security group + + :param security_group_id: Security group ID + :param fields: The fields that you want the server to return. If no + fields list is specified, the networking API returns all + attributes allowed by the policy settings. By using fields + parameter, the API returns only the requested set of attributes. + """ + body = _clean_dict(fields=fields) + resp = self.client.show_security_group(security_group_id, **body) + return resp["security_group"] + + @atomic.action_timer("neutron.update_security_group") + def update_security_group(self, security_group_id, name=_NONE, + description=_NONE, stateful=_NONE): + """Update a security group + + :param security_group_id: Security group ID + :param name: Human-readable name of the resource. + :param description: A human-readable description for the resource. + Default is an empty string. + :param stateful: Indicates if the security group is stateful or + stateless. + """ + body = _clean_dict( + name=name, + description=description, + stateful=stateful + ) + if not body: + raise TypeError("No updates for a security group.") + + resp = self.client.update_security_group(security_group_id, + {"security_group": body}) + return resp["security_group"] + + @atomic.action_timer("neutron.delete_security_group") + def delete_security_group(self, security_group_id): + """Delete security group. + + :param security_group_id: Security group ID + """ + return self.client.delete_security_group(security_group_id) + + @atomic.action_timer("neutron.list_security_groups") + def list_security_groups(self, name=_NONE, **kwargs): + """List security groups. + + :param name: Filter the list result by the human-readable name of the + resource. + :param kwargs: additional security group list filters + """ + if name: + kwargs["name"] = name + resp = self.client.list_security_groups(**kwargs) + return resp["security_groups"] + + @atomic.action_timer("neutron.create_security_group_rule") + def create_security_group_rule(self, + security_group_id, + direction="ingress", + protocol="tcp", + ethertype=_NONE, + port_range_min=_NONE, + port_range_max=_NONE, + remote_ip_prefix=_NONE, + description=_NONE): + """Create security group rule. + + :param security_group_id: The security group ID to associate with this + security group rule. + :param direction: Ingress or egress, which is the direction in which + the security group rule is applied. + :param protocol: The IP protocol can be represented by a string, an + integer, or null. Valid string or integer values are any or 0, ah + or 51, dccp or 33, egp or 8, esp or 50, gre or 47, icmp or 1, + icmpv6 or 58, igmp or 2, ipip or 4, ipv6-encap or 41, + ipv6-frag or 44, ipv6-icmp or 58, ipv6-nonxt or 59, + ipv6-opts or 60, ipv6-route or 43, ospf or 89, pgm or 113, + rsvp or 46, sctp or 132, tcp or 6, udp or 17, udplite or 136, + vrrp or 112. Additionally, any integer value between [0-255] is + also valid. The string any (or integer 0) means all IP protocols. + See the constants in neutron_lib.constants for the most + up-to-date list of supported strings. + :param ethertype: Must be IPv4 or IPv6, and addresses represented in + CIDR must match the ingress or egress rules. + :param port_range_min: The minimum port number in the range that is + matched by the security group rule. If the protocol is TCP, UDP, + DCCP, SCTP or UDP-Lite this value must be less than or equal to + the port_range_max attribute value. If the protocol is ICMP, this + value must be an ICMP type. + :param port_range_max: The maximum port number in the range that is + matched by the security group rule. If the protocol is TCP, UDP, + DCCP, SCTP or UDP-Lite this value must be greater than or equal to + the port_range_min attribute value. If the protocol is ICMP, this + value must be an ICMP code. + :param remote_ip_prefix: The remote IP prefix that is matched by this + security group rule. + :param description: A human-readable description for the resource. + Default is an empty string. + """ + body = _clean_dict( + security_group_id=security_group_id, + direction=direction, + protocol=protocol, + ethertype=ethertype, + port_range_min=port_range_min, + port_range_max=port_range_max, + remote_ip_prefix=remote_ip_prefix, + description=description + ) + return self.client.create_security_group_rule( + {"security_group_rule": body})["security_group_rule"] + + @atomic.action_timer("neutron.show_security_group_rule") + def get_security_group_rule(self, security_group_rule_id, verbose=_NONE, + fields=_NONE): + """Get security group details + + :param security_group_rule_id: Security group rule ID + :param verbose: Show detailed information. + :param fields: The fields that you want the server to return. If no + fields list is specified, the networking API returns all + attributes allowed by the policy settings. By using fields + parameter, the API returns only the requested set of attributes. + """ + body = _clean_dict(verbose=verbose, fields=fields) + resp = self.client.show_security_group_rule( + security_group_rule_id, **body) + return resp["security_group_rule"] + + @atomic.action_timer("neutron.delete_security_group_rule") + def delete_security_group_rule(self, security_group_rule_id): + """Delete a given security group rule. + + :param security_group_rule_id: Security group rule ID + """ + self.client.delete_security_group_rule( + security_group_rule_id) + + @atomic.action_timer("neutron.list_security_group_rules") + def list_security_group_rules( + self, security_group_id=_NONE, protocol=_NONE, direction=_NONE, + port_range_min=_NONE, port_range_max=_NONE, description=_NONE, + **kwargs): + """List all security group rules. + + :param security_group_id: Filter the security group rule list result + by the ID of the security group that associates with this security + group rule. + :param protocol: Filter the security group rule list result by the IP + protocol. + :param direction: Filter the security group rule list result by the + direction in which the security group rule is applied, which is + ingress or egress. + :param port_range_min: Filter the security group rule list result by + the minimum port number in the range that is matched by the + security group rule. + :param port_range_max: Filter the security group rule list result by + the maximum port number in the range that is matched by the + security group rule. + :param description: Filter the list result by the human-readable + description of the resource. + :param kwargs: additional security group rule list filters + :return: list of security group rules + """ + filters = _clean_dict( + security_group_id=security_group_id, + protocol=protocol, + direction=direction, + port_range_min=port_range_min, + port_range_max=port_range_max, + description=description, + **kwargs + ) + resp = self.client.list_security_group_rules(**filters) + return resp["security_group_rules"] + + @atomic.action_timer("neutron.list_agents") + def list_agents(self, **kwargs): + """Fetches agents. + + :param kwargs: filters + :returns: user agents list + """ + return self.client.list_agents(**kwargs)["agents"] + + @atomic.action_timer("neutron.list_extension") + def list_extensions(self): + """List neutron extensions.""" + return self.client.list_extensions()["extensions"] + + @property + def cached_supported_extensions(self): + """Return cached list of extension if exist or fetch it if is missed""" + if self._cached_supported_extensions is None: + self._cached_supported_extensions = self.list_extensions() + return self._cached_supported_extensions + + def supports_extension(self, extension, silent=False): + """Check whether a neutron extension is supported. + + :param extension: Extension to check + :param silent: Return boolean result of the search instead of raising + an exception + """ + exist = any(ext.get("alias") == extension + for ext in self.cached_supported_extensions) + if not silent and not exist: + raise exceptions.NotFoundException( + message=f"Neutron driver does not support {extension}") + + return exist diff --git a/rally_openstack/common/wrappers/network.py b/rally_openstack/common/wrappers/network.py index f2f7d8ee..682385a3 100644 --- a/rally_openstack/common/wrappers/network.py +++ b/rally_openstack/common/wrappers/network.py @@ -14,26 +14,22 @@ # under the License. import abc -import itertools -import netaddr from neutronclient.common import exceptions as neutron_exceptions + from rally.common import cfg from rally.common import logging -from rally.common import utils from rally import exceptions from rally_openstack.common import consts +from rally_openstack.common.services.network import net_utils +from rally_openstack.common.services.network import neutron LOG = logging.getLogger(__name__) CONF = cfg.CONF -cidr_incr = utils.RAMInt() -ipv6_cidr_incr = utils.RAMInt() - - def generate_cidr(start_cidr="10.2.0.0/24"): """Generate next CIDR for network or subnet, without IP overlapping. @@ -44,11 +40,7 @@ def generate_cidr(start_cidr="10.2.0.0/24"): :param start_cidr: start CIDR str :returns: next available CIDR str """ - if netaddr.IPNetwork(start_cidr).version == 4: - cidr = str(netaddr.IPNetwork(start_cidr).next(next(cidr_incr))) - else: - cidr = str(netaddr.IPNetwork(start_cidr).next(next(ipv6_cidr_incr))) - LOG.debug("CIDR generated: %s" % cidr) + ip_version, cidr = net_utils.generate_cidr(start_cidr=start_cidr) return cidr @@ -69,7 +61,7 @@ class NetworkWrapper(object, metaclass=abc.ABCMeta): START_IPV6_CIDR = "dead:beaf::/64" SERVICE_IMPL = None - def __init__(self, clients, owner, config=None): + def __init__(self, clients, owner, config=None, atomics=None): """Returns available network wrapper instance. :param clients: rally.plugins.openstack.osclients.Clients instance @@ -124,10 +116,22 @@ class NeutronWrapper(NetworkWrapper): LB_METHOD = "ROUND_ROBIN" LB_PROTOCOL = "HTTP" + def __init__(self, *args, **kwargs): + super(NeutronWrapper, self).__init__(*args, **kwargs) + + class _SingleClientWrapper(object): + def neutron(_self): + return self.client + + self.neutron = neutron.NeutronService( + clients=_SingleClientWrapper(), + name_generator=self.owner.generate_random_name, + atomic_inst=getattr(self.owner, "_atomic_actions", []) + ) + @property def external_networks(self): - return self.client.list_networks(**{ - "router:external": True})["networks"] + return self.neutron.list_networks(router_external=True) @property def ext_gw_mode_enabled(self): @@ -135,25 +139,30 @@ class NeutronWrapper(NetworkWrapper): Without this extension, we can't pass the enable_snat parameter. """ - return any(e["alias"] == "ext-gw-mode" - for e in self.client.list_extensions()["extensions"]) + return self.neutron.supports_extension("ext-gw-mode", silent=True) def get_network(self, net_id=None, name=None): net = None try: if net_id: - net = self.client.show_network(net_id)["network"] + net = self.neutron.get_network(net_id) else: - for net in self.client.list_networks(name=name)["networks"]: - break + networks = self.neutron.list_networks(name=name) + if networks: + net = networks[0] + except neutron_exceptions.NeutronClientException: + pass + + if net: return {"id": net["id"], "name": net["name"], - "tenant_id": net["tenant_id"], + "tenant_id": net.get("tenant_id", + net.get("project_id", None)), "status": net["status"], - "external": net["router:external"], - "subnets": net["subnets"], + "external": net.get("router:external", False), + "subnets": net.get("subnets", []), "router_id": None} - except (TypeError, neutron_exceptions.NeutronClientException): + else: raise NetworkWrapperException( "Network not found: %s" % (name or net_id)) @@ -164,14 +173,12 @@ class NeutronWrapper(NetworkWrapper): :param **kwargs: POST /v2.0/routers request options :returns: neutron router dict """ - kwargs["name"] = self.owner.generate_random_name() + kwargs.pop("name", None) + if "tenant_id" in kwargs and "project_id" not in kwargs: + kwargs["project_id"] = kwargs.pop("tenant_id") - if external and "external_gateway_info" not in kwargs: - for net in self.external_networks: - kwargs["external_gateway_info"] = {"network_id": net["id"]} - if self.ext_gw_mode_enabled: - kwargs["external_gateway_info"]["enable_snat"] = True - return self.client.create_router({"router": kwargs})["router"] + return self.neutron.create_router( + discover_external_gw=external, **kwargs) def create_v1_pool(self, tenant_id, subnet_id, **kwargs): """Create LB Pool (v1). @@ -194,9 +201,10 @@ class NeutronWrapper(NetworkWrapper): def _generate_cidr(self, ip_version=4): # TODO(amaretskiy): Generate CIDRs unique for network, not cluster - return generate_cidr( + ip_version, cidr = net_utils.generate_cidr( start_cidr=self.start_cidr if ip_version == 4 else self.start_ipv6_cidr) + return cidr def _create_network_infrastructure(self, tenant_id, **kwargs): """Create network. @@ -218,51 +226,34 @@ class NeutronWrapper(NetworkWrapper): See above for recognized keyword args. :returns: dict, network data """ - network_args = {"network": kwargs.get("network_create_args", {})} - network_args["network"].update({ - "tenant_id": tenant_id, - "name": self.owner.generate_random_name()}) - network = self.client.create_network(network_args)["network"] + network_args = dict(kwargs.get("network_create_args", {})) + network_args["project_id"] = tenant_id - router = None router_args = dict(kwargs.get("router_create_args", {})) add_router = kwargs.get("add_router", False) - if router_args or add_router: - router_args["external"] = ( - router_args.get("external", False) or add_router) - router_args["tenant_id"] = tenant_id - router = self.create_router(**router_args) + if not (router_args or add_router): + router_args = None + else: + router_args["project_id"] = tenant_id + router_args["discover_external_gw"] = router_args.pop( + "external", False) or add_router + subnet_create_args = {"project_id": tenant_id} + if "dns_nameservers" in kwargs: + subnet_create_args["dns_nameservers"] = kwargs["dns_nameservers"] - dualstack = kwargs.get("dualstack", False) - - subnets = [] - subnets_num = kwargs.get("subnets_num", 0) - ip_versions = itertools.cycle( - [self.SUBNET_IP_VERSION, self.SUBNET_IPV6_VERSION] - if dualstack else [self.SUBNET_IP_VERSION]) - for i in range(subnets_num): - ip_version = next(ip_versions) - subnet_args = { - "subnet": { - "tenant_id": tenant_id, - "network_id": network["id"], - "name": self.owner.generate_random_name(), - "ip_version": ip_version, - "cidr": self._generate_cidr(ip_version), - "enable_dhcp": True, - "dns_nameservers": ( - kwargs.get("dns_nameservers", ["8.8.8.8", "8.8.4.4"]) - if ip_version == 4 - else kwargs.get("dns_nameservers", - ["dead:beaf::1", "dead:beaf::2"])) - } - } - subnet = self.client.create_subnet(subnet_args)["subnet"] - subnets.append(subnet) - - if router: - self.client.add_interface_router(router["id"], - {"subnet_id": subnet["id"]}) + net_topo = self.neutron.create_network_topology( + network_create_args=network_args, + router_create_args=router_args, + subnet_create_args=subnet_create_args, + subnets_dualstack=kwargs.get("dualstack", False), + subnets_count=kwargs.get("subnets_num", 0) + ) + network = net_topo["network"] + subnets = net_topo["subnets"] + if net_topo["routers"]: + router = net_topo["routers"][0] + else: + router = None return { "network": { @@ -309,50 +300,33 @@ class NeutronWrapper(NetworkWrapper): self.client.delete_pool(pool_id) def delete_network(self, network): + """Delete network - if network["router_id"]: - self.client.remove_gateway_router(network["router_id"]) + :param network: network object returned by create_network method + """ - for port in self.client.list_ports(network_id=network["id"])["ports"]: - if port["device_owner"] in ( - "network:router_interface", - "network:router_interface_distributed", - "network:ha_router_replicated_interface", - "network:router_gateway"): - try: - self.client.remove_interface_router( - port["device_id"], {"port_id": port["id"]}) - except (neutron_exceptions.BadRequest, - neutron_exceptions.NotFound): - # Some neutron plugins don't use router as - # the device ID. Also, some plugin doesn't allow - # to update the ha rotuer interface as there is - # an internal logic to update the interface/data model - # instead. - pass - else: - try: - self.client.delete_port(port["id"]) - except neutron_exceptions.PortNotFoundClient: - # port is auto-removed - pass + router = {"id": network["router_id"]} if network["router_id"] else None + # delete_network_topology uses only IDs, but let's transmit as much as + # possible info + topo = { + "network": { + "id": network["id"], + "name": network["name"], + "status": network["status"], + "subnets": network["subnets"], + "router:external": network["external"] + }, + "subnets": [{"id": s} for s in network["subnets"]], + "routers": [router] if router else [] + } - for subnet in self.client.list_subnets( - network_id=network["id"])["subnets"]: - self._delete_subnet(subnet["id"]) - - responce = self.client.delete_network(network["id"]) - - if network["router_id"]: - self.client.delete_router(network["router_id"]) - - return responce + self.neutron.delete_network_topology(topo) def _delete_subnet(self, subnet_id): - self.client.delete_subnet(subnet_id) + self.neutron.delete_subnet(subnet_id) def list_networks(self): - return self.client.list_networks()["networks"] + return self.neutron.list_networks() def create_port(self, network_id, **kwargs): """Create neutron port. @@ -361,9 +335,7 @@ class NeutronWrapper(NetworkWrapper): :param **kwargs: POST /v2.0/ports request options :returns: neutron port dict """ - kwargs["network_id"] = network_id - kwargs["name"] = self.owner.generate_random_name() - return self.client.create_port({"port": kwargs})["port"] + return self.neutron.create_port(network_id=network_id, **kwargs) def create_floating_ip(self, ext_network=None, tenant_id=None, port_id=None, **kwargs): @@ -377,34 +349,13 @@ class NeutronWrapper(NetworkWrapper): """ if not tenant_id: raise ValueError("Missed tenant_id") - - if type(ext_network) is dict: - net_id = ext_network["id"] - elif ext_network: - ext_net = self.get_network(name=ext_network) - if not ext_net["external"]: - raise NetworkWrapperException("Network is not external: %s" - % ext_network) - net_id = ext_net["id"] - else: - ext_networks = self.external_networks - if not ext_networks: - raise NetworkWrapperException( - "Failed to allocate floating IP: " - "no external networks found") - net_id = ext_networks[0]["id"] - - kwargs = {"floatingip": {"floating_network_id": net_id, - "tenant_id": tenant_id}} - - if not CONF.openstack.pre_newton_neutron: - descr = self.owner.generate_random_name() - kwargs["floatingip"]["description"] = descr - - if port_id: - kwargs["floatingip"]["port_id"] = port_id - - fip = self.client.create_floatingip(kwargs)["floatingip"] + try: + fip = self.neutron.create_floatingip( + floating_network=ext_network, project_id=tenant_id, + port_id=port_id) + except (exceptions.NotFoundException, + exceptions.GetResourceFailure) as e: + raise NetworkWrapperException(str(e)) from None return {"id": fip["id"], "ip": fip["floating_ip_address"]} def delete_floating_ip(self, fip_id, **kwargs): @@ -413,7 +364,7 @@ class NeutronWrapper(NetworkWrapper): :param fip_id: int floating IP id :param **kwargs: for compatibility, not used here """ - self.client.delete_floatingip(fip_id) + self.neutron.delete_floatingip(fip_id) def supports_extension(self, extension): """Check whether a neutron extension is supported @@ -422,11 +373,12 @@ class NeutronWrapper(NetworkWrapper): :returns: result tuple :rtype: (bool, string) """ - extensions = self.client.list_extensions().get("extensions", []) - if any(ext.get("alias") == extension for ext in extensions): - return True, "" + try: + self.neutron.supports_extension(extension) + except exceptions.NotFoundException as e: + return False, str(e) - return False, "Neutron driver does not support %s" % extension + return True, "" def wrap(clients, owner, config=None): diff --git a/rally_openstack/task/cleanup/resources.py b/rally_openstack/task/cleanup/resources.py index 68f42fac..9723a285 100644 --- a/rally_openstack/task/cleanup/resources.py +++ b/rally_openstack/task/cleanup/resources.py @@ -20,6 +20,7 @@ from rally.task import utils as task_utils from rally_openstack.common.services.identity import identity from rally_openstack.common.services.image import glance_v2 from rally_openstack.common.services.image import image +from rally_openstack.common.services.network import neutron from rally_openstack.task.cleanup import base @@ -201,13 +202,11 @@ _neutron_order = get_order(300) @base.resource(service=None, resource=None, admin_required=True) class NeutronMixin(SynchronizedDeletion, base.ResourceManager): - # Neutron has the best client ever, so we need to override everything - def supports_extension(self, extension): - exts = self._manager().list_extensions().get("extensions", []) - if any(ext.get("alias") == extension for ext in exts): - return True - return False + @property + def _neutron(self): + return neutron.NeutronService( + self._admin_required and self.admin or self.user) def _manager(self): client = self._admin_required and self.admin or self.user @@ -220,7 +219,10 @@ class NeutronMixin(SynchronizedDeletion, base.ResourceManager): return self.raw_resource["name"] def delete(self): - delete_method = getattr(self._manager(), "delete_%s" % self._resource) + key = "delete_%s" % self._resource + delete_method = getattr( + self._neutron, key, getattr(self._manager(), key) + ) delete_method(self.id()) @property @@ -242,7 +244,7 @@ class NeutronMixin(SynchronizedDeletion, base.ResourceManager): class NeutronLbaasV1Mixin(NeutronMixin): def list(self): - if self.supports_extension("lbaas"): + if self._neutron.supports_extension("lbaas", silent=True): return super(NeutronLbaasV1Mixin, self).list() return [] @@ -268,7 +270,7 @@ class NeutronV1Pool(NeutronLbaasV1Mixin): class NeutronLbaasV2Mixin(NeutronMixin): def list(self): - if self.supports_extension("lbaasv2"): + if self._neutron.supports_extension("lbaasv2", silent=True): return super(NeutronLbaasV2Mixin, self).list() return [] @@ -373,7 +375,7 @@ class OctaviaHealthMonitors(OctaviaMixIn): admin_required=True, perform_for_admin_only=True) class NeutronBgpvpn(NeutronMixin): def list(self): - if self.supports_extension("bgpvpn"): + if self._neutron.supports_extension("bgpvpn", silent=True): return self._manager().list_bgpvpns()["bgpvpns"] return [] @@ -414,20 +416,23 @@ class NeutronPort(NeutronMixin): # NOTE(andreykurilin): port is the kind of resource that can be created # automatically. In this case it doesn't have name field which matches # our resource name templates. - ROUTER_INTERFACE_OWNERS = ("network:router_interface", - "network:router_interface_distributed", - "network:ha_router_replicated_interface") - - ROUTER_GATEWAY_OWNER = "network:router_gateway" def __init__(self, *args, **kwargs): super(NeutronPort, self).__init__(*args, **kwargs) self._cache = {} + @property + def ROUTER_INTERFACE_OWNERS(self): + return self._neutron.ROUTER_INTERFACE_OWNERS + + @property + def ROUTER_GATEWAY_OWNER(self): + return self._neutron.ROUTER_GATEWAY_OWNER + def _get_resources(self, resource): if resource not in self._cache: - resources = getattr(self._manager(), "list_%s" % resource)() - self._cache[resource] = [r for r in resources[resource] + resources = getattr(self._neutron, "list_%s" % resource)() + self._cache[resource] = [r for r in resources if r["tenant_id"] == self.tenant_uuid] return self._cache[resource] @@ -455,23 +460,11 @@ class NeutronPort(NeutronMixin): self.raw_resource.get("name", "")) def delete(self): - device_owner = self.raw_resource["device_owner"] - if (device_owner in self.ROUTER_INTERFACE_OWNERS - or device_owner == self.ROUTER_GATEWAY_OWNER): - if device_owner == self.ROUTER_GATEWAY_OWNER: - self._manager().remove_gateway_router( - self.raw_resource["device_id"]) - - self._manager().remove_interface_router( - self.raw_resource["device_id"], {"port_id": self.id()}) - else: - from neutronclient.common import exceptions as neutron_exceptions - try: - self._manager().delete_port(self.id()) - except neutron_exceptions.PortNotFoundClient: - # Port can be already auto-deleted, skip silently - LOG.debug("Port %s was not deleted. Skip silently because " - "port can be already auto-deleted." % self.id()) + found = self._neutron.delete_port(self.raw_resource) + if not found: + # Port can be already auto-deleted, skip silently + LOG.debug(f"Port {self.id()} was not deleted. Skip silently " + f"because port can be already auto-deleted.") @base.resource("neutron", "subnet", order=next(_neutron_order), diff --git a/rally_openstack/task/scenarios/neutron/utils.py b/rally_openstack/task/scenarios/neutron/utils.py index 13b70692..7941355a 100644 --- a/rally_openstack/task/scenarios/neutron/utils.py +++ b/rally_openstack/task/scenarios/neutron/utils.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import netaddr import random from rally.common import cfg @@ -22,7 +21,7 @@ from rally import exceptions from rally.task import atomic from rally.task import utils -from rally_openstack.common.wrappers import network as network_wrapper +from rally_openstack.common.services.network import neutron from rally_openstack.task import scenario @@ -32,7 +31,20 @@ CONF = cfg.CONF LOG = logging.getLogger(__name__) -class NeutronScenario(scenario.OpenStackScenario): +class NeutronBaseScenario(scenario.OpenStackScenario): + """Base class for Neutron scenarios with basic atomic actions.""" + + def __init__(self, *args, **kwargs): + super(NeutronBaseScenario, self).__init__(*args, **kwargs) + if hasattr(self, "_clients"): + self.neutron = neutron.NeutronService( + clients=self._clients, + name_generator=self.generate_random_name, + atomic_inst=self.atomic_actions() + ) + + +class NeutronScenario(NeutronBaseScenario): """Base class for Neutron scenarios with basic atomic actions.""" # TODO(rkiran): modify in case LBaaS-v2 requires @@ -51,12 +63,11 @@ class NeutronScenario(scenario.OpenStackScenario): :param kwargs: dict, network options :returns: str, Neutron network-id """ - networks = self._list_networks() - for net in networks: - if (net["name"] == network) or (net["id"] == network): - return net["id"] - raise exceptions.NotFoundException( - message="Network %s not found." % network) + try: + return self.neutron.find_network(network)["id"] + except exceptions.GetResourceFailure: + raise exceptions.NotFoundException( + message="Network %s not found." % network) @property def _ext_gw_mode_enabled(self): @@ -64,39 +75,32 @@ class NeutronScenario(scenario.OpenStackScenario): Without this extension, we can't pass the enable_snat parameter. """ - return any( - e["alias"] == "ext-gw-mode" - for e in self.clients("neutron").list_extensions()["extensions"]) + return self.neutron.supports_extension("ext-gw-mode", silent=True) - @atomic.action_timer("neutron.create_network") def _create_network(self, network_create_args): """Create neutron network. :param network_create_args: dict, POST /v2.0/networks request options :returns: neutron network dict """ - network_create_args["name"] = self.generate_random_name() - return self.clients("neutron").create_network( - {"network": network_create_args}) + network_create_args.pop("name", None) + return {"network": self.neutron.create_network(**network_create_args)} - @atomic.action_timer("neutron.list_networks") def _list_networks(self, **kwargs): """Return user networks list. :param kwargs: network list options """ - return self.clients("neutron").list_networks(**kwargs)["networks"] + return self.neutron.list_networks(**kwargs) - @atomic.action_timer("neutron.list_agents") def _list_agents(self, **kwargs): """Fetches agents. :param kwargs: neutron agent list options :returns: user agents list """ - return self.clients("neutron").list_agents(**kwargs)["agents"] + return self.neutron.list_agents(**kwargs) - @atomic.action_timer("neutron.update_network") def _update_network(self, network, network_update_args): """Update the network. @@ -107,11 +111,9 @@ class NeutronScenario(scenario.OpenStackScenario): :returns: updated neutron network dict """ network_update_args["name"] = self.generate_random_name() - body = {"network": network_update_args} - return self.clients("neutron").update_network( - network["network"]["id"], body) + return {"network": self.neutron.update_network( + network["network"]["id"], **network_update_args)} - @atomic.action_timer("neutron.show_network") def _show_network(self, network, **kwargs): """show network details. @@ -119,18 +121,16 @@ class NeutronScenario(scenario.OpenStackScenario): :param kwargs: dict, POST /v2.0/networks show options :returns: details of the network """ - return self.clients("neutron").show_network( - network["network"]["id"], **kwargs) + network = self.neutron.get_network(network["network"]["id"], **kwargs) + return {"network": network} - @atomic.action_timer("neutron.delete_network") def _delete_network(self, network): """Delete neutron network. :param network: Network object """ - self.clients("neutron").delete_network(network["id"]) + self.neutron.delete_network(network["id"]) - @atomic.action_timer("neutron.create_subnet") def _create_subnet(self, network, subnet_create_args, start_cidr=None): """Create neutron subnet. @@ -138,27 +138,17 @@ class NeutronScenario(scenario.OpenStackScenario): :param subnet_create_args: POST /v2.0/subnets request options :returns: neutron subnet dict """ - network_id = network["network"]["id"] - if not subnet_create_args.get("cidr"): - start_cidr = start_cidr or "10.2.0.0/24" - subnet_create_args["cidr"] = ( - network_wrapper.generate_cidr(start_cidr=start_cidr)) + subnet_create_args.pop("name", None) + subnet_create_args["network_id"] = network["network"]["id"] + subnet_create_args["start_cidr"] = start_cidr - subnet_create_args["network_id"] = network_id - subnet_create_args["name"] = self.generate_random_name() - subnet_create_args["ip_version"] = netaddr.IPNetwork( - subnet_create_args["cidr"]).version + return {"subnet": self.neutron.create_subnet(**subnet_create_args)} - return self.clients("neutron").create_subnet( - {"subnet": subnet_create_args}) - - @atomic.action_timer("neutron.list_subnets") def _list_subnets(self): """Returns user subnetworks list.""" - return self.clients("neutron").list_subnets()["subnets"] + return self.neutron.list_subnets() - @atomic.action_timer("neutron.show_subnet") def _show_subnet(self, subnet, **kwargs): """show subnet details. @@ -166,10 +156,8 @@ class NeutronScenario(scenario.OpenStackScenario): :param kwargs: Optional additional arguments for subnet show :returns: details of the subnet """ - return self.clients("neutron").show_subnet(subnet["subnet"]["id"], - **kwargs) + return {"subnet": self.neutron.get_subnet(subnet["subnet"]["id"])} - @atomic.action_timer("neutron.update_subnet") def _update_subnet(self, subnet, subnet_update_args): """Update the neutron subnet. @@ -180,46 +168,35 @@ class NeutronScenario(scenario.OpenStackScenario): :returns: updated neutron subnet dict """ subnet_update_args["name"] = self.generate_random_name() - body = {"subnet": subnet_update_args} - return self.clients("neutron").update_subnet( - subnet["subnet"]["id"], body) + return {"subnet": self.neutron.update_subnet( + subnet["subnet"]["id"], **subnet_update_args)} - @atomic.action_timer("neutron.delete_subnet") def _delete_subnet(self, subnet): """Delete neutron subnet :param subnet: Subnet object """ - self.clients("neutron").delete_subnet(subnet["subnet"]["id"]) + self.neutron.delete_subnet(subnet["subnet"]["id"]) - @atomic.action_timer("neutron.create_router") def _create_router(self, router_create_args, external_gw=False): """Create neutron router. :param router_create_args: POST /v2.0/routers request options :returns: neutron router dict """ - router_create_args["name"] = self.generate_random_name() + router_create_args.pop("name", None) + if ("tenant_id" in router_create_args + and "project_id" not in router_create_args): + router_create_args["project_id"] = router_create_args.pop( + "tenant_id") - if external_gw: - for network in self._list_networks(): - if network.get("router:external"): - external_network = network - gw_info = {"network_id": external_network["id"]} - if self._ext_gw_mode_enabled: - gw_info["enable_snat"] = True - router_create_args.setdefault("external_gateway_info", - gw_info) + return {"router": self.neutron.create_router( + discover_external_gw=external_gw, **router_create_args)} - return self.clients("neutron").create_router( - {"router": router_create_args}) - - @atomic.action_timer("neutron.list_routers") def _list_routers(self): """Returns user routers list.""" - return self.clients("neutron").list_routers()["routers"] + return self.neutron.list_routers() - @atomic.action_timer("neutron.show_router") def _show_router(self, router, **kwargs): """Show information of a given router. @@ -227,18 +204,16 @@ class NeutronScenario(scenario.OpenStackScenario): :kwargs: dict, POST /v2.0/routers show options :return: details of the router """ - return self.clients("neutron").show_router( - router["router"]["id"], **kwargs) + return {"router": self.neutron.get_router( + router["router"]["id"], **kwargs)} - @atomic.action_timer("neutron.delete_router") def _delete_router(self, router): """Delete neutron router :param router: Router object """ - self.clients("neutron").delete_router(router["router"]["id"]) + self.neutron.delete_router(router["router"]["id"]) - @atomic.action_timer("neutron.update_router") def _update_router(self, router, router_update_args): """Update the neutron router. @@ -249,11 +224,9 @@ class NeutronScenario(scenario.OpenStackScenario): :returns: updated neutron router dict """ router_update_args["name"] = self.generate_random_name() - body = {"router": router_update_args} - return self.clients("neutron").update_router( - router["router"]["id"], body) + return {"router": self.neutron.update_router( + router["router"]["id"], **router_update_args)} - @atomic.action_timer("neutron.create_port") def _create_port(self, network, port_create_args): """Create neutron port. @@ -261,16 +234,13 @@ class NeutronScenario(scenario.OpenStackScenario): :param port_create_args: POST /v2.0/ports request options :returns: neutron port dict """ - port_create_args["network_id"] = network["network"]["id"] - port_create_args["name"] = self.generate_random_name() - return self.clients("neutron").create_port({"port": port_create_args}) + return {"port": self.neutron.create_port( + network_id=network["network"]["id"], **port_create_args)} - @atomic.action_timer("neutron.list_ports") def _list_ports(self): """Return user ports list.""" - return self.clients("neutron").list_ports()["ports"] + return self.neutron.list_ports() - @atomic.action_timer("neutron.show_port") def _show_port(self, port, **params): """Return user port details. @@ -278,9 +248,8 @@ class NeutronScenario(scenario.OpenStackScenario): :param params: neutron port show options :returns: neutron port dict """ - return self.clients("neutron").show_port(port["port"]["id"], **params) + return {"port": self.neutron.get_port(port["port"]["id"], **params)} - @atomic.action_timer("neutron.update_port") def _update_port(self, port, port_update_args): """Update the neutron port. @@ -291,16 +260,15 @@ class NeutronScenario(scenario.OpenStackScenario): :returns: updated neutron port dict """ port_update_args["name"] = self.generate_random_name() - body = {"port": port_update_args} - return self.clients("neutron").update_port(port["port"]["id"], body) + return {"port": self.neutron.update_port(port["port"]["id"], + **port_update_args)} - @atomic.action_timer("neutron.delete_port") def _delete_port(self, port): """Delete neutron port. :param port: Port object """ - self.clients("neutron").delete_port(port["port"]["id"]) + self.neutron.delete_port(port["port"]["id"]) @logging.log_deprecated_args( "network_create_args is deprecated; use the network context instead", @@ -356,10 +324,16 @@ class NeutronScenario(scenario.OpenStackScenario): :parm subnet_cidr_start: str, start value for subnets CIDR :returns: tuple of result network and subnets list """ - network = self._create_network(network_create_args or {}) - subnets = self._create_subnets(network, subnet_create_args, - subnet_cidr_start, subnets_per_network) - return network, subnets + subnet_create_args = dict(subnet_create_args or {}) + subnet_create_args["start_cidr"] = subnet_cidr_start + + net_topo = self.neutron.create_network_topology( + network_create_args=(network_create_args or {}), + subnet_create_args=subnet_create_args, + subnets_count=subnets_per_network + ) + subnets = [{"subnet": s} for s in net_topo["subnets"]] + return {"network": net_topo["network"]}, subnets def _create_network_structure(self, network_create_args=None, subnet_create_args=None, @@ -375,41 +349,39 @@ class NeutronScenario(scenario.OpenStackScenario): :param router_create_args: dict, POST /v2.0/routers request options :returns: tuple of (network, subnets, routers) """ - network = self._create_network(network_create_args or {}) - subnets = self._create_subnets(network, subnet_create_args, - subnet_cidr_start, - subnets_per_network) - routers = [] - for subnet in subnets: - router = self._create_router(router_create_args or {}) - self._add_interface_router(subnet["subnet"], - router["router"]) - routers.append(router) + subnet_create_args = dict(subnet_create_args or {}) + subnet_create_args["start_cidr"] = subnet_cidr_start - return (network, subnets, routers) + net_topo = self.neutron.create_network_topology( + network_create_args=(network_create_args or {}), + router_create_args=(router_create_args or {}), + router_per_subnet=True, + subnet_create_args=subnet_create_args, + subnets_count=subnets_per_network + ) + return ({"network": net_topo["network"]}, + [{"subnet": s} for s in net_topo["subnets"]], + [{"router": r} for r in net_topo["routers"]]) - @atomic.action_timer("neutron.add_interface_router") def _add_interface_router(self, subnet, router): """Connect subnet to router. :param subnet: dict, neutron subnet :param router: dict, neutron router """ - self.clients("neutron").add_interface_router( - router["id"], {"subnet_id": subnet["id"]}) + self.neutron.add_interface_to_router(router_id=router["id"], + subnet_id=subnet["id"]) - @atomic.action_timer("neutron.remove_interface_router") def _remove_interface_router(self, subnet, router): """Remove subnet from router :param subnet: dict, neutron subnet :param router: dict, neutron router """ - self.clients("neutron").remove_interface_router( - router["id"], {"subnet_id": subnet["id"]}) + self.neutron.remove_interface_from_router( + router_id=router["id"], subnet_id=subnet["id"]) - @atomic.action_timer("neutron.add_gateway_router") def _add_gateway_router(self, router, ext_net, enable_snat=None): """Set the external network gateway for a router. @@ -417,21 +389,18 @@ class NeutronScenario(scenario.OpenStackScenario): :param ext_net: external network for the gateway :param enable_snat: True if enable snat, None to avoid update """ - gw_info = {"network_id": ext_net["network"]["id"]} - if enable_snat is not None: - if self._ext_gw_mode_enabled: - gw_info["enable_snat"] = enable_snat - self.clients("neutron").add_gateway_router( - router["router"]["id"], gw_info) + self.neutron.add_gateway_to_router( + router_id=router["router"]["id"], + network_id=ext_net["network"]["id"], + enable_snat=enable_snat + ) - @atomic.action_timer("neutron.remove_gateway_router") def _remove_gateway_router(self, router): """Removes an external network gateway from the specified router. :param router: dict, neutron router """ - self.clients("neutron").remove_gateway_router( - router["router"]["id"]) + self.neutron.remove_gateway_from_router(router["router"]["id"]) @atomic.action_timer("neutron.create_pool") def _create_lb_pool(self, subnet_id, **pool_create_args): @@ -533,7 +502,6 @@ class NeutronScenario(scenario.OpenStackScenario): body = {"vip": vip_update_args} return self.clients("neutron").update_vip(vip["vip"]["id"], body) - @atomic.action_timer("neutron.create_floating_ip") def _create_floatingip(self, floating_network, **floating_ip_args): """Create floating IP with floating_network. @@ -541,40 +509,21 @@ class NeutronScenario(scenario.OpenStackScenario): :param floating_ip_args: dict, POST /floatingips create options :returns: dict, neutron floating IP """ - from neutronclient.common import exceptions as ne - floating_network_id = self._get_network_id( - floating_network) - args = {"floating_network_id": floating_network_id} - if not CONF.openstack.pre_newton_neutron: - args["description"] = self.generate_random_name() - args.update(floating_ip_args) - try: - return self.clients("neutron").create_floatingip( - {"floatingip": args}) - except ne.BadRequest as e: - error = "%s" % e - if "Unrecognized attribute" in error and "'description'" in error: - LOG.info("It looks like you have Neutron API of pre-Newton " - "OpenStack release. Setting " - "openstack.pre_newton_neutron option via Rally " - "configuration should fix an issue.") - raise + return {"floatingip": self.neutron.create_floatingip( + floating_network=floating_network, **floating_ip_args)} - @atomic.action_timer("neutron.list_floating_ips") def _list_floating_ips(self, **kwargs): """Return floating IPs list.""" - return self.clients("neutron").list_floatingips(**kwargs) + return {"floatingips": self.neutron.list_floatingips(**kwargs)} - @atomic.action_timer("neutron.delete_floating_ip") def _delete_floating_ip(self, floating_ip): """Delete floating IP. :param dict, floating IP object """ - return self.clients("neutron").delete_floatingip(floating_ip["id"]) + return self.neutron.delete_floatingip(floating_ip["id"]) - @atomic.action_timer("neutron.associate_floating_ip") def _associate_floating_ip(self, floatingip, port): """Associate floating IP with port. @@ -582,20 +531,18 @@ class NeutronScenario(scenario.OpenStackScenario): :param port: port dict :returns: updated floating IP dict """ - return self.clients("neutron").update_floatingip( - floatingip["id"], - {"floatingip": {"port_id": port["id"]}})["floatingip"] + return self.neutron.associate_floatingip( + port_id=port["id"], + floatingip_id=floatingip["id"]) - @atomic.action_timer("neutron.dissociate_floating_ip") def _dissociate_floating_ip(self, floatingip): """Dissociate floating IP from ports. :param floatingip: floating IP dict :returns: updated floating IP dict """ - return self.clients("neutron").update_floatingip( - floatingip["id"], - {"floatingip": {"port_id": None}})["floatingip"] + return self.neutron.dissociate_floatingip( + floatingip_id=floatingip["id"]) @atomic.action_timer("neutron.create_healthmonitor") def _create_v1_healthmonitor(self, **healthmonitor_create_args): @@ -648,7 +595,6 @@ class NeutronScenario(scenario.OpenStackScenario): return self.clients("neutron").update_health_monitor( healthmonitor["health_monitor"]["id"], body) - @atomic.action_timer("neutron.create_security_group") def _create_security_group(self, **security_group_create_args): """Create Neutron security-group. @@ -657,24 +603,21 @@ class NeutronScenario(scenario.OpenStackScenario): :returns: dict, neutron security-group """ security_group_create_args["name"] = self.generate_random_name() - return self.clients("neutron").create_security_group( - {"security_group": security_group_create_args}) + return {"security_group": self.neutron.create_security_group( + **security_group_create_args)} - @atomic.action_timer("neutron.delete_security_group") def _delete_security_group(self, security_group): """Delete Neutron security group. :param security_group: dict, neutron security_group """ - return self.clients("neutron").delete_security_group( + return self.neutron.delete_security_group( security_group["security_group"]["id"]) - @atomic.action_timer("neutron.list_security_groups") def _list_security_groups(self, **kwargs): """Return list of Neutron security groups.""" - return self.clients("neutron").list_security_groups(**kwargs) + return {"security_groups": self.neutron.list_security_groups(**kwargs)} - @atomic.action_timer("neutron.show_security_group") def _show_security_group(self, security_group, **kwargs): """Show security group details. @@ -682,10 +625,9 @@ class NeutronScenario(scenario.OpenStackScenario): :param kwargs: Optional additional arguments for security_group show :returns: security_group details """ - return self.clients("neutron").show_security_group( - security_group["security_group"]["id"], **kwargs) + return {"security_group": self.neutron.get_security_group( + security_group["security_group"]["id"], **kwargs)} - @atomic.action_timer("neutron.update_security_group") def _update_security_group(self, security_group, **security_group_update_args): """Update Neutron security-group. @@ -696,9 +638,9 @@ class NeutronScenario(scenario.OpenStackScenario): :returns: dict, updated neutron security-group """ security_group_update_args["name"] = self.generate_random_name() - body = {"security_group": security_group_update_args} - return self.clients("neutron").update_security_group( - security_group["security_group"]["id"], body) + return {"security_group": self.neutron.update_security_group( + security_group["security_group"]["id"], + **security_group_update_args)} def update_loadbalancer_resource(self, lb): try: @@ -857,7 +799,6 @@ class NeutronScenario(scenario.OpenStackScenario): return self.clients("neutron").list_bgpvpn_router_assocs( bgpvpn["bgpvpn"]["id"], **kwargs) - @atomic.action_timer("neutron.create_security_group_rule") def _create_security_group_rule(self, security_group_id, **security_group_rule_args): """Create Neutron security-group-rule. @@ -867,25 +808,19 @@ class NeutronScenario(scenario.OpenStackScenario): /v2.0/security-group-rules request options :returns: dict, neutron security-group-rule """ - security_group_rule_args["security_group_id"] = security_group_id - if "direction" not in security_group_rule_args: - security_group_rule_args["direction"] = "ingress" - if "protocol" not in security_group_rule_args: - security_group_rule_args["protocol"] = "tcp" + return {"security_group_rule": self.neutron.create_security_group_rule( + security_group_id, **security_group_rule_args + )} - return self.clients("neutron").create_security_group_rule( - {"security_group_rule": security_group_rule_args}) - - @atomic.action_timer("neutron.list_security_group_rules") def _list_security_group_rules(self, **kwargs): """List all security group rules. :param kwargs: Optional additional arguments for roles list :return: list of security group rules """ - return self.clients("neutron").list_security_group_rules(**kwargs) + result = self.neutron.list_security_group_rules(**kwargs) + return {"security_group_rules": result} - @atomic.action_timer("neutron.show_security_group_rule") def _show_security_group_rule(self, security_group_rule, **kwargs): """Show information of a given security group rule. @@ -893,17 +828,15 @@ class NeutronScenario(scenario.OpenStackScenario): :param kwargs: Optional additional arguments for roles list :return: details of security group rule """ - return self.clients("neutron").show_security_group_rule( - security_group_rule, **kwargs) + return {"security_group_rule": self.neutron.get_security_group_rule( + security_group_rule, **kwargs)} - @atomic.action_timer("neutron.delete_security_group_rule") def _delete_security_group_rule(self, security_group_rule): """Delete a given security group rule. :param security_group_rule: id of security group rule """ - self.clients("neutron").delete_security_group_rule( - security_group_rule) + self.neutron.delete_security_group_rule(security_group_rule) @atomic.action_timer("neutron.delete_trunk") def _delete_trunk(self, trunk_port): @@ -918,10 +851,6 @@ class NeutronScenario(scenario.OpenStackScenario): def _list_trunks(self, **kwargs): return self.clients("neutron").list_trunks(**kwargs)["trunks"] - @atomic.action_timer("neutron.list_ports_by_device_id") - def _list_ports_by_device_id(self, device_id): - return self.clients("neutron").list_ports(device_id=device_id) - @atomic.action_timer("neutron.list_subports_by_trunk") def _list_subports_by_trunk(self, trunk_id): return self.clients("neutron").trunk_get_subports(trunk_id) @@ -930,3 +859,6 @@ class NeutronScenario(scenario.OpenStackScenario): def _add_subports_to_trunk(self, trunk_id, subports): return self.clients("neutron").trunk_add_subports( trunk_id, {"sub_ports": subports}) + + def _list_ports_by_device_id(self, device_id): + return self.neutron.list_ports(device_id=device_id) diff --git a/rally_openstack/task/scenarios/nova/utils.py b/rally_openstack/task/scenarios/nova/utils.py index 1b680210..13e42573 100644 --- a/rally_openstack/task/scenarios/nova/utils.py +++ b/rally_openstack/task/scenarios/nova/utils.py @@ -23,13 +23,15 @@ from rally.task import utils from rally_openstack.common.services.image import image as image_service from rally_openstack.task import scenario from rally_openstack.task.scenarios.cinder import utils as cinder_utils +from rally_openstack.task.scenarios.neutron import utils as neutron_utils CONF = cfg.CONF LOG = logging.getLogger(__file__) -class NovaScenario(scenario.OpenStackScenario): +class NovaScenario(neutron_utils.NeutronBaseScenario, + scenario.OpenStackScenario): """Base class for Nova scenarios with basic atomic actions.""" @atomic.action_timer("nova.list_servers") @@ -633,37 +635,18 @@ class NovaScenario(scenario.OpenStackScenario): :param fixed_address: The fixedIP address the FloatingIP is to be associated with (optional) """ - with atomic.ActionTimer(self, "neutron.list_ports"): - ports = self.clients("neutron").list_ports(device_id=server.id) - port = ports["ports"][0] + if isinstance(address, dict): + floating_ip = self.neutron.associate_floatingip( + device_id=server.id, fixed_ip_address=fixed_address, + floatingip_id=address["id"]) + else: + floating_ip = self.neutron.associate_floatingip( + device_id=server.id, fixed_ip_address=fixed_address, + floating_ip_address=address) - fip = address - if not isinstance(address, dict): - LOG.warning( - "The argument 'address' of " - "NovaScenario._associate_floating_ip method accepts a " - "dict-like representation of floating ip. Transmitting a " - "string with just an IP is deprecated.") - with atomic.ActionTimer(self, "neutron.list_floating_ips"): - all_fips = self.clients("neutron").list_floatingips( - tenant_id=self.context["tenant"]["id"]) - filtered_fip = [f for f in all_fips["floatingips"] - if f["floating_ip_address"] == address] - if not filtered_fip: - raise exceptions.NotFoundException( - "There is no floating ip with '%s' address." % address) - fip = filtered_fip[0] - # the first case: fip object is returned from network wrapper - # the second case: from neutronclient directly - fip_ip = fip.get("ip", fip.get("floating_ip_address", None)) - fip_update_dict = {"port_id": port["id"]} - if fixed_address: - fip_update_dict["fixed_ip_address"] = fixed_address - self.clients("neutron").update_floatingip( - fip["id"], {"floatingip": fip_update_dict} - ) utils.wait_for(server, - is_ready=self.check_ip_address(fip_ip), + is_ready=self.check_ip_address( + floating_ip["floating_ip_address"]), update_resource=utils.get_from_manager()) # Update server data server.addresses = server.manager.get(server.id).addresses @@ -675,32 +658,19 @@ class NovaScenario(scenario.OpenStackScenario): :param server: The :class:`Server` to add an IP to. :param address: The dict-like representation of FloatingIP to remove """ - fip = address - if not isinstance(fip, dict): - LOG.warning( - "The argument 'address' of " - "NovaScenario._dissociate_floating_ip method accepts a " - "dict-like representation of floating ip. Transmitting a " - "string with just an IP is deprecated.") - with atomic.ActionTimer(self, "neutron.list_floating_ips"): - all_fips = self.clients("neutron").list_floatingips( - tenant_id=self.context["tenant"]["id"] - ) - filtered_fip = [f for f in all_fips["floatingips"] - if f["floating_ip_address"] == address] - if not filtered_fip: - raise exceptions.NotFoundException( - "There is no floating ip with '%s' address." % address) - fip = filtered_fip[0] - self.clients("neutron").update_floatingip( - fip["id"], {"floatingip": {"port_id": None}} - ) - # the first case: fip object is returned from network wrapper - # the second case: from neutronclient directly - fip_ip = fip.get("ip", fip.get("floating_ip_address", None)) + if isinstance(address, dict): + floating_ip = self.neutron.dissociate_floatingip( + floatingip_id=address["id"] + ) + else: + floating_ip = self.neutron.dissociate_floatingip( + floating_ip_address=address + ) + utils.wait_for( server, - is_ready=self.check_ip_address(fip_ip, must_exist=False), + is_ready=self.check_ip_address( + floating_ip["floating_ip_address"], must_exist=False), update_resource=utils.get_from_manager() ) # Update server data diff --git a/tests/unit/common/services/network/__init__.py b/tests/unit/common/services/network/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/common/services/network/test_net_utils.py b/tests/unit/common/services/network/test_net_utils.py new file mode 100644 index 00000000..86c29b83 --- /dev/null +++ b/tests/unit/common/services/network/test_net_utils.py @@ -0,0 +1,42 @@ +# All Rights Reserved. +# +# Licensed 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. + +from unittest import mock + +from rally_openstack.common.services.network import net_utils +from tests.unit import test + + +PATH = "rally_openstack.common.services.network.net_utils" + + +class FunctionsTestCase(test.TestCase): + + def test_generate_cidr(self): + with mock.patch("%s._IPv4_CIDR_INCR" % PATH, iter(range(1, 4))): + self.assertEqual((4, "10.2.1.0/24"), net_utils.generate_cidr()) + self.assertEqual((4, "10.2.2.0/24"), net_utils.generate_cidr()) + self.assertEqual((4, "10.2.3.0/24"), net_utils.generate_cidr()) + + with mock.patch("%s._IPv4_CIDR_INCR" % PATH, iter(range(1, 4))): + start_cidr = "1.1.0.0/26" + self.assertEqual( + (4, "1.1.0.64/26"), + net_utils.generate_cidr(start_cidr=start_cidr)) + self.assertEqual( + (4, "1.1.0.128/26"), + net_utils.generate_cidr(start_cidr=start_cidr)) + self.assertEqual( + (4, "1.1.0.192/26"), + net_utils.generate_cidr(start_cidr=start_cidr)) diff --git a/tests/unit/common/services/network/test_neutron.py b/tests/unit/common/services/network/test_neutron.py new file mode 100644 index 00000000..4255e3e8 --- /dev/null +++ b/tests/unit/common/services/network/test_neutron.py @@ -0,0 +1,1237 @@ +# All Rights Reserved. +# +# Licensed 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. + +from unittest import mock + +from rally import exceptions +from rally_openstack.common.services.network import neutron +from tests.unit import test + + +PATH = "rally_openstack.common.services.network.neutron" + + +class NeutronServiceTestCase(test.TestCase): + def setUp(self): + super(NeutronServiceTestCase, self).setUp() + self.clients = mock.MagicMock() + self.nc = self.clients.neutron.return_value + self.atomic_inst = [] + + self.name_generator_count = 0 + + def name_generator(): + self.name_generator_count += 1 + return f"s-{self.name_generator_count}" + + self.neutron = neutron.NeutronService( + clients=self.clients, + name_generator=name_generator, + atomic_inst=self.atomic_inst + ) + + def test_create_network_topology_without_a_router(self): + network = {"id": "net-id", "name": "s-1"} + subnets = [ + {"id": "subnet1-id", "name": "subnet1-name"}, + {"id": "subnet2-id", "name": "subnet2-name"} + ] + self.nc.create_network.return_value = {"network": network.copy()} + self.nc.create_subnet.side_effect = [{"subnet": s} for s in subnets] + + network_create_args = {} + subnet_create_args = {} + + topo = self.neutron.create_network_topology( + network_create_args=network_create_args, + subnet_create_args=subnet_create_args + ) + + self.assertEqual( + { + "network": dict(subnets=[subnets[0]["id"]], **network), + "subnets": [subnets[0]], + "routers": [] + }, + topo + ) + + self.nc.create_network.assert_called_once_with( + {"network": {"name": "s-1"}}) + self.nc.create_subnet.assert_called_once_with( + {"subnet": {"name": "s-2", "network_id": "net-id", + "dns_nameservers": mock.ANY, "ip_version": 4, + "cidr": mock.ANY}} + ) + + self.assertFalse(self.nc.create_router.called) + self.assertFalse(self.nc.add_interface_router.called) + + def test_create_network_topology(self): + network = {"id": "net-id", "name": "s-1"} + subnets = [ + {"id": "subnet1-id", "name": "subnet1-name"}, + {"id": "subnet2-id", "name": "subnet2-name"} + ] + router = {"id": "router"} + self.nc.create_network.return_value = {"network": network.copy()} + self.nc.create_router.return_value = {"router": router.copy()} + self.nc.create_subnet.side_effect = [{"subnet": s} for s in subnets] + + network_create_args = {} + subnet_create_args = {} + + topo = self.neutron.create_network_topology( + network_create_args=network_create_args, + subnet_create_args=subnet_create_args, + router_create_args={}, + subnets_count=2, + subnets_dualstack=True + ) + + self.assertEqual( + { + "network": dict(subnets=[subnets[0]["id"], subnets[1]["id"]], + **network), + "subnets": [subnets[0], subnets[1]], + "routers": [router] + }, + topo + ) + self.nc.create_network.assert_called_once_with( + {"network": {"name": "s-1"}}) + self.nc.create_router.assert_called_once_with( + {"router": {"name": "s-2"}}) + self.assertEqual( + [ + mock.call({"subnet": { + "name": f"s-{i}", "network_id": "net-id", + "dns_nameservers": mock.ANY, + "ip_version": 4 if i % 3 == 0 else 6, + "cidr": mock.ANY}}) + for i in range(3, 5)], + self.nc.create_subnet.call_args_list + ) + self.assertEqual( + [ + mock.call(router["id"], {"subnet_id": subnets[0]["id"]}), + mock.call(router["id"], {"subnet_id": subnets[1]["id"]}) + ], + self.nc.add_interface_router.call_args_list + ) + + def test_delete_network_topology(self): + topo = { + "network": {"id": "net-id"}, + "routers": [{"id": "r1"}, {"id": "r2"}, {"id": "r3"}], + "subnets": [{"id": "s-1"}, {"id": "s-2"}, {"id": "s-3"}] + } + self.nc.list_ports.return_value = { + "ports": [ + {"id": "p1", "device_owner": "1"}, + {"id": "p2", "device_owner": "2"} + ] + } + self.nc.list_subnets.return_value = { + "subnets": [{"id": "snet-1"}, {"id": "snet-2"}] + } + + self.neutron.delete_network_topology(topo) + + self.assertEqual( + [mock.call(r["id"]) for r in topo["routers"]], + self.nc.remove_gateway_router.call_args_list + ) + self.nc.list_ports.assert_called_once_with( + network_id=topo["network"]["id"] + ) + self.assertEqual( + # subnets from topo object should be ignored and all subnets should + # be listed + [mock.call(s["id"]) + for s in self.nc.list_subnets.return_value["subnets"]], + self.nc.delete_subnet.call_args_list + ) + self.nc.delete_network.assert_called_once_with(topo["network"]["id"]) + self.assertEqual( + [mock.call(r["id"]) for r in topo["routers"]], + self.nc.delete_router.call_args_list + ) + + def test_create_network(self): + net = "foo" + self.nc.create_network.return_value = {"network": net} + + self.assertEqual( + net, + self.neutron.create_network( + provider_physical_network="ppn", + **{"router:external": True} + ) + ) + self.nc.create_network.assert_called_once_with( + {"network": {"name": "s-1", "provider:physical_network": "ppn", + "router:external": True}} + ) + + def test_get_network(self): + network = "foo" + self.nc.show_network.return_value = {"network": network} + net_id = "net-id" + + self.assertEqual(network, self.neutron.get_network(net_id)) + self.nc.show_network.assert_called_once_with(net_id) + + self.nc.show_network.reset_mock() + + fields = ["a", "b"] + self.assertEqual(network, + self.neutron.get_network(net_id, fields=fields)) + self.nc.show_network.assert_called_once_with(net_id, fields=fields) + + def test_find_network(self): + net1 = {"id": "net-1", "name": "foo"} + net2 = {"id": "net-2", "name": "bar"} + self.nc.list_networks.return_value = {"networks": [net1, net2]} + + self.assertEqual(net2, self.neutron.find_network("bar")) + self.assertEqual(net1, self.neutron.find_network("net-1")) + self.assertRaises(exceptions.GetResourceFailure, + self.neutron.find_network, "net-3") + + def test_update_network(self): + network = "foo" + self.nc.update_network.return_value = {"network": network} + net_id = "net-id" + + self.assertEqual(network, self.neutron.update_network( + net_id, admin_state_up=False)) + self.nc.update_network.assert_called_once_with( + net_id, {"network": {"admin_state_up": False}}) + + self.nc.update_network.reset_mock() + + self.assertRaises(TypeError, + self.neutron.update_network, net_id) + self.assertFalse(self.nc.update_network.called) + + def test_delete_network(self): + net_id = "net-id" + self.neutron.delete_network(net_id) + self.nc.delete_network.assert_called_once_with(net_id) + + def test_list_networks(self): + net1 = {"id": "net-1", "name": "foo"} + net2 = {"id": "net-2", "name": "bar"} + self.nc.list_networks.return_value = {"networks": [net1, net2]} + + self.assertEqual([net1, net2], self.neutron.list_networks()) + self.nc.list_networks.assert_called_once_with() + + @mock.patch("%s.net_utils.generate_cidr" % PATH) + def test_create_subnet(self, mock_generate_cidr): + net_id = "net-id" + router_id = "router-id" + mock_generate_cidr.return_value = (6, "generated_cidr") + subnet = {"id": "subnet-id"} + self.nc.create_subnet.return_value = {"subnet": subnet} + + # case 1: + # - cidr is not specified, so it should be generated + # - ip_version equals to 6, so proper dns nameserbers should be used + # - router_id is specified, so add_interface_router method should be + # called + self.assertEqual( + subnet, + self.neutron.create_subnet(network_id=net_id, + router_id=router_id, + ip_version=6) + ) + self.nc.create_subnet.assert_called_once_with({"subnet": { + "name": "s-1", + "network_id": net_id, + "ip_version": 6, + "cidr": "generated_cidr", + "dns_nameservers": self.neutron.IPv6_DEFAULT_DNS_NAMESERVERS + }}) + mock_generate_cidr.assert_called_once_with( + ip_version=6, + start_cidr=None + ) + self.nc.add_interface_router.assert_called_once_with( + router_id, {"subnet_id": subnet["id"]} + ) + + mock_generate_cidr.reset_mock() + self.nc.create_subnet.reset_mock() + self.nc.add_interface_router.reset_mock() + + # case 2: + # - cidr is specified, so it should not be generated + # - ip_version equals to 4, so proper dns nameserbers should be used + # - router_id is not specified, so add_interface_router method should + # not be called + self.assertEqual( + subnet, + self.neutron.create_subnet(network_id=net_id, + cidr="some-cidr", + ip_version=4) + ) + self.nc.create_subnet.assert_called_once_with({"subnet": { + "name": "s-2", + "network_id": net_id, + "ip_version": 4, + "cidr": "some-cidr", + "dns_nameservers": self.neutron.IPv4_DEFAULT_DNS_NAMESERVERS + }}) + self.assertFalse(mock_generate_cidr.called) + self.assertFalse(self.nc.add_interface_router.called) + + mock_generate_cidr.reset_mock() + self.nc.create_subnet.reset_mock() + self.nc.add_interface_router.reset_mock() + + # case 3: + # - cidr is specified, so it should not be generated + # - dns_nameservers equals to None, so default values should not be + # applied + # - router_id is specified, so add_interface_router method should + # be called + self.assertEqual( + subnet, + self.neutron.create_subnet(network_id=net_id, + router_id=router_id, + cidr="some-cidr", + dns_nameservers=None, + ip_version=4) + ) + self.nc.create_subnet.assert_called_once_with({"subnet": { + "name": "s-3", + "network_id": net_id, + "ip_version": 4, + "cidr": "some-cidr", + "dns_nameservers": None + }}) + self.assertFalse(mock_generate_cidr.called) + self.nc.add_interface_router.assert_called_once_with( + router_id, {"subnet_id": subnet["id"]} + ) + + def test_get_subnet(self): + subnet = "foo" + self.nc.show_subnet.return_value = {"subnet": subnet} + subnet_id = "subnet-id" + + self.assertEqual(subnet, self.neutron.get_subnet(subnet_id)) + self.nc.show_subnet.assert_called_once_with(subnet_id) + + def test_update_subnet(self): + subnet = "foo" + self.nc.update_subnet.return_value = {"subnet": subnet} + subnet_id = "subnet-id" + + self.assertEqual(subnet, self.neutron.update_subnet( + subnet_id, enable_dhcp=False)) + self.nc.update_subnet.assert_called_once_with( + subnet_id, {"subnet": {"enable_dhcp": False}}) + + self.nc.update_subnet.reset_mock() + + self.assertRaises(TypeError, + self.neutron.update_subnet, subnet_id) + self.assertFalse(self.nc.update_subnet.called) + + def test_delete_subnet(self): + subnet_id = "subnet-id" + self.neutron.delete_subnet(subnet_id) + self.nc.delete_subnet.assert_called_once_with(subnet_id) + + def test_list_subnets(self): + subnet1 = {"id": "subnet-1", "name": "foo"} + subnet2 = {"id": "subnet-2", "name": "bar"} + self.nc.list_subnets.return_value = {"subnets": [subnet1, subnet2]} + + self.assertEqual([subnet1, subnet2], self.neutron.list_subnets()) + self.nc.list_subnets.assert_called_once_with() + + def test_create_router(self): + net1 = {"id": "net-1", "name": "foo"} + net2 = {"id": "net-2", "name": "bar"} + self.nc.list_networks.return_value = {"networks": [net1, net2]} + + router = {"id": "router-id"} + self.nc.create_router.return_value = {"router": router} + + # case 1: external_gateway_info is specified, list_networks should + # not be called + self.assertEqual( + router, + self.neutron.create_router( + external_gateway_info={"network_id": "net-id"}, + ha=True + ) + ) + self.nc.create_router.assert_called_once_with({"router": { + "name": "s-1", + "external_gateway_info": {"network_id": "net-id"}, + "ha": True + }}) + + self.assertFalse(self.nc.list_networks.called) + + self.nc.create_router.reset_mock() + + # case 2: external_gateway_info is not specified, but + # discover_external_gw is False, so list_networks should not be + # called as well + self.assertEqual( + router, + self.neutron.create_router( + discover_external_gw=False, + ha=True + ) + ) + self.nc.create_router.assert_called_once_with({"router": { + "name": "s-2", + "ha": True + }}) + + self.assertFalse(self.nc.list_networks.called) + + self.nc.create_router.reset_mock() + + # case 3: external_gateway_info is not specified, so list_networks + # should be called to discover external network + self.assertEqual( + router, + self.neutron.create_router(ha=True, discover_external_gw=True) + ) + self.nc.create_router.assert_called_once_with({"router": { + "name": "s-3", + "external_gateway_info": {"network_id": net1["id"]}, + "ha": True + }}) + + self.nc.list_networks.assert_called_once_with( + **{"router:external": True} + ) + + def test_get_router(self): + router = "foo" + self.nc.show_router.return_value = {"router": router} + router_id = "router-id" + + self.assertEqual(router, self.neutron.get_router(router_id)) + self.nc.show_router.assert_called_once_with(router_id) + + self.nc.show_router.reset_mock() + + fields = ["a", "b"] + self.assertEqual(router, + self.neutron.get_router(router_id, fields=fields)) + self.nc.show_router.assert_called_once_with(router_id, fields=fields) + + def test_add_interface_to_router(self): + router_id = "router-id" + subnet_id = "subnet-id" + port_id = "port-id" + + self.neutron.add_interface_to_router(router_id, subnet_id=subnet_id) + self.nc.add_interface_router.assert_called_once_with( + router_id, {"subnet_id": subnet_id}) + self.nc.add_interface_router.reset_mock() + + self.neutron.add_interface_to_router(router_id, port_id=port_id) + self.nc.add_interface_router.assert_called_once_with( + router_id, {"port_id": port_id}) + self.nc.add_interface_router.reset_mock() + + self.assertRaises(TypeError, + self.neutron.add_interface_to_router, router_id) + self.assertFalse(self.nc.add_interface_router.called) + + self.assertRaises(TypeError, + self.neutron.add_interface_to_router, router_id, + port_id=port_id, subnet_id=subnet_id) + self.assertFalse(self.nc.add_interface_router.called) + + def test_remove_interface_from_router(self): + router_id = "router-id" + subnet_id = "subnet-id" + port_id = "port-id" + + # case 1: use subnet-id + self.neutron.remove_interface_from_router( + router_id, subnet_id=subnet_id) + self.nc.remove_interface_router.assert_called_once_with( + router_id, {"subnet_id": subnet_id}) + self.nc.remove_interface_router.reset_mock() + + # case 2: use port-id + self.neutron.remove_interface_from_router(router_id, port_id=port_id) + self.nc.remove_interface_router.assert_called_once_with( + router_id, {"port_id": port_id}) + self.nc.remove_interface_router.reset_mock() + + # case 3: no port and subnet are specified + self.assertRaises(TypeError, + self.neutron.remove_interface_from_router, router_id) + self.assertFalse(self.nc.remove_interface_router.called) + + # case 4: both port and subnet are specified + self.assertRaises(TypeError, + self.neutron.remove_interface_from_router, router_id, + port_id=port_id, subnet_id=subnet_id) + self.assertFalse(self.nc.remove_interface_router.called) + + def test_test_remove_interface_from_router_silent_error(self): + from neutronclient.common import exceptions as neutron_exceptions + + router_id = "router-id" + subnet_id = "subnet-id" + + for exc in (neutron_exceptions.BadRequest, + neutron_exceptions.NotFound): + self.nc.remove_interface_router.side_effect = exc + + self.neutron.remove_interface_from_router( + router_id, subnet_id=subnet_id) + self.nc.remove_interface_router.assert_called_once_with( + router_id, {"subnet_id": subnet_id}) + + self.nc.remove_interface_router.reset_mock() + + def test_add_gateway_to_router(self): + router_id = "r-id" + net_id = "net-id" + external_fixed_ips = "ex-net-obj" + self.nc.list_extensions.return_value = { + "extensions": [{"alias": "ext-gw-mode"}] + } + + # case 1 + self.neutron.add_gateway_to_router( + router_id, + network_id=net_id, + external_fixed_ips=external_fixed_ips, + enable_snat=True + ) + self.nc.add_gateway_router.assert_called_once_with( + router_id, {"network_id": net_id, + "enable_snat": True, + "external_fixed_ips": external_fixed_ips}) + self.nc.add_gateway_router.reset_mock() + + # case 2 + self.neutron.add_gateway_to_router(router_id, network_id=net_id) + self.nc.add_gateway_router.assert_called_once_with( + router_id, {"network_id": net_id}) + + def test_remove_gateway_from_router(self): + router_id = "r-id" + self.neutron.remove_gateway_from_router(router_id) + self.nc.remove_gateway_router.assert_called_once_with(router_id) + + def test_update_router(self): + router = "foo" + self.nc.update_router.return_value = {"router": router} + router_id = "subnet-id" + + self.assertEqual(router, self.neutron.update_router( + router_id, admin_state_up=False)) + self.nc.update_router.assert_called_once_with( + router_id, {"router": {"admin_state_up": False}}) + + self.nc.update_router.reset_mock() + + self.assertRaises(TypeError, + self.neutron.update_router, router_id) + self.assertFalse(self.nc.update_router.called) + + def test_delete_router(self): + router_id = "r-id" + self.neutron.delete_router(router_id) + self.nc.delete_router.assert_called_once_with(router_id) + + def test_list_routers(self): + router1 = { + "id": "router-1", + "name": "r1", + "external_gateway_info": None + } + router2 = { + "id": "router-2", + "name": "r2", + "external_gateway_info": {"external_fixed_ips": []} + } + router3 = { + "id": "router-3", + "name": "r3", + "external_gateway_info": { + "external_fixed_ips": [{"subnet_id": "s1"}] + } + } + router4 = { + "id": "router-4", + "name": "r4", + "external_gateway_info": { + "external_fixed_ips": [{"subnet_id": "s1"}, + {"subnet_id": "s2"}] + } + } + router5 = { + "id": "router-5", + "name": "r5", + "external_gateway_info": { + "external_fixed_ips": [{"subnet_id": "s2"}] + } + } + self.nc.list_routers.return_value = {"routers": [ + router1, router2, router3, router4, router5]} + + # case 1: use native neutron api filters + self.assertEqual( + [router1, router2, router3, router4, router5], + self.neutron.list_routers(admin_state_up=True) + ) + self.nc.list_routers.assert_called_once_with(admin_state_up=True) + + self.nc.list_routers.reset_mock() + + # case 2: use additional post api filtering by subnet + self.assertEqual( + [router4, router5], + self.neutron.list_routers(subnet_ids=["s2"]) + ) + self.nc.list_routers.assert_called_once_with() + + def test_create_port(self): + net_id = "net-id" + port = "foo" + self.nc.create_port.return_value = {"port": port} + + self.assertEqual(port, self.neutron.create_port(network_id=net_id)) + self.nc.create_port.assert_called_once_with( + {"port": {"name": "s-1", "network_id": net_id}} + ) + + def test_get_port(self): + port = "foo" + self.nc.show_port.return_value = {"port": port} + port_id = "net-id" + + self.assertEqual(port, self.neutron.get_port(port_id)) + self.nc.show_port.assert_called_once_with(port_id) + + self.nc.show_port.reset_mock() + + fields = ["a", "b"] + self.assertEqual(port, + self.neutron.get_port(port_id, fields=fields)) + self.nc.show_port.assert_called_once_with(port_id, fields=fields) + + def test_update_port(self): + port = "foo" + self.nc.update_port.return_value = {"port": port} + port_id = "net-id" + + self.assertEqual(port, self.neutron.update_port( + port_id, admin_state_up=False)) + self.nc.update_port.assert_called_once_with( + port_id, {"port": {"admin_state_up": False}}) + + self.nc.update_port.reset_mock() + + self.assertRaises(TypeError, self.neutron.update_port, port_id) + self.assertFalse(self.nc.update_port.called) + + def test_delete_port(self): + # case 1: port argument is a string with port ID + port = "port-id" + + self.neutron.delete_port(port) + + self.nc.delete_port.assert_called_once_with(port) + self.assertFalse(self.nc.remove_gateway_router.called) + self.assertFalse(self.nc.remove_interface_router.called) + self.nc.delete_port.reset_mock() + + # case 2: port argument is a dict with an id and not-special + # device_owner + port = {"id": "port-id", "device_owner": "someone", + "device_id": "device-id"} + + self.neutron.delete_port(port) + + self.nc.delete_port.assert_called_once_with(port["id"]) + self.assertFalse(self.nc.remove_interface_router.called) + self.nc.delete_port.reset_mock() + + # case 3: port argument is a dict with an id and owner is a router + # interface + port = {"id": "port-id", + "device_id": "device-id", + "device_owner": "network:router_interface_distributed"} + + self.neutron.delete_port(port) + + self.assertFalse(self.nc.delete_port.called) + self.assertFalse(self.nc.remove_gateway_router.called) + self.nc.remove_interface_router.assert_called_once_with( + port["device_id"], {"port_id": port["id"]} + ) + + self.nc.delete_port.reset_mock() + self.nc.remove_interface_router.reset_mock() + + # case 4: port argument is a dict with an id and owner is a router + # gateway + port = {"id": "port-id", + "device_id": "device-id", + "device_owner": "network:router_gateway"} + + self.neutron.delete_port(port) + + self.assertFalse(self.nc.delete_port.called) + self.nc.remove_gateway_router.assert_called_once_with( + port["device_id"] + ) + self.nc.remove_interface_router.assert_called_once_with( + port["device_id"], {"port_id": port["id"]} + ) + + def test_delete_port_silently(self): + from neutronclient.common import exceptions as neutron_exceptions + + self.nc.delete_port.side_effect = neutron_exceptions.PortNotFoundClient + + port = "port-id" + + self.neutron.delete_port(port) + + self.nc.delete_port.assert_called_once_with(port) + self.assertFalse(self.nc.remove_gateway_router.called) + self.assertFalse(self.nc.remove_interface_router.called) + + def test_list_ports(self): + port1 = {"id": "port-1", "name": "foo"} + port2 = {"id": "port-2", "name": "bar"} + self.nc.list_ports.return_value = {"ports": [port1, port2]} + + self.assertEqual([port1, port2], self.neutron.list_ports()) + self.nc.list_ports.assert_called_once_with() + + def test_create_floatingip(self): + floatingip = "foo" + self.nc.create_floatingip.return_value = {"floatingip": floatingip} + networks = [ + {"id": "net1-id", "name": "net1"}, + {"id": "net2-id", "name": "net2", "router:external": True}, + {"id": "net3-id", "name": "net3", "router:external": False} + ] + self.nc.list_networks.return_value = {"networks": networks} + + # case 1: floating_network is a dict with network id + floating_network = {"id": "net-id"} + + self.assertEqual( + floatingip, + self.neutron.create_floatingip(floating_network=floating_network) + ) + self.nc.create_floatingip.assert_called_once_with( + { + "floatingip": {"description": "s-1", + "floating_network_id": floating_network["id"]} + } + ) + self.assertFalse(self.nc.list_networks.called) + self.nc.create_floatingip.reset_mock() + + # case 2: floating_network is an ID + floating_network = "net2-id" + + self.assertEqual( + floatingip, + self.neutron.create_floatingip(floating_network=floating_network) + ) + self.nc.create_floatingip.assert_called_once_with( + { + "floatingip": {"description": "s-2", + "floating_network_id": floating_network} + } + ) + self.nc.list_networks.assert_called_once_with() + self.nc.create_floatingip.reset_mock() + self.nc.list_networks.reset_mock() + + # case 3: floating_network is an ID + floating_network = "net2-id" + + self.assertEqual( + floatingip, + self.neutron.create_floatingip(floating_network=floating_network) + ) + self.nc.create_floatingip.assert_called_once_with( + { + "floatingip": {"description": "s-3", + "floating_network_id": floating_network} + } + ) + self.nc.list_networks.assert_called_once_with() + self.nc.create_floatingip.reset_mock() + self.nc.list_networks.reset_mock() + + # case 4: floating_network is a name of not external network + floating_network = "net3" + + self.assertRaises( + exceptions.NotFoundException, + self.neutron.create_floatingip, floating_network=floating_network + ) + self.assertFalse(self.nc.create_floatingip.called) + + self.nc.list_networks.assert_called_once_with() + self.nc.create_floatingip.reset_mock() + self.nc.list_networks.reset_mock() + + # case 4: floating_network is not specified + self.assertEqual( + floatingip, + self.neutron.create_floatingip() + ) + self.nc.create_floatingip.assert_called_once_with( + { + "floatingip": {"description": "s-4", + "floating_network_id": networks[0]["id"]} + } + ) + self.nc.list_networks.assert_called_once_with( + **{"router:external": True}) + self.nc.create_floatingip.reset_mock() + self.nc.list_networks.reset_mock() + + @mock.patch("%s.LOG.info" % PATH) + def test_create_floatingip_failure(self, mock_log_info): + from neutronclient.common import exceptions as neutron_exceptions + + # case 1: an error which we should not handle + self.nc.create_floatingip.side_effect = neutron_exceptions.BadRequest( + "oops" + ) + self.assertRaises( + neutron_exceptions.BadRequest, + self.neutron.create_floatingip, floating_network={"id": "net-id"} + ) + self.assertFalse(mock_log_info.called) + + # case 2: exception that we should handle + self.nc.create_floatingip.side_effect = neutron_exceptions.BadRequest( + "Unrecognized attribute: 'description'" + ) + self.assertRaises( + neutron_exceptions.BadRequest, + self.neutron.create_floatingip, floating_network={"id": "net-id"} + ) + self.assertTrue(mock_log_info.called) + + def test_get_floatingip(self): + floatingip = "foo" + self.nc.show_floatingip.return_value = {"floatingip": floatingip} + floatingip_id = "fip-id" + + self.assertEqual(floatingip, + self.neutron.get_floatingip(floatingip_id)) + self.nc.show_floatingip.assert_called_once_with(floatingip_id) + + self.nc.show_floatingip.reset_mock() + + fields = ["a", "b"] + self.assertEqual( + floatingip, + self.neutron.get_floatingip(floatingip_id, fields=fields) + ) + self.nc.show_floatingip.assert_called_once_with(floatingip_id, + fields=fields) + + def test_update_floatingip(self): + floatingip = "foo" + self.nc.update_floatingip.return_value = {"floatingip": floatingip} + floatingip_id = "fip-id" + + self.assertEqual(floatingip, self.neutron.update_floatingip( + floatingip_id, port_id="port-id")) + self.nc.update_floatingip.assert_called_once_with( + floatingip_id, {"floatingip": {"port_id": "port-id"}}) + + self.nc.update_floatingip.reset_mock() + + self.assertRaises(TypeError, + self.neutron.update_floatingip, floatingip_id) + self.assertFalse(self.nc.update_floatingip.called) + + def test_associate_floatingip(self): + floatingip_id = "fip-id" + device_id = "device-id" + floating_ip_address = "floating_ip_address" + + floatingip = "foo" + self.nc.update_floatingip.return_value = {"floatingip": floatingip} + + port_id = "port-id" + self.nc.list_ports.return_value = { + "ports": [{"id": port_id, "device_id": device_id}] + } + + self.nc.list_floatingips.return_value = { + "floatingips": [{"id": floatingip_id}] + } + + # case 1: + # - port_id is None, so it should be discovered using device_id + # - floatingip_id is not None, so nothing should be specified here + + self.assertEqual( + floatingip, + self.neutron.associate_floatingip( + device_id=device_id, floatingip_id=floatingip_id)) + self.nc.update_floatingip.assert_called_once_with( + floatingip_id, {"floatingip": {"port_id": port_id}}) + self.nc.list_ports.assert_called_once_with(device_id=device_id) + self.assertFalse(self.nc.list_floatingips.called) + + self.nc.update_floatingip.reset_mock() + self.nc.list_ports.reset_mock() + + # case 2: + # - port_id is not None, so not discovery should be performed + # - floatingip_id is None, so it should be discovered + + self.assertEqual( + floatingip, + self.neutron.associate_floatingip( + port_id=port_id, floating_ip_address=floating_ip_address, + fixed_ip_address="fixed_ip_addr" + )) + self.nc.update_floatingip.assert_called_once_with( + floatingip_id, + {"floatingip": {"port_id": port_id, + "fixed_ip_address": "fixed_ip_addr"}}) + self.assertFalse(self.nc.list_ports.called) + self.nc.list_floatingips.assert_called_once_with( + floating_ip_address=floating_ip_address + ) + + self.nc.update_floatingip.reset_mock() + self.nc.list_ports.reset_mock() + self.nc.list_floatingips.reset_mock() + + # case 3: + # - port_id is not None, so not discovery should be performed + # - floatingip_id is None, so it should be discovered, but error + # happens + + self.nc.list_floatingips.return_value = {"floatingips": []} + + self.assertRaises( + exceptions.GetResourceFailure, + self.neutron.associate_floatingip, + port_id=port_id, floating_ip_address=floating_ip_address + ) + self.assertFalse(self.nc.update_floatingip.called) + self.assertFalse(self.nc.list_ports.called) + self.nc.list_floatingips.assert_called_once_with( + floating_ip_address=floating_ip_address + ) + + self.nc.update_floatingip.reset_mock() + self.nc.list_floatingips.reset_mock() + + # case 4: + # - port_id is None, so discovery should be performed, but error + # happens + # - floatingip_id is None, so discovery should not be performed + # since port discovery fails first + + self.nc.list_floatingips.return_value = {"floatingips": []} + self.nc.list_ports.return_value = {"ports": []} + + self.assertRaises( + exceptions.GetResourceFailure, + self.neutron.associate_floatingip, + device_id=device_id, floating_ip_address=floating_ip_address + ) + self.nc.list_ports.assert_called_once_with(device_id=device_id) + self.assertFalse(self.nc.update_floatingip.called) + self.assertFalse(self.nc.list_floatingips.called) + + def test_associate_floatingip_typeerror(self): + # no device_id and port_id + self.assertRaises(TypeError, self.neutron.associate_floatingip) + # both args are specified + self.assertRaises(TypeError, self.neutron.associate_floatingip, + device_id="d-id", port_id="p-id") + + # no floating_ip_address and floating_ip_id + self.assertRaises(TypeError, self.neutron.associate_floatingip, + port_id="p-id") + # both args are specified + self.assertRaises(TypeError, self.neutron.associate_floatingip, + port_id="p-id", + floating_ip_address="fip", floating_ip_id="fip_id") + + def test_disassociate_floatingip(self): + floatingip_id = "fip-id" + floating_ip_address = "floating_ip_address" + + floatingip = "foo" + self.nc.update_floatingip.return_value = {"floatingip": floatingip} + + self.nc.list_floatingips.return_value = { + "floatingips": [{"id": floatingip_id}] + } + + # case 1: floatingip_id is specified + + self.assertEqual( + floatingip, + self.neutron.dissociate_floatingip(floatingip_id=floatingip_id)) + self.nc.update_floatingip.assert_called_once_with( + floatingip_id, {"floatingip": {"port_id": None}}) + self.assertFalse(self.nc.list_floatingips.called) + + self.nc.update_floatingip.reset_mock() + + # case 2: floating_ip_address is specified + + self.assertEqual( + floatingip, + self.neutron.dissociate_floatingip( + floating_ip_address=floating_ip_address + )) + self.nc.update_floatingip.assert_called_once_with( + floatingip_id, {"floatingip": {"port_id": None}}) + self.nc.list_floatingips.assert_called_once_with( + floating_ip_address=floating_ip_address + ) + + self.nc.update_floatingip.reset_mock() + self.nc.list_floatingips.reset_mock() + + # case 3: floating_ip_address is specified but failing to + # find floatingip by it + + self.nc.list_floatingips.return_value = {"floatingips": []} + + self.assertRaises( + exceptions.GetResourceFailure, + self.neutron.dissociate_floatingip, + floating_ip_address=floating_ip_address + ) + self.assertFalse(self.nc.update_floatingip.called) + self.nc.list_floatingips.assert_called_once_with( + floating_ip_address=floating_ip_address + ) + + def test_disassociate_floatingip_typeerror(self): + # no floating_ip_address and floating_ip_id + self.assertRaises(TypeError, self.neutron.dissociate_floatingip) + # both args are specified + self.assertRaises(TypeError, self.neutron.dissociate_floatingip, + floating_ip_address="fip", floating_ip_id="fip_id") + + def delete_floatingip(self): + floatingip_id = "fip-id" + self.neutron.delete_floatingip(floatingip_id) + self.nc.delete_floatingip.assert_called_once_with(floatingip_id) + + def test_list_floatingips(self): + floatingip_1 = {"id": "fip-1", "name": "foo"} + floatingip_2 = {"id": "fip-2", "name": "bar"} + self.nc.list_floatingips.return_value = { + "floatingips": [floatingip_1, floatingip_2] + } + + self.assertEqual( + [floatingip_1, floatingip_2], + self.neutron.list_floatingips(port_id="port-id") + ) + self.nc.list_floatingips.assert_called_once_with(port_id="port-id") + + def test_create_security_group(self): + security_group = "foo" + self.nc.create_security_group.return_value = { + "security_group": security_group} + + self.assertEqual( + security_group, self.neutron.create_security_group(stateful=True) + ) + self.nc.create_security_group.assert_called_once_with( + {"security_group": {"name": "s-1", "stateful": True}} + ) + + def test_get_security_group(self): + security_group = "foo" + self.nc.show_security_group.return_value = { + "security_group": security_group} + security_group_id = "security-group-id" + + self.assertEqual(security_group, + self.neutron.get_security_group(security_group_id)) + self.nc.show_security_group.assert_called_once_with(security_group_id) + + self.nc.show_security_group.reset_mock() + + fields = ["a", "b"] + self.assertEqual( + security_group, + self.neutron.get_security_group(security_group_id, fields=fields)) + self.nc.show_security_group.assert_called_once_with( + security_group_id, fields=fields) + + def test_update_update_security_group(self): + security_group = "foo" + self.nc.update_security_group.return_value = { + "security_group": security_group} + security_group_id = "security-group-id" + + self.assertEqual( + security_group, + self.neutron.update_security_group( + security_group_id, stateful=False)) + self.nc.update_security_group.assert_called_once_with( + security_group_id, {"security_group": {"stateful": False}}) + + self.nc.update_security_group.reset_mock() + + self.assertRaises( + TypeError, + self.neutron.update_security_group, security_group_id) + self.assertFalse(self.nc.update_security_group.called) + + def test_delete_security_group(self): + security_group_id = "security-group-id" + self.neutron.delete_security_group(security_group_id) + self.nc.delete_security_group.assert_called_once_with( + security_group_id) + + def test_list_security_groups(self): + sg1 = {"id": "sg-1", "name": "foo"} + sg2 = {"id": "sg-2", "name": "bar"} + self.nc.list_security_groups.return_value = { + "security_groups": [sg1, sg2] + } + + self.assertEqual([sg1, sg2], self.neutron.list_security_groups()) + self.nc.list_security_groups.assert_called_once_with() + + def test_create_security_group_rule(self): + security_group_rule = "foo" + self.nc.create_security_group_rule.return_value = { + "security_group_rule": security_group_rule} + + self.assertEqual( + security_group_rule, + self.neutron.create_security_group_rule( + security_group_id="sg1", ) + ) + self.nc.create_security_group_rule.assert_called_once_with( + {"security_group_rule": { + "security_group_id": "sg1", "direction": "ingress", + "protocol": "tcp" + }} + ) + + def test_get_security_group_rule(self): + security_group_rule = "foo" + self.nc.show_security_group_rule.return_value = { + "security_group_rule": security_group_rule} + security_group_rule_id = "security-group-id" + + self.assertEqual( + security_group_rule, + self.neutron.get_security_group_rule(security_group_rule_id)) + self.nc.show_security_group_rule.assert_called_once_with( + security_group_rule_id) + + self.nc.show_security_group_rule.reset_mock() + + fields = ["a", "b"] + self.assertEqual( + security_group_rule, + self.neutron.get_security_group_rule( + security_group_rule_id, fields=fields)) + self.nc.show_security_group_rule.assert_called_once_with( + security_group_rule_id, fields=fields) + + def test_delete_security_group_rule(self): + security_group_rule_id = "security-group-rule-id" + self.neutron.delete_security_group_rule(security_group_rule_id) + self.nc.delete_security_group_rule.assert_called_once_with( + security_group_rule_id) + + def test_list_security_groups_rule(self): + sgr1 = {"id": "sg-1", "name": "foo"} + sgr2 = {"id": "sg-2", "name": "bar"} + self.nc.list_security_group_rules.return_value = { + "security_group_rules": [sgr1, sgr2] + } + + self.assertEqual([sgr1, sgr2], + self.neutron.list_security_group_rules()) + self.nc.list_security_group_rules.assert_called_once_with() + + def test_list_agents(self): + agent1 = {"id": "agent-1", "name": "foo"} + agent2 = {"id": "agent-2", "name": "bar"} + self.nc.list_agents.return_value = {"agents": [agent1, agent2]} + + self.assertEqual([agent1, agent2], self.neutron.list_agents()) + self.nc.list_agents.assert_called_once_with() + + def test_list_extensions(self): + ext1 = {"alias": "foo"} + ext2 = {"alias": "bar"} + self.nc.list_extensions.return_value = {"extensions": [ext1, ext2]} + + self.assertEqual([ext1, ext2], self.neutron.list_extensions()) + self.nc.list_extensions.assert_called_once_with() + + def test_cached_supported_extensions(self): + ext1 = {"alias": "foo"} + ext2 = {"alias": "bar"} + self.nc.list_extensions.return_value = {"extensions": [ext1, ext2]} + + self.assertEqual([ext1, ext2], + self.neutron.cached_supported_extensions) + self.nc.list_extensions.assert_called_once_with() + + self.nc.list_extensions.reset_mock() + # another try + self.assertEqual([ext1, ext2], + self.neutron.cached_supported_extensions) + self.assertFalse(self.nc.list_extensions.called) + + def test_supports_extension(self): + ext1 = {"alias": "foo"} + ext2 = {"alias": "bar"} + self.nc.list_extensions.return_value = {"extensions": [ext1, ext2]} + + self.assertTrue(self.neutron.supports_extension("foo")) + self.assertTrue(self.neutron.supports_extension("bar")) + self.assertFalse(self.neutron.supports_extension("xxx", silent=True)) + self.assertRaises(exceptions.NotFoundException, + self.neutron.supports_extension, "xxx") + + # this should be called once + self.nc.list_extensions.assert_called_once_with() diff --git a/tests/unit/common/wrappers/test_network.py b/tests/unit/common/wrappers/test_network.py index 5cc9620c..2e3860d1 100644 --- a/tests/unit/common/wrappers/test_network.py +++ b/tests/unit/common/wrappers/test_network.py @@ -35,84 +35,87 @@ class Owner(utils.RandomNameGeneratorMixin): @ddt.ddt class NeutronWrapperTestCase(test.TestCase): def setUp(self): + super(NeutronWrapperTestCase, self).setUp() self.owner = Owner() self.owner.generate_random_name = mock.Mock() - super(NeutronWrapperTestCase, self).setUp() - - def get_wrapper(self, *skip_cidrs, **kwargs): - return network.NeutronWrapper(mock.Mock(), self.owner, config=kwargs) + self.wrapper = network.NeutronWrapper(mock.MagicMock(), + self.owner, + config={}) + self._nc = self.wrapper.neutron.client def test_SUBNET_IP_VERSION(self): self.assertEqual(4, network.NeutronWrapper.SUBNET_IP_VERSION) - @mock.patch("rally_openstack.common.wrappers.network.generate_cidr") + @mock.patch( + "rally_openstack.common.services.network.net_utils.generate_cidr") def test__generate_cidr(self, mock_generate_cidr): cidrs = iter(range(5)) - mock_generate_cidr.side_effect = ( - lambda start_cidr: start_cidr + next(cidrs) - ) - service = self.get_wrapper(start_cidr=3) - self.assertEqual(3, service._generate_cidr()) - self.assertEqual(4, service._generate_cidr()) - self.assertEqual(5, service._generate_cidr()) - self.assertEqual(6, service._generate_cidr()) - self.assertEqual(7, service._generate_cidr()) - self.assertEqual([mock.call(start_cidr=3)] * 5, - mock_generate_cidr.mock_calls) + + def fake_gen_cidr(ip_version=None, start_cidr=None): + return 4, 3 + next(cidrs) + + mock_generate_cidr.side_effect = fake_gen_cidr + + self.assertEqual(3, self.wrapper._generate_cidr()) + self.assertEqual(4, self.wrapper._generate_cidr()) + self.assertEqual(5, self.wrapper._generate_cidr()) + self.assertEqual(6, self.wrapper._generate_cidr()) + self.assertEqual(7, self.wrapper._generate_cidr()) + self.assertEqual([mock.call(start_cidr=self.wrapper.start_cidr)] * 5, + mock_generate_cidr.call_args_list) def test_external_networks(self): - wrap = self.get_wrapper() - wrap.client.list_networks.return_value = {"networks": "foo_networks"} - self.assertEqual("foo_networks", wrap.external_networks) - wrap.client.list_networks.assert_called_once_with( + self._nc.list_networks.return_value = {"networks": "foo_networks"} + self.assertEqual("foo_networks", self.wrapper.external_networks) + self._nc.list_networks.assert_called_once_with( **{"router:external": True}) def test_get_network(self): - wrap = self.get_wrapper() neutron_net = {"id": "foo_id", - "name": self.owner.generate_random_name.return_value, + "name": "foo_name", "tenant_id": "foo_tenant", "status": "foo_status", "router:external": "foo_external", "subnets": "foo_subnets"} expected_net = {"id": "foo_id", - "name": self.owner.generate_random_name.return_value, + "name": "foo_name", "tenant_id": "foo_tenant", "status": "foo_status", "external": "foo_external", "router_id": None, "subnets": "foo_subnets"} - wrap.client.show_network.return_value = {"network": neutron_net} - net = wrap.get_network(net_id="foo_id") + self._nc.show_network.return_value = {"network": neutron_net} + net = self.wrapper.get_network(net_id="foo_id") self.assertEqual(expected_net, net) - wrap.client.show_network.assert_called_once_with("foo_id") + self._nc.show_network.assert_called_once_with("foo_id") - wrap.client.show_network.side_effect = ( + self._nc.show_network.side_effect = ( neutron_exceptions.NeutronClientException) - self.assertRaises(network.NetworkWrapperException, wrap.get_network, + self.assertRaises(network.NetworkWrapperException, + self.wrapper.get_network, net_id="foo_id") - wrap.client.list_networks.return_value = {"networks": [neutron_net]} - net = wrap.get_network(name="foo_name") + self._nc.list_networks.return_value = {"networks": [neutron_net]} + net = self.wrapper.get_network(name="foo_name") self.assertEqual(expected_net, net) - wrap.client.list_networks.assert_called_once_with(name="foo_name") + self._nc.list_networks.assert_called_once_with(name="foo_name") - wrap.client.list_networks.return_value = {"networks": []} - self.assertRaises(network.NetworkWrapperException, wrap.get_network, + self._nc.list_networks.return_value = {"networks": []} + self.assertRaises(network.NetworkWrapperException, + self.wrapper.get_network, name="foo_name") def test_create_v1_pool(self): subnet = "subnet_id" tenant = "foo_tenant" - service = self.get_wrapper() expected_pool = {"pool": { "id": "pool_id", "name": self.owner.generate_random_name.return_value, "subnet_id": subnet, "tenant_id": tenant}} - service.client.create_pool.return_value = expected_pool - resultant_pool = service.create_v1_pool(tenant, subnet) - service.client.create_pool.assert_called_once_with({ + self.wrapper.client.create_pool.return_value = expected_pool + resultant_pool = self.wrapper.create_v1_pool(tenant, subnet) + self.wrapper.client.create_pool.assert_called_once_with({ "pool": {"lb_method": "ROUND_ROBIN", "subnet_id": subnet, "tenant_id": tenant, @@ -121,13 +124,12 @@ class NeutronWrapperTestCase(test.TestCase): self.assertEqual(expected_pool, resultant_pool) def test_create_network(self): - service = self.get_wrapper() - service.client.create_network.return_value = { + self._nc.create_network.return_value = { "network": {"id": "foo_id", "name": self.owner.generate_random_name.return_value, "status": "foo_status"}} - net = service.create_network("foo_tenant") - service.client.create_network.assert_called_once_with({ + net = self.wrapper.create_network("foo_tenant") + self._nc.create_network.assert_called_once_with({ "network": {"tenant_id": "foo_tenant", "name": self.owner.generate_random_name.return_value}}) self.assertEqual({"id": "foo_id", @@ -140,22 +142,18 @@ class NeutronWrapperTestCase(test.TestCase): def test_create_network_with_subnets(self): subnets_num = 4 - service = self.get_wrapper() - subnets_cidrs = iter(range(subnets_num)) subnets_ids = iter(range(subnets_num)) - service._generate_cidr = mock.Mock( - side_effect=lambda v: "cidr-%d" % next(subnets_cidrs)) - service.client.create_subnet = mock.Mock( - side_effect=lambda i: { - "subnet": {"id": "subnet-%d" % next(subnets_ids)}}) - service.client.create_network.return_value = { + self._nc.create_subnet.side_effect = lambda i: { + "subnet": {"id": "subnet-%d" % next(subnets_ids)}} + self._nc.create_network.return_value = { "network": {"id": "foo_id", "name": self.owner.generate_random_name.return_value, "status": "foo_status"}} - net = service.create_network("foo_tenant", subnets_num=subnets_num) + net = self.wrapper.create_network("foo_tenant", + subnets_num=subnets_num) - service.client.create_network.assert_called_once_with({ + self._nc.create_network.assert_called_once_with({ "network": {"tenant_id": "foo_tenant", "name": self.owner.generate_random_name.return_value}}) self.assertEqual({"id": "foo_id", @@ -167,25 +165,24 @@ class NeutronWrapperTestCase(test.TestCase): "subnets": ["subnet-%d" % i for i in range(subnets_num)]}, net) self.assertEqual( - service.client.create_subnet.mock_calls, [mock.call({"subnet": {"name": self.owner.generate_random_name.return_value, - "enable_dhcp": True, "network_id": "foo_id", "tenant_id": "foo_tenant", - "ip_version": service.SUBNET_IP_VERSION, + "ip_version": self.wrapper.SUBNET_IP_VERSION, "dns_nameservers": ["8.8.8.8", "8.8.4.4"], - "cidr": "cidr-%d" % i}}) - for i in range(subnets_num)]) + "cidr": mock.ANY}}) + for i in range(subnets_num)], + self.wrapper.client.create_subnet.call_args_list + ) def test_create_network_with_router(self): - service = self.get_wrapper() - service.create_router = mock.Mock(return_value={"id": "foo_router"}) - service.client.create_network.return_value = { + self._nc.create_router.return_value = {"router": {"id": "foo_router"}} + self._nc.create_network.return_value = { "network": {"id": "foo_id", "name": self.owner.generate_random_name.return_value, "status": "foo_status"}} - net = service.create_network("foo_tenant", add_router=True) + net = self.wrapper.create_network("foo_tenant", add_router=True) self.assertEqual({"id": "foo_id", "name": self.owner.generate_random_name.return_value, "status": "foo_status", @@ -193,23 +190,25 @@ class NeutronWrapperTestCase(test.TestCase): "tenant_id": "foo_tenant", "router_id": "foo_router", "subnets": []}, net) - service.create_router.assert_called_once_with(external=True, - tenant_id="foo_tenant") + self._nc.create_router.assert_called_once_with({ + "router": { + "name": self.owner.generate_random_name(), + "tenant_id": "foo_tenant" + } + }) def test_create_network_with_router_and_subnets(self): subnets_num = 4 - service = self.get_wrapper() - service._generate_cidr = mock.Mock(return_value="foo_cidr") - service.create_router = mock.Mock(return_value={"id": "foo_router"}) - service.client.create_subnet = mock.Mock( - return_value={"subnet": {"id": "foo_subnet"}}) - service.client.create_network.return_value = { + self.wrapper._generate_cidr = mock.Mock(return_value="foo_cidr") + self._nc.create_router.return_value = {"router": {"id": "foo_router"}} + self._nc.create_subnet.return_value = {"subnet": {"id": "foo_subnet"}} + self._nc.create_network.return_value = { "network": {"id": "foo_id", "name": self.owner.generate_random_name.return_value, "status": "foo_status"}} - net = service.create_network("foo_tenant", add_router=True, - subnets_num=subnets_num, - dns_nameservers=["foo_nameservers"]) + net = self.wrapper.create_network( + "foo_tenant", add_router=True, subnets_num=subnets_num, + dns_nameservers=["foo_nameservers"]) self.assertEqual({"id": "foo_id", "name": self.owner.generate_random_name.return_value, "status": "foo_status", @@ -217,76 +216,70 @@ class NeutronWrapperTestCase(test.TestCase): "tenant_id": "foo_tenant", "router_id": "foo_router", "subnets": ["foo_subnet"] * subnets_num}, net) - service.create_router.assert_called_once_with(external=True, - tenant_id="foo_tenant") + self._nc.create_router.assert_called_once_with( + {"router": {"name": self.owner.generate_random_name.return_value, + "tenant_id": "foo_tenant"}}) self.assertEqual( - service.client.create_subnet.mock_calls, - [mock.call({"subnet": - {"name": self.owner.generate_random_name.return_value, - "enable_dhcp": True, - "network_id": "foo_id", - "tenant_id": "foo_tenant", - "ip_version": service.SUBNET_IP_VERSION, - "dns_nameservers": ["foo_nameservers"], - "cidr": "foo_cidr"}})] * subnets_num) - self.assertEqual(service.client.add_interface_router.mock_calls, + [ + mock.call( + {"subnet": { + "name": self.owner.generate_random_name.return_value, + "network_id": "foo_id", + "tenant_id": "foo_tenant", + "ip_version": self.wrapper.SUBNET_IP_VERSION, + "dns_nameservers": ["foo_nameservers"], + "cidr": mock.ANY + }} + ) + ] * subnets_num, + self._nc.create_subnet.call_args_list, + ) + self.assertEqual(self._nc.add_interface_router.call_args_list, [mock.call("foo_router", {"subnet_id": "foo_subnet"}) for i in range(subnets_num)]) - @mock.patch("rally_openstack.common.wrappers.network.NeutronWrapper" - ".supports_extension", return_value=(False, "")) - def test_delete_network(self, mock_neutron_wrapper_supports_extension): - service = self.get_wrapper() - service.client.list_ports.return_value = {"ports": []} - service.client.list_subnets.return_value = {"subnets": []} - service.client.delete_network.return_value = "foo_deleted" - result = service.delete_network({"id": "foo_id", "router_id": None, - "subnets": []}) - self.assertEqual("foo_deleted", result) - self.assertEqual([], service.client.remove_gateway_router.mock_calls) - self.assertEqual( - [], service.client.remove_interface_router.mock_calls) - self.assertEqual([], service.client.delete_router.mock_calls) - self.assertEqual([], service.client.delete_subnet.mock_calls) - service.client.delete_network.assert_called_once_with("foo_id") - def test_delete_v1_pool(self): - service = self.get_wrapper() pool = {"pool": {"id": "pool-id"}} - service.delete_v1_pool(pool["pool"]["id"]) - service.client.delete_pool.assert_called_once_with("pool-id") + self.wrapper.delete_v1_pool(pool["pool"]["id"]) + self.wrapper.client.delete_pool.assert_called_once_with("pool-id") - @mock.patch("rally_openstack.common.wrappers.network.NeutronWrapper" - ".supports_extension", return_value=(True, "")) - def test_delete_network_with_dhcp_and_router_and_ports_and_subnets( - self, mock_neutron_wrapper_supports_extension): + def test_delete_network(self): + self._nc.list_ports.return_value = {"ports": []} + self._nc.list_subnets.return_value = {"subnets": []} + self._nc.delete_network.return_value = "foo_deleted" + self.wrapper.delete_network( + {"id": "foo_id", "router_id": None, "subnets": [], "name": "x", + "status": "y", "external": False}) + self.assertFalse(self._nc.remove_gateway_router.called) + self.assertFalse(self._nc.remove_interface_router.called) + self.assertFalse(self._nc.client.delete_router.called) + self.assertFalse(self._nc.client.delete_subnet.called) + self._nc.delete_network.assert_called_once_with("foo_id") + + def test_delete_network_with_router_and_ports_and_subnets(self): - service = self.get_wrapper() - agents = ["foo_agent", "bar_agent"] subnets = ["foo_subnet", "bar_subnet"] ports = [{"id": "foo_port", "device_owner": "network:router_interface", "device_id": "rounttter"}, {"id": "bar_port", "device_owner": "network:dhcp"}] - service.client.list_dhcp_agent_hosting_networks.return_value = ( - {"agents": [{"id": agent_id} for agent_id in agents]}) - service.client.list_ports.return_value = ({"ports": ports}) - service.client.list_subnets.return_value = ( + self._nc.list_ports.return_value = ({"ports": ports}) + self._nc.list_subnets.return_value = ( {"subnets": [{"id": id_} for id_ in subnets]}) - service.client.delete_network.return_value = "foo_deleted" - result = service.delete_network( + self.wrapper.delete_network( {"id": "foo_id", "router_id": "foo_router", "subnets": subnets, - "lb_pools": []}) + "lb_pools": [], "name": "foo", "status": "x", "external": False}) - self.assertEqual("foo_deleted", result) - self.assertEqual(service.client.remove_gateway_router.mock_calls, + self.assertEqual(self._nc.remove_gateway_router.mock_calls, [mock.call("foo_router")]) - service.client.delete_port.assert_called_once_with(ports[1]["id"]) - service.client.remove_interface_router.assert_called_once_with( + self._nc.delete_port.assert_called_once_with(ports[1]["id"]) + self._nc.remove_interface_router.assert_called_once_with( ports[0]["device_id"], {"port_id": ports[0]["id"]}) - self.assertEqual(service.client.delete_subnet.mock_calls, - [mock.call(subnet_id) for subnet_id in subnets]) - service.client.delete_network.assert_called_once_with("foo_id") + self.assertEqual( + [mock.call(subnet_id) for subnet_id in subnets], + self._nc.delete_subnet.call_args_list + ) + self._nc.delete_network.assert_called_once_with("foo_id") @ddt.data({"exception_type": neutron_exceptions.NotFound, "should_raise": False}, @@ -295,193 +288,153 @@ class NeutronWrapperTestCase(test.TestCase): {"exception_type": KeyError, "should_raise": True}) @ddt.unpack - @mock.patch("rally_openstack.common.wrappers.network.NeutronWrapper" - ".supports_extension", return_value=(True, "")) def test_delete_network_with_router_throw_exception( - self, mock_neutron_wrapper_supports_extension, exception_type, - should_raise): + self, exception_type, should_raise): # Ensure cleanup context still move forward even # remove_interface_router throw NotFound/BadRequest exception - service = self.get_wrapper() - service.client.remove_interface_router.side_effect = exception_type - agents = ["foo_agent", "bar_agent"] + self._nc.remove_interface_router.side_effect = exception_type subnets = ["foo_subnet", "bar_subnet"] ports = [{"id": "foo_port", "device_owner": "network:router_interface", "device_id": "rounttter"}, {"id": "bar_port", "device_owner": "network:dhcp"}] - service.client.list_dhcp_agent_hosting_networks.return_value = ( - {"agents": [{"id": agent_id} for agent_id in agents]}) - service.client.list_ports.return_value = ({"ports": ports}) - service.client.delete_network.return_value = "foo_deleted" - service.client.list_subnets.return_value = {"subnets": [ + self._nc.list_ports.return_value = {"ports": ports} + self._nc.list_subnets.return_value = {"subnets": [ {"id": id_} for id_ in subnets]} if should_raise: - self.assertRaises(exception_type, service.delete_network, - {"id": "foo_id", "router_id": "foo_router", - "subnets": subnets, "lb_pools": []}) - - self.assertNotEqual(service.client.delete_subnet.mock_calls, - [mock.call(subnet_id) for subnet_id in - subnets]) - self.assertFalse(service.client.delete_network.called) + self.assertRaises( + exception_type, self.wrapper.delete_network, + {"id": "foo_id", "name": "foo", "router_id": "foo_router", + "subnets": subnets, "lb_pools": [], "status": "xxx", + "external": False}) + self.assertFalse(self._nc.delete_subnet.called) + self.assertFalse(self._nc.delete_network.called) else: - result = service.delete_network( - {"id": "foo_id", "router_id": "foo_router", "subnets": subnets, - "lb_pools": []}) + self.wrapper.delete_network( + {"id": "foo_id", "name": "foo", "status": "xxx", + "router_id": "foo_router", "subnets": subnets, + "lb_pools": [], "external": False}) - self.assertEqual("foo_deleted", result) - service.client.delete_port.assert_called_once_with(ports[1]["id"]) - service.client.remove_interface_router.assert_called_once_with( + self._nc.delete_port.assert_called_once_with(ports[1]["id"]) + self._nc.remove_interface_router.assert_called_once_with( ports[0]["device_id"], {"port_id": ports[0]["id"]}) - self.assertEqual(service.client.delete_subnet.mock_calls, - [mock.call(subnet_id) for subnet_id in subnets]) - service.client.delete_network.assert_called_once_with("foo_id") + self.assertEqual( + [mock.call(subnet_id) for subnet_id in subnets], + self._nc.delete_subnet.call_args_list + ) + self._nc.delete_network.assert_called_once_with("foo_id") - self.assertEqual(service.client.remove_gateway_router.mock_calls, - [mock.call("foo_router")]) + self._nc.remove_gateway_router.assert_called_once_with( + "foo_router") def test_list_networks(self): - service = self.get_wrapper() - service.client.list_networks.return_value = {"networks": "foo_nets"} - self.assertEqual("foo_nets", service.list_networks()) - service.client.list_networks.assert_called_once_with() + self._nc.list_networks.return_value = {"networks": "foo_nets"} + self.assertEqual("foo_nets", self.wrapper.list_networks()) + self._nc.list_networks.assert_called_once_with() - @mock.patch(SVC + "NeutronWrapper.external_networks") - def test_create_floating_ip(self, mock_neutron_wrapper_external_networks): - wrap = self.get_wrapper() - wrap.create_port = mock.Mock(return_value={"id": "port_id"}) - wrap.client.create_floatingip = mock.Mock( - return_value={"floatingip": {"id": "fip_id", - "floating_ip_address": "fip_ip"}}) + def test_create_floating_ip(self): + self._nc.create_port.return_value = {"port": {"id": "port_id"}} + self._nc.create_floatingip.return_value = { + "floatingip": {"id": "fip_id", "floating_ip_address": "fip_ip"}} - self.assertRaises(ValueError, wrap.create_floating_ip) + self.assertRaises(ValueError, self.wrapper.create_floating_ip) - mock_neutron_wrapper_external_networks.__get__ = lambda *args: [] + self._nc.list_networks.return_value = {"networks": []} self.assertRaises(network.NetworkWrapperException, - wrap.create_floating_ip, tenant_id="foo_tenant") + self.wrapper.create_floating_ip, + tenant_id="foo_tenant") - mock_neutron_wrapper_external_networks.__get__ = ( - lambda *args: [{"id": "ext_id"}] - ) - fip = wrap.create_floating_ip(tenant_id="foo_tenant", - port_id="port_id") + self._nc.list_networks.return_value = {"networks": [{"id": "ext_id"}]} + fip = self.wrapper.create_floating_ip( + tenant_id="foo_tenant", port_id="port_id") self.assertEqual({"id": "fip_id", "ip": "fip_ip"}, fip) - wrap.get_network = mock.Mock( - return_value={"id": "foo_net", "external": True}) - wrap.create_floating_ip(tenant_id="foo_tenant", ext_network="ext_net", - port_id="port_id") + self._nc.list_networks.return_value = {"networks": [ + {"id": "ext_net_id", "name": "ext_net", "router:external": True}]} + self.wrapper.create_floating_ip( + tenant_id="foo_tenant", ext_network="ext_net", port_id="port_id") - wrap.get_network = mock.Mock( - return_value={"id": "foo_net", "external": False}) - wrap.create_floating_ip(tenant_id="foo_tenant", port_id="port_id") - - self.assertRaises(network.NetworkWrapperException, - wrap.create_floating_ip, tenant_id="foo_tenant", - ext_network="ext_net") + self.assertRaises( + network.NetworkWrapperException, + self.wrapper.create_floating_ip, tenant_id="foo_tenant", + ext_network="ext_net_2") def test_delete_floating_ip(self): - wrap = self.get_wrapper() - wrap.delete_floating_ip("fip_id") - wrap.delete_floating_ip("fip_id", ignored_kwarg="bar") + self.wrapper.delete_floating_ip("fip_id") + self.wrapper.delete_floating_ip("fip_id", ignored_kwarg="bar") self.assertEqual([mock.call("fip_id")] * 2, - wrap.client.delete_floatingip.mock_calls) + self._nc.delete_floatingip.call_args_list) - @mock.patch(SVC + "NeutronWrapper.external_networks") - def test_create_router(self, mock_neutron_wrapper_external_networks): - wrap = self.get_wrapper() - wrap.client.create_router.return_value = {"router": "foo_router"} - wrap.client.list_extensions.return_value = { + def test_create_router(self): + self._nc.create_router.return_value = {"router": "foo_router"} + self._nc.list_extensions.return_value = { "extensions": [{"alias": "ext-gw-mode"}]} - mock_neutron_wrapper_external_networks.__get__ = ( - lambda *args: [{"id": "ext_id"}] - ) + self._nc.list_networks.return_value = {"networks": [{"id": "ext_id"}]} - router = wrap.create_router() - wrap.client.create_router.assert_called_once_with( + router = self.wrapper.create_router() + self._nc.create_router.assert_called_once_with( {"router": {"name": self.owner.generate_random_name.return_value}}) self.assertEqual("foo_router", router) - router = wrap.create_router(external=True, foo="bar") - wrap.client.create_router.assert_called_with( + self.wrapper.create_router(external=True, flavor_id="bar") + self._nc.create_router.assert_called_with( {"router": {"name": self.owner.generate_random_name.return_value, "external_gateway_info": { "network_id": "ext_id", "enable_snat": True}, - "foo": "bar"}}) + "flavor_id": "bar"}}) - @mock.patch(SVC + "NeutronWrapper.external_networks") - def test_create_router_without_ext_gw_mode_extension( - self, mock_neutron_wrapper_external_networks): - wrap = self.get_wrapper() - wrap.client.create_router.return_value = {"router": "foo_router"} - wrap.client.list_extensions.return_value = {"extensions": []} - mock_neutron_wrapper_external_networks.__get__ = ( - lambda *args: [{"id": "ext_id"}] - ) + def test_create_router_without_ext_gw_mode_extension(self): + self._nc.create_router.return_value = {"router": "foo_router"} + self._nc.list_extensions.return_value = {"extensions": []} + self._nc.list_networks.return_value = {"networks": [{"id": "ext_id"}]} - router = wrap.create_router() - wrap.client.create_router.assert_called_once_with( + router = self.wrapper.create_router() + self._nc.create_router.assert_called_once_with( {"router": {"name": self.owner.generate_random_name.return_value}}) self.assertEqual(router, "foo_router") - router = wrap.create_router(external=True, foo="bar") - wrap.client.create_router.assert_called_with( + self.wrapper.create_router(external=True, flavor_id="bar") + self._nc.create_router.assert_called_with( {"router": {"name": self.owner.generate_random_name.return_value, "external_gateway_info": {"network_id": "ext_id"}, - "foo": "bar"}}) + "flavor_id": "bar"}}) def test_create_port(self): - wrap = self.get_wrapper() - wrap.client.create_port.return_value = {"port": "foo_port"} + self._nc.create_port.return_value = {"port": "foo_port"} - port = wrap.create_port("foo_net") - wrap.client.create_port.assert_called_once_with( + port = self.wrapper.create_port("foo_net") + self._nc.create_port.assert_called_once_with( {"port": {"network_id": "foo_net", "name": self.owner.generate_random_name.return_value}}) self.assertEqual("foo_port", port) - port = wrap.create_port("foo_net", foo="bar") - wrap.client.create_port.assert_called_with( + port = self.wrapper.create_port("foo_net", foo="bar") + self.wrapper.client.create_port.assert_called_with( {"port": {"network_id": "foo_net", "name": self.owner.generate_random_name.return_value, "foo": "bar"}}) def test_supports_extension(self): - wrap = self.get_wrapper() - wrap.client.list_extensions.return_value = ( + self._nc.list_extensions.return_value = ( {"extensions": [{"alias": "extension"}]}) - self.assertTrue(wrap.supports_extension("extension")[0]) + self.assertTrue(self.wrapper.supports_extension("extension")[0]) - wrap.client.list_extensions.return_value = ( + self.wrapper.neutron._cached_supported_extensions = None + self._nc.list_extensions.return_value = ( {"extensions": [{"alias": "extension"}]}) - self.assertFalse(wrap.supports_extension("dummy-group")[0]) + self.assertFalse(self.wrapper.supports_extension("dummy-group")[0]) - wrap.client.list_extensions.return_value = {} - self.assertFalse(wrap.supports_extension("extension")[0]) + self.wrapper.neutron._cached_supported_extensions = None + self._nc.list_extensions.return_value = {"extensions": []} + self.assertFalse(self.wrapper.supports_extension("extension")[0]) class FunctionsTestCase(test.TestCase): - def test_generate_cidr(self): - with mock.patch("rally_openstack.common.wrappers.network.cidr_incr", - iter(range(1, 4))): - self.assertEqual("10.2.1.0/24", network.generate_cidr()) - self.assertEqual("10.2.2.0/24", network.generate_cidr()) - self.assertEqual("10.2.3.0/24", network.generate_cidr()) - - with mock.patch("rally_openstack.common.wrappers.network.cidr_incr", - iter(range(1, 4))): - start_cidr = "1.1.0.0/26" - self.assertEqual("1.1.0.64/26", network.generate_cidr(start_cidr)) - self.assertEqual("1.1.0.128/26", network.generate_cidr(start_cidr)) - self.assertEqual("1.1.0.192/26", network.generate_cidr(start_cidr)) - def test_wrap(self): mock_clients = mock.Mock() - mock_clients.nova().networks.list.return_value = [] config = {"fakearg": "fake"} owner = Owner() diff --git a/tests/unit/task/cleanup/test_resources.py b/tests/unit/task/cleanup/test_resources.py index 19c6bee3..08550903 100644 --- a/tests/unit/task/cleanup/test_resources.py +++ b/tests/unit/task/cleanup/test_resources.py @@ -149,16 +149,6 @@ class NeutronMixinTestCase(test.TestCase): neut.user = mock.MagicMock() self.assertEqual(neut.user.neutron.return_value, neut._manager()) - @mock.patch("%s.NeutronMixin._manager" % BASE) - def test_supports_extension(self, mock__manager): - mock__manager().list_extensions.return_value = { - "extensions": [{"alias": "foo"}, {"alias": "bar"}] - } - neut = self.get_neutron_mixin() - self.assertTrue(neut.supports_extension("foo")) - self.assertTrue(neut.supports_extension("bar")) - self.assertFalse(neut.supports_extension("foobar")) - def test_id(self): neut = self.get_neutron_mixin() neut.raw_resource = {"id": "test"} @@ -200,11 +190,12 @@ class NeutronLbaasV1MixinTestCase(test.TestCase): def get_neutron_lbaasv1_mixin(self, extensions=None): if extensions is None: extensions = [] - neut = resources.NeutronLbaasV1Mixin() + user = mock.MagicMock() + neut = resources.NeutronLbaasV1Mixin(user=user) neut._service = "neutron" neut._resource = "some_resource" neut._manager = mock.Mock() - neut._manager().list_extensions.return_value = { + user.neutron.return_value.list_extensions.return_value = { "extensions": [{"alias": ext} for ext in extensions] } return neut @@ -234,11 +225,13 @@ class NeutronLbaasV2MixinTestCase(test.TestCase): def get_neutron_lbaasv2_mixin(self, extensions=None): if extensions is None: extensions = [] - neut = resources.NeutronLbaasV2Mixin() + + user = mock.MagicMock() + neut = resources.NeutronLbaasV2Mixin(user=user) neut._service = "neutron" neut._resource = "some_resource" neut._manager = mock.Mock() - neut._manager().list_extensions.return_value = { + user.neutron.return_value.list_extensions.return_value = { "extensions": [{"alias": ext} for ext in extensions] } return neut @@ -310,7 +303,8 @@ class NeutronBgpvpnTestCase(test.TestCase): admin = mock.Mock() neut = resources.NeutronBgpvpn(admin=admin) neut._manager = mock.Mock() - neut._manager().list_extensions.return_value = { + nc = admin.neutron.return_value + nc.list_extensions.return_value = { "extensions": [{"alias": ext} for ext in extensions] } return neut diff --git a/tests/unit/task/scenarios/neutron/test_utils.py b/tests/unit/task/scenarios/neutron/test_utils.py index 73ac1fc7..5bcec458 100644 --- a/tests/unit/task/scenarios/neutron/test_utils.py +++ b/tests/unit/task/scenarios/neutron/test_utils.py @@ -22,6 +22,8 @@ from rally import exceptions from rally_openstack.task.scenarios.neutron import utils from tests.unit import test +NETWORK_SERVICE = "rally_openstack.common.services.network" +NET_UTILS = "%s.net_utils" % NETWORK_SERVICE NEUTRON_UTILS = "rally_openstack.task.scenarios.neutron.utils" @@ -31,11 +33,15 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): def setUp(self): super(NeutronScenarioTestCase, self).setUp() self.network = mock.Mock() - self.scenario = utils.NeutronScenario(self.context) + self._clients = mock.MagicMock() + self._nc = self._clients.neutron.return_value + self.scenario = utils.NeutronScenario(self.context, + clients=self._clients) self.random_name = "random_name" - self.scenario.generate_random_name = mock.Mock( - return_value=self.random_name) + name_generator = mock.Mock(return_value=self.random_name) + self.scenario.generate_random_name = name_generator + self.scenario.neutron._name_generator = name_generator def test__get_network_id(self): networks = [{"id": "foo-id", "name": "foo-network"}, @@ -44,52 +50,49 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): # Valid network-name network = "foo-network" - self.scenario._list_networks = mock.Mock(return_value=networks) + self._nc.list_networks = mock.Mock(return_value={"networks": networks}) resultant_network_id = self.scenario._get_network_id(network) self.assertEqual(network_id, resultant_network_id) - self.scenario._list_networks.assert_called_once_with() + self._nc.list_networks.assert_called_once_with() - self.scenario._list_networks.reset_mock() + self._nc.list_networks.reset_mock() # Valid network-id network = "foo-id" resultant_network_id = self.scenario._get_network_id(network) self.assertEqual(network_id, resultant_network_id) - self.scenario._list_networks.assert_called_once_with() - self.scenario._list_networks.reset_mock() + self._nc.list_networks.assert_called_once_with() + self._nc.list_networks.reset_mock() # Invalid network-name network = "absent-network" self.assertRaises(exceptions.NotFoundException, self.scenario._get_network_id, network) - self.scenario._list_networks.assert_called_once_with() + self._nc.list_networks.assert_called_once_with() def test_create_network(self): - self.clients("neutron").create_network.return_value = self.network + network = {"network": mock.Mock()} + self._nc.create_network.return_value = network network_data = {"admin_state_up": False} + + self.assertEqual(network, self.scenario._create_network(network_data)) + expected_network_data = {"network": network_data} - network = self.scenario._create_network(network_data) - self.assertEqual(self.network, network) - self.clients("neutron").create_network.assert_called_once_with( - expected_network_data) + network_data["name"] = self.scenario.generate_random_name.return_value + self._nc.create_network.assert_called_once_with(expected_network_data) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.create_network") def test_list_networks(self): networks_list = [] networks_dict = {"networks": networks_list} - self.clients("neutron").list_networks.return_value = networks_dict + self._nc.list_networks.return_value = networks_dict - # without atomic action - return_networks_list = self.scenario._list_networks() - self.assertEqual(networks_list, return_networks_list) - - # with atomic action return_networks_list = self.scenario._list_networks() self.assertEqual(networks_list, return_networks_list) self._test_atomic_action_timer(self.scenario.atomic_actions(), - "neutron.list_networks", count=2) + "neutron.list_networks", count=1) def test_show_network(self): network = { @@ -101,8 +104,9 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): } return_network = self.scenario._show_network(network) - self.assertEqual(self.clients("neutron").show_network.return_value, - return_network) + self.assertEqual( + {"network": self._nc.show_network.return_value["network"]}, + return_network) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.show_network") @@ -116,8 +120,9 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): } return_router = self.scenario._show_router(router) - self.assertEqual(self.clients("neutron").show_router.return_value, - return_router) + self.assertEqual( + {"router": self._nc.show_router.return_value["router"]}, + return_router) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.show_router") @@ -125,37 +130,35 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): expected_network = { "network": { "name": self.scenario.generate_random_name.return_value, - "admin_state_up": False, - "fakearg": "fake" + "admin_state_up": False } } - self.clients("neutron").update_network.return_value = expected_network + self._nc.update_network.return_value = expected_network network = {"network": {"name": "network-name", "id": "network-id"}} - network_update_args = {"name": "foo", - "admin_state_up": False, - "fakearg": "fake"} + network_update_args = {"name": "foo", "admin_state_up": False} result_network = self.scenario._update_network(network, network_update_args) - self.clients("neutron").update_network.assert_called_once_with( + self._nc.update_network.assert_called_once_with( network["network"]["id"], expected_network) self.assertEqual(expected_network, result_network) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.update_network") def test_delete_network(self): - network_create_args = {} - network = self.scenario._create_network(network_create_args) + net_id = "foo" + network = {"id": net_id} self.scenario._delete_network(network) + self._nc.delete_network.assert_called_once_with(net_id) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.delete_network") - @mock.patch("%s.network_wrapper" % NEUTRON_UTILS) - def test_create_subnet(self, mock_network_wrapper): + @mock.patch("%s.generate_cidr" % NET_UTILS) + def test_create_subnet(self, mock_generate_cidr): network_id = "fake-id" start_cidr = "192.168.0.0/24" - mock_network_wrapper.generate_cidr.return_value = "192.168.0.0/24" + mock_generate_cidr.return_value = (4, "192.168.0.0/24") network = {"network": {"id": network_id}} expected_subnet_data = { @@ -163,35 +166,33 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): "network_id": network_id, "cidr": start_cidr, "ip_version": netaddr.IPNetwork(start_cidr).version, - "name": self.scenario.generate_random_name.return_value + "name": self.scenario.generate_random_name.return_value, + "dns_nameservers": mock.ANY } } # Default options subnet_data = {"network_id": network_id} self.scenario._create_subnet(network, subnet_data, start_cidr) - self.clients("neutron").create_subnet.assert_called_once_with( + self._nc.create_subnet.assert_called_once_with( expected_subnet_data) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.create_subnet") - self.clients("neutron").create_subnet.reset_mock() + self._nc.create_subnet.reset_mock() # Custom options extras = {"cidr": "2001::/64", "allocation_pools": []} extras["ip_version"] = netaddr.IPNetwork(extras["cidr"]).version - mock_network_wrapper.generate_cidr.return_value = "2001::/64" + mock_generate_cidr.return_value = (6, "2001::/64") subnet_data.update(extras) expected_subnet_data["subnet"].update(extras) self.scenario._create_subnet(network, subnet_data) - self.clients("neutron").create_subnet.assert_called_once_with( - expected_subnet_data) + self._nc.create_subnet.assert_called_once_with(expected_subnet_data) def test_list_subnets(self): subnets = [{"name": "fake1"}, {"name": "fake2"}] - self.clients("neutron").list_subnets.return_value = { - "subnets": subnets - } + self._nc.list_subnets.return_value = {"subnets": subnets} result = self.scenario._list_subnets() self.assertEqual(subnets, result) self._test_atomic_action_timer(self.scenario.atomic_actions(), @@ -201,8 +202,9 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): subnet = {"subnet": {"name": "fake-name", "id": "fake-id"}} result_subnet = self.scenario._show_subnet(subnet) - self.assertEqual(self.clients("neutron").show_subnet.return_value, - result_subnet) + self.assertEqual( + {"subnet": self._nc.show_subnet.return_value["subnet"]}, + result_subnet) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.show_subnet") @@ -210,19 +212,17 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): expected_subnet = { "subnet": { "name": self.scenario.generate_random_name.return_value, - "enable_dhcp": False, - "fakearg": "fake" + "enable_dhcp": False } } - self.clients("neutron").update_subnet.return_value = expected_subnet + self._nc.update_subnet.return_value = expected_subnet subnet = {"subnet": {"name": "subnet-name", "id": "subnet-id"}} - subnet_update_args = {"name": "foo", "enable_dhcp": False, - "fakearg": "fake"} + subnet_update_args = {"name": "foo", "enable_dhcp": False} result_subnet = self.scenario._update_subnet(subnet, subnet_update_args) - self.clients("neutron").update_subnet.assert_called_once_with( + self._nc.update_subnet.assert_called_once_with( subnet["subnet"]["id"], expected_subnet) self.assertEqual(expected_subnet, result_subnet) self._test_atomic_action_timer(self.scenario.atomic_actions(), @@ -237,105 +237,96 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): "neutron.delete_subnet") def test_create_router(self): - router = mock.Mock() - self.clients("neutron").create_router.return_value = router + router = self._nc.create_router.return_value # Default options result_router = self.scenario._create_router({}) - self.clients("neutron").create_router.assert_called_once_with({ + self._nc.create_router.assert_called_once_with({ "router": { "name": self.scenario.generate_random_name.return_value } }) - self.assertEqual(result_router, router) + self.assertEqual({"router": router["router"]}, result_router) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.create_router") def test_create_router_with_ext_gw(self): - router = mock.Mock() - external_network = [{"id": "ext-net", "router:external": True}] - self.scenario._list_networks = mock.Mock(return_value=external_network) - self.clients("neutron").create_router.return_value = router - self.clients("neutron").list_extensions.return_value = { + net_id = "ext-net" + self._nc.list_networks.return_value = { + "networks": [{"id": net_id, "router:external": True}] + } + self._nc.list_extensions.return_value = { "extensions": [{"alias": "ext-gw-mode"}]} # External_gw options - gw_info = {"network_id": external_network[0]["id"], - "enable_snat": True} + gw_info = {"network_id": net_id, "enable_snat": True} router_data = { "name": self.scenario.generate_random_name.return_value, "external_gateway_info": gw_info } result_router = self.scenario._create_router({}, external_gw=True) - self.clients("neutron").create_router.assert_called_once_with( + self._nc.create_router.assert_called_once_with( {"router": router_data}) - self.assertEqual(result_router, router) + self.assertEqual( + {"router": self._nc.create_router.return_value["router"]}, + result_router + ) self._test_atomic_action_timer( self.scenario.atomic_actions(), "neutron.create_router") def test_create_router_with_ext_gw_but_no_ext_net(self): - router = mock.Mock() - external_network = [{"id": "ext-net", "router:external": False}] - self.scenario._list_networks = mock.Mock(return_value=external_network) - self.clients("neutron").create_router.return_value = router - self.clients("neutron").list_extensions.return_value = { - "extensions": [{"alias": "ext-gw-mode"}]} + self._nc.list_networks.return_value = {"networks": []} + self._nc.list_extensions.return_value = { + "extensions": [{"alias": "ext-gw-mode"}] + } # External_gw options with no external networks in list_networks() result_router = self.scenario._create_router({}, external_gw=True) - self.clients("neutron").create_router.assert_called_once_with({ + self._nc.create_router.assert_called_once_with({ "router": {"name": self.scenario.generate_random_name.return_value} }) - self.assertEqual(result_router, router) + self.assertEqual( + {"router": self._nc.create_router.return_value["router"]}, + result_router + ) + self._nc.list_networks.assert_called_once_with( + **{"router:external": True} + ) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.create_router") def test_create_router_with_ext_gw_but_no_ext_gw_mode_extension(self): - router = mock.Mock() - external_network = [{"id": "ext-net", "router:external": True}] - self.scenario._list_networks = mock.Mock(return_value=external_network) - self.clients("neutron").create_router.return_value = router - self.clients("neutron").list_extensions.return_value = { - "extensions": []} + net_id = "ext-net" + self._nc.list_networks.return_value = { + "networks": [{"id": net_id, "router:external": True}] + } + self._nc.list_extensions.return_value = {"extensions": []} + + result_router = self.scenario._create_router({}, external_gw=True) - # External_gw options - gw_info = {"network_id": external_network[0]["id"]} router_data = { "name": self.scenario.generate_random_name.return_value, - "external_gateway_info": gw_info + "external_gateway_info": {"network_id": net_id} } - result_router = self.scenario._create_router({}, external_gw=True) - self.clients("neutron").create_router.assert_called_once_with( - {"router": router_data}) - self.assertEqual(result_router, router) + + self._nc.create_router.assert_called_once_with({"router": router_data}) + self.assertEqual( + {"router": self._nc.create_router.return_value["router"]}, + result_router + ) self._test_atomic_action_timer( self.scenario.atomic_actions(), "neutron.create_router") - def test_create_router_explicit(self): - router = mock.Mock() - self.clients("neutron").create_router.return_value = router - - # Custom options - router_data = {"name": "explicit_name", "admin_state_up": True} - result_router = self.scenario._create_router(router_data) - self.clients("neutron").create_router.assert_called_once_with( - {"router": router_data}) - self.assertEqual(result_router, router) - self._test_atomic_action_timer(self.scenario.atomic_actions(), - "neutron.create_router") - def test_list_routers(self): routers = [mock.Mock()] - self.clients("neutron").list_routers.return_value = { - "routers": routers} + self._nc.list_routers.return_value = {"routers": routers} self.assertEqual(routers, self.scenario._list_routers()) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.list_routers") def test_list_agents(self): agents = [mock.Mock()] - self.clients("neutron").list_agents.return_value = { - "agents": agents} + self._nc.list_agents.return_value = {"agents": agents} self.assertEqual(agents, self.scenario._list_agents()) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.list_agents") @@ -344,11 +335,10 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): expected_router = { "router": { "name": self.scenario.generate_random_name.return_value, - "admin_state_up": False, - "fakearg": "fake" + "admin_state_up": False } } - self.clients("neutron").update_router.return_value = expected_router + self._nc.update_router.return_value = expected_router router = { "router": { @@ -357,34 +347,30 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): "admin_state_up": True } } - router_update_args = {"name": "foo", - "admin_state_up": False, - "fakearg": "fake"} + router_update_args = {"name": "foo", "admin_state_up": False} result_router = self.scenario._update_router(router, router_update_args) - self.clients("neutron").update_router.assert_called_once_with( + self._nc.update_router.assert_called_once_with( router["router"]["id"], expected_router) self.assertEqual(expected_router, result_router) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.update_router") def test_delete_router(self): - router = self.scenario._create_router({}) + router_id = "foo" + router = {"router": {"id": router_id}} self.scenario._delete_router(router) - self.clients("neutron").delete_router.assert_called_once_with( - router["router"]["id"]) + self._nc.delete_router.assert_called_once_with(router_id) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.delete_router") def test_remove_interface_router(self): subnet = {"name": "subnet-name", "id": "subnet-id"} - router_data = {"id": 1} - router = self.scenario._create_router(router_data) + router = {"id": 1} self.scenario._add_interface_router(subnet, router) self.scenario._remove_interface_router(subnet, router) - mock_remove_router = self.clients("neutron").remove_interface_router - mock_remove_router.assert_called_once_with( + self._nc.remove_interface_router.assert_called_once_with( router["id"], {"subnet_id": subnet["id"]}) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.remove_interface_router") @@ -403,14 +389,14 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): } } enable_snat = "fake_snat" - gw_info = {"network_id": ext_net["network"]["id"], - "enable_snat": enable_snat} - self.clients("neutron").list_extensions.return_value = { + self._nc.list_extensions.return_value = { "extensions": [{"alias": "ext-gw-mode"}]} self.scenario._add_gateway_router(router, ext_net, enable_snat) - self.clients("neutron").add_gateway_router.assert_called_once_with( - router["router"]["id"], gw_info) + self._nc.add_gateway_router.assert_called_once_with( + router["router"]["id"], + {"network_id": ext_net["network"]["id"], + "enable_snat": enable_snat}) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.add_gateway_router") @@ -427,13 +413,14 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): "id": "router-id" } } - gw_info = {"network_id": ext_net["network"]["id"]} - self.clients("neutron").list_extensions.return_value = { + self._nc.list_extensions.return_value = { "extensions": [{"alias": "ext-gw-mode"}]} self.scenario._add_gateway_router(router, ext_net) - self.clients("neutron").add_gateway_router.assert_called_once_with( - router["router"]["id"], gw_info) + self._nc.add_gateway_router.assert_called_once_with( + router["router"]["id"], + {"network_id": ext_net["network"]["id"]} + ) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.add_gateway_router") @@ -450,14 +437,12 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): "id": "router-id" } } - enable_snat = "fake_snat" - gw_info = {"network_id": ext_net["network"]["id"]} - self.clients("neutron").list_extensions.return_value = { + self._nc.list_extensions.return_value = { "extensions": {}} - self.scenario._add_gateway_router(router, ext_net, enable_snat) - self.clients("neutron").add_gateway_router.assert_called_once_with( - router["router"]["id"], gw_info) + self.scenario._add_gateway_router(router, ext_net, enable_snat=True) + self._nc.add_gateway_router.assert_called_once_with( + router["router"]["id"], {"network_id": ext_net["network"]["id"]}) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.add_gateway_router") @@ -469,7 +454,7 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): } } self.scenario._remove_gateway_router(router) - self.clients("neutron").remove_gateway_router.assert_called_once_with( + self._nc.remove_gateway_router.assert_called_once_with( router["router"]["id"]) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.remove_gateway_router") @@ -487,23 +472,21 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): # Defaults port_create_args = {} self.scenario._create_port(net, port_create_args) - self.clients("neutron" - ).create_port.assert_called_once_with(expected_port_args) + self._nc.create_port.assert_called_once_with(expected_port_args) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.create_port") - self.clients("neutron").create_port.reset_mock() + self._nc.create_port.reset_mock() # Custom options port_args = {"admin_state_up": True} expected_port_args["port"].update(port_args) self.scenario._create_port(net, port_args) - self.clients("neutron" - ).create_port.assert_called_once_with(expected_port_args) + self._nc.create_port.assert_called_once_with(expected_port_args) def test_list_ports(self): ports = [{"name": "port1"}, {"name": "port2"}] - self.clients("neutron").list_ports.return_value = {"ports": ports} + self._nc.list_ports.return_value = {"ports": ports} self.assertEqual(ports, self.scenario._list_ports()) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.list_ports") @@ -516,9 +499,9 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): "admin_state_up": True } } - self.clients("neutron").show_port.return_value = expect_port + self._nc.show_port.return_value = expect_port self.assertEqual(expect_port, self.scenario._show_port(expect_port)) - self.clients("neutron").show_port.assert_called_once_with( + self._nc.show_port.assert_called_once_with( expect_port["port"]["id"]) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.show_port") @@ -527,11 +510,10 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): expected_port = { "port": { "admin_state_up": False, - "fakearg": "fake", "name": self.scenario.generate_random_name.return_value } } - self.clients("neutron").update_port.return_value = expected_port + self._nc.update_port.return_value = expected_port port = { "port": { @@ -540,13 +522,10 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): "admin_state_up": True } } - port_update_args = { - "admin_state_up": False, - "fakearg": "fake" - } + port_update_args = {"admin_state_up": False} result_port = self.scenario._update_port(port, port_update_args) - self.clients("neutron").update_port.assert_called_once_with( + self._nc.update_port.assert_called_once_with( port["port"]["id"], expected_port) self.assertEqual(expected_port, result_port) self._test_atomic_action_timer(self.scenario.atomic_actions(), @@ -589,13 +568,9 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): self.scenario._create_network.assert_called_once_with( network_create_args or {}) - @mock.patch("%s.NeutronScenario._create_subnet" % NEUTRON_UTILS) - @mock.patch("%s.NeutronScenario._create_network" % NEUTRON_UTILS) - def test_create_network_and_subnets(self, - mock__create_network, - mock__create_subnet): - mock__create_network.return_value = {"network": {"id": "fake-id"}} - mock__create_subnet.return_value = { + def test_create_network_and_subnets(self): + self._nc.create_network.return_value = {"network": {"id": "fake-id"}} + self._nc.create_subnet.return_value = { "subnet": { "name": "subnet-name", "id": "subnet-id", @@ -613,40 +588,62 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): subnet_create_args=subnet_create_args, subnets_per_network=subnets_per_network) - mock__create_network.assert_called_once_with({}) - mock__create_subnet.assert_has_calls( - [mock.call({"network": {"id": "fake-id"}}, - {}, "1.0.0.0/24")] * subnets_per_network) + self._nc.create_network.assert_called_once_with( + {"network": {"name": self.random_name}} + ) + self.assertEqual( + [ + mock.call( + {"subnet": {"name": self.random_name, + "network_id": "fake-id", + "dns_nameservers": mock.ANY, + "ip_version": 4, "cidr": mock.ANY} + } + ) + ] * subnets_per_network, + self._nc.create_subnet.call_args_list + ) - mock__create_network.reset_mock() - mock__create_subnet.reset_mock() + self._nc.create_network.reset_mock() + self._nc.create_subnet.reset_mock() # Custom options self.scenario._create_network_and_subnets( network_create_args=network_create_args, - subnet_create_args={"allocation_pools": []}, + subnet_create_args={"allocation_pools": ["x"]}, subnet_cidr_start="10.10.10.0/24", subnets_per_network=subnets_per_network) - mock__create_network.assert_called_once_with({}) - mock__create_subnet.assert_has_calls( - [mock.call({"network": {"id": "fake-id"}}, - {"allocation_pools": []}, - "10.10.10.0/24")] * subnets_per_network) + self._nc.create_network.assert_called_once_with( + {"network": {"name": self.random_name}} + ) + self.assertEqual( + [ + mock.call( + {"subnet": {"name": self.random_name, + "network_id": "fake-id", + "allocation_pools": ["x"], + "dns_nameservers": mock.ANY, + "ip_version": 4, "cidr": mock.ANY} + } + ) + ] * subnets_per_network, + self._nc.create_subnet.call_args_list + ) def test_list_floating_ips(self): fips_list = [{"id": "floating-ip-id"}] fips_dict = {"floatingips": fips_list} - self.clients("neutron").list_floatingips.return_value = fips_dict + self._nc.list_floatingips.return_value = fips_dict self.assertEqual(self.scenario._list_floating_ips(), - self.clients("neutron").list_floatingips.return_value) + self._nc.list_floatingips.return_value) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.list_floating_ips") def test_delete_floating_ip(self): fip = {"floatingip": {"id": "fake-id"}} self.scenario._delete_floating_ip(fip["floatingip"]) - self.clients("neutron").delete_floatingip.assert_called_once_with( + self._nc.delete_floatingip.assert_called_once_with( fip["floatingip"]["id"]) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.delete_floating_ip") @@ -655,7 +652,7 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): fip = {"id": "fip-id"} port = {"id": "port-id"} self.scenario._associate_floating_ip(fip, port) - self.clients("neutron").update_floatingip.assert_called_once_with( + self._nc.update_floatingip.assert_called_once_with( "fip-id", {"floatingip": {"port_id": "port-id"}}) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.associate_floating_ip") @@ -663,7 +660,7 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): def test_dissociate_floating_ip(self): fip = {"id": "fip-id"} self.scenario._dissociate_floating_ip(fip) - self.clients("neutron").update_floatingip.assert_called_once_with( + self._nc.update_floatingip.assert_called_once_with( "fip-id", {"floatingip": {"port_id": None}}) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.dissociate_floating_ip") @@ -672,32 +669,49 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): {}, {"router_create_args": {"admin_state_up": False}}, {"network_create_args": {"router:external": True}, - "subnet_create_args": {"allocation_pools": []}, - "subnet_cidr_start": "default_cidr", + "subnet_create_args": {"allocation_pools": ["x"]}, "subnets_per_network": 3, "router_create_args": {"admin_state_up": False}}) @ddt.unpack def test_create_network_structure(self, network_create_args=None, subnet_create_args=None, subnet_cidr_start=None, - subnets_per_network=None, + subnets_per_network=1, router_create_args=None): - network = mock.MagicMock() + network_id = "net-id" + network = {"network": {"id": network_id}} router_create_args = router_create_args or {} subnets = [] + subnet_create_calls = [] routers = [] router_create_calls = [] - for i in range(subnets_per_network or 1): - subnets.append(mock.MagicMock()) - routers.append(mock.MagicMock()) - router_create_calls.append(mock.call(router_create_args)) + for i in range(subnets_per_network): + subnets.append({"subnet": mock.MagicMock()}) + routers.append({"router": mock.MagicMock()}) + subnet_create_calls.append( + mock.call({ + "subnet": { + "network_id": network_id, + "name": self.random_name, + "dns_nameservers": mock.ANY, + "ip_version": 4, + "cidr": mock.ANY, + **(subnet_create_args or {}) + } + })) + router_create_calls.append( + mock.call({ + "router": { + "name": self.random_name, + **(router_create_args or {}) + } + })) - self.scenario._create_network = mock.Mock(return_value=network) - self.scenario._create_subnets = mock.Mock(return_value=subnets) - self.scenario._create_router = mock.Mock(side_effect=routers) - self.scenario._add_interface_router = mock.Mock() + self._nc.create_network.return_value = network + self._nc.create_subnet.side_effect = subnets + self._nc.create_router.side_effect = routers actual = self.scenario._create_network_structure(network_create_args, subnet_create_args, @@ -705,19 +719,27 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): subnets_per_network, router_create_args) self.assertEqual((network, subnets, routers), actual) - self.scenario._create_network.assert_called_once_with( - network_create_args or {}) - self.scenario._create_subnets.assert_called_once_with( - network, - subnet_create_args, - subnet_cidr_start, - subnets_per_network) - self.scenario._create_router.assert_has_calls(router_create_calls) + network_create_args = network_create_args or {} + network_create_args["name"] = self.random_name + self._nc.create_network.assert_called_once_with( + {"network": network_create_args}) + self.assertEqual( + subnet_create_calls, self._nc.create_subnet.call_args_list + ) + self.assertEqual( + router_create_calls, self._nc.create_router.call_args_list + ) - add_iface_calls = [mock.call(subnets[i]["subnet"], - routers[i]["router"]) - for i in range(subnets_per_network or 1)] - self.scenario._add_interface_router.assert_has_calls(add_iface_calls) + add_iface_calls = [ + mock.call( + routers[i]["router"]["id"], + {"subnet_id": subnets[i]["subnet"]["id"]} + ) + for i in range(subnets_per_network or 1)] + self.assertEqual( + add_iface_calls, + self._nc.add_interface_router.call_args_list + ) def test_delete_v1_pool(self): pool = {"pool": {"id": "fake-id"}} @@ -794,8 +816,7 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.update_vip") - @mock.patch("%s.NeutronScenario.generate_random_name" % NEUTRON_UTILS) - def test_create_security_group(self, mock_generate_random_name): + def test_create_security_group(self): security_group_create_args = {"description": "Fake security group"} expected_security_group = { "security_group": { @@ -804,8 +825,7 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): "description": "Fake security group" } } - self.clients("neutron").create_security_group = mock.Mock( - return_value=expected_security_group) + self._nc.create_security_group.return_value = expected_security_group security_group_data = { "security_group": @@ -815,7 +835,7 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): resultant_security_group = self.scenario._create_security_group( **security_group_create_args) self.assertEqual(expected_security_group, resultant_security_group) - self.clients("neutron").create_security_group.assert_called_once_with( + self._nc.create_security_group.assert_called_once_with( security_group_data) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.create_security_group") @@ -823,11 +843,11 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): def test_list_security_groups(self): security_groups_list = [{"id": "security-group-id"}] security_groups_dict = {"security_groups": security_groups_list} - self.clients("neutron").list_security_groups = mock.Mock( + self._nc.list_security_groups = mock.Mock( return_value=security_groups_dict) self.assertEqual( self.scenario._list_security_groups(), - self.clients("neutron").list_security_groups.return_value) + self._nc.list_security_groups.return_value) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.list_security_groups") @@ -835,9 +855,11 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): security_group = {"security_group": {"id": "fake-id"}} result = self.scenario._show_security_group(security_group) self.assertEqual( - result, - self.clients("neutron").show_security_group.return_value) - self.clients("neutron").show_security_group.assert_called_once_with( + {"security_group": + self._nc.show_security_group.return_value["security_group"]}, + result + ) + self._nc.show_security_group.assert_called_once_with( security_group["security_group"]["id"]) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.show_security_group") @@ -845,7 +867,7 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): def test_delete_security_group(self): security_group = {"security_group": {"id": "fake-id"}} self.scenario._delete_security_group(security_group) - self.clients("neutron").delete_security_group.assert_called_once_with( + self._nc.delete_security_group.assert_called_once_with( security_group["security_group"]["id"]) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.delete_security_group") @@ -865,11 +887,12 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): } } - self.clients("neutron").update_security_group = mock.Mock( - return_value=expected_security_group) + self._nc.update_security_group.return_value = expected_security_group + result_security_group = self.scenario._update_security_group( security_group, description="Updated") - self.clients("neutron").update_security_group.assert_called_once_with( + + self._nc.update_security_group.assert_called_once_with( security_group["security_group"]["id"], {"security_group": { "description": "Updated", @@ -881,7 +904,7 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): def test_create_security_group_rule(self): security_group_rule_args = {"description": "Fake Rule"} - expected_security_group_rule = { + expected_rules = { "security_group_rule": { "id": "fake-id", "security_group_id": "security-group-id", @@ -890,9 +913,7 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): "description": "Fake Rule" } } - client = self.clients("neutron") - client.create_security_group_rule = mock.Mock( - return_value=expected_security_group_rule) + self._nc.create_security_group_rule.return_value = expected_rules security_group_rule_data = { "security_group_rule": @@ -903,9 +924,9 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): } result_security_group_rule = self.scenario._create_security_group_rule( "security-group-id", **security_group_rule_args) - self.assertEqual(expected_security_group_rule, + self.assertEqual(expected_rules, result_security_group_rule) - client.create_security_group_rule.assert_called_once_with( + self._nc.create_security_group_rule.assert_called_once_with( security_group_rule_data) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.create_security_group_rule") @@ -915,27 +936,26 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): security_group_rules_dict = { "security_group_rules": security_group_rules_list} - self.clients("neutron").list_security_group_rules = mock.Mock( + self._nc.list_security_group_rules = mock.Mock( return_value=security_group_rules_dict) self.assertEqual( self.scenario._list_security_group_rules(), - self.clients("neutron").list_security_group_rules.return_value) + self._nc.list_security_group_rules.return_value) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.list_security_group_rules") def test_show_security_group_rule(self): return_rule = self.scenario._show_security_group_rule(1) - self.assertEqual( - self.clients("neutron").show_security_group_rule.return_value, - return_rule) + expected = self._nc.show_security_group_rule.return_value + expected = {"security_group_rule": expected["security_group_rule"]} + self.assertEqual(expected, return_rule) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.show_security_group_rule") def test_delete_security_group_rule(self): self.scenario._delete_security_group_rule(1) - clients = self.clients("neutron") - clients.delete_security_group_rule.assert_called_once_with(1) + self._nc.delete_security_group_rule.assert_called_once_with(1) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.delete_security_group_rule") @@ -1015,56 +1035,71 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): expected_vip_data) @ddt.data( - {}, {"floating_ip_args": {}}, {"floating_ip_args": {"floating_ip_address": "1.0.0.1"}}, ) @ddt.unpack - def test__create_floating_ip(self, floating_ip_args=None): + def test__create_floating_ip(self, floating_ip_args): floating_network = "floating" fip = {"floatingip": {"id": "fip-id"}} network_id = "net-id" - floating_ip_args = floating_ip_args or {} - self.clients("neutron").create_floatingip.return_value = fip - mock_get_network_id = self.scenario._get_network_id = mock.Mock() - mock_get_network_id.return_value = network_id - args = {"floating_network_id": network_id, - "description": "random_name"} - args.update(floating_ip_args) - expected_fip_data = {"floatingip": args} + self._nc.create_floatingip.return_value = fip + self._nc.list_networks.return_value = { + "networks": [ + {"id": "id-1", "name": "xxx", + "router:external": True}, + {"id": network_id, "name": floating_network, + "router:external": True} + ] + } + expected_fip_data = { + "floatingip": { + "floating_network_id": network_id, + "description": "random_name", + **floating_ip_args + } + } + resultant_fip = self.scenario._create_floatingip( floating_network, **floating_ip_args) + self.assertEqual(fip, resultant_fip) - self.clients("neutron").create_floatingip.assert_called_once_with( + self._nc.create_floatingip.assert_called_once_with( expected_fip_data) - mock_get_network_id.assert_called_once_with(floating_network) self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.create_floating_ip") - @mock.patch("%s.LOG.info" % NEUTRON_UTILS) + @mock.patch("%s.neutron.LOG.info" % NETWORK_SERVICE) def test__create_floating_ip_in_pre_newton_openstack(self, mock_log_info): + from neutronclient.common import exceptions as n_exceptions + floating_network = "floating" fip = {"floatingip": {"id": "fip-id"}} network_id = "net-id" - self.clients("neutron").create_floatingip.return_value = fip - mock_get_network_id = self.scenario._get_network_id = mock.Mock() - mock_get_network_id.return_value = network_id + self._nc.create_floatingip.return_value = fip + self._nc.list_networks.return_value = { + "networks": [ + {"id": "id-1", "name": "xxx", + "router:external": True}, + {"id": network_id, "name": floating_network, + "router:external": True} + ] + } - from neutronclient.common import exceptions as n_exceptions e = n_exceptions.BadRequest("Unrecognized attribute(s) 'description'") - self.clients("neutron").create_floatingip.side_effect = e + self._nc.create_floatingip.side_effect = e a_e = self.assertRaises(n_exceptions.BadRequest, self.scenario._create_floatingip, floating_network) + self.assertEqual(e, a_e) self.assertTrue(mock_log_info.called) expected_fip_data = {"floatingip": {"floating_network_id": network_id, "description": "random_name"}} - self.clients("neutron").create_floatingip.assert_called_once_with( - expected_fip_data) - mock_get_network_id.assert_called_once_with(floating_network) + self._nc.create_floatingip.assert_called_once_with(expected_fip_data) + self._nc.list_networks.assert_called_once_with() self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.create_floating_ip") @@ -1371,10 +1406,9 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): def test__list_ports_by_device_id(self): device_id = "device-id" self.scenario._list_ports_by_device_id(device_id) - self.clients("neutron").list_ports.assert_called_once_with( - device_id=device_id) + self._nc.list_ports.assert_called_once_with(device_id=device_id) self._test_atomic_action_timer(self.scenario.atomic_actions(), - "neutron.list_ports_by_device_id") + "neutron.list_ports") def test__list_subports_by_trunk(self): trunk_id = "trunk-id" @@ -1398,26 +1432,32 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): "neutron._add_subports_to_trunk") -class NeutronScenarioFunctionalTestCase(test.FakeClientsScenarioTestCase): +class NeutronScenarioFunctionalTestCase(test.ScenarioTestCase): - @mock.patch("%s.network_wrapper.generate_cidr" % NEUTRON_UTILS) + @mock.patch("%s.generate_cidr" % NET_UTILS) def test_functional_create_network_and_subnets(self, mock_generate_cidr): - scenario = utils.NeutronScenario(context=self.context) + clients = mock.MagicMock() + scenario = utils.NeutronScenario(context=self.context, + clients=clients) network_create_args = {} subnet_create_args = {} subnets_per_network = 5 subnet_cidr_start = "1.1.1.0/24" - cidrs = ["1.1.%d.0/24" % i for i in range(subnets_per_network)] + cidrs = [(4, "1.1.%d.0/24" % i) for i in range(subnets_per_network)] cidrs_ = iter(cidrs) mock_generate_cidr.side_effect = lambda **kw: next(cidrs_) - network, subnets = scenario._create_network_and_subnets( + scenario._create_network_and_subnets( network_create_args, subnet_create_args, subnets_per_network, subnet_cidr_start) # This checks both data (cidrs seem to be enough) and subnets number - result_cidrs = sorted([s["subnet"]["cidr"] for s in subnets]) + nc = clients.neutron.return_value + result_cidrs = sorted( + (4, arg[0]["subnet"]["cidr"]) + for arg, _kwarg in nc.create_subnet.call_args_list + ) self.assertEqual(cidrs, result_cidrs) diff --git a/tests/unit/task/scenarios/nova/test_utils.py b/tests/unit/task/scenarios/nova/test_utils.py index 0a3385c2..6bba15da 100644 --- a/tests/unit/task/scenarios/nova/test_utils.py +++ b/tests/unit/task/scenarios/nova/test_utils.py @@ -534,8 +534,10 @@ class NovaScenarioTestCase(test.ScenarioTestCase): "nova.get_console_url_server") def test__associate_floating_ip(self): - nova_scenario = utils.NovaScenario(context=self.context) - neutronclient = nova_scenario.clients("neutron") + clients = mock.MagicMock() + nova_scenario = utils.NovaScenario(context=self.context, + clients=clients) + neutronclient = clients.neutron.return_value neutronclient.list_ports.return_value = {"ports": [{"id": "p1"}, {"id": "p2"}]} @@ -568,8 +570,10 @@ class NovaScenarioTestCase(test.ScenarioTestCase): "nova.associate_floating_ip", count=2) def test__associate_floating_ip_deprecated_behavior(self): - nova_scenario = utils.NovaScenario(context=self.context) - neutronclient = nova_scenario.clients("neutron") + clients = mock.MagicMock() + nova_scenario = utils.NovaScenario(context=self.context, + clients=clients) + neutronclient = clients.neutron.return_value neutronclient.list_ports.return_value = {"ports": [{"id": "p1"}, {"id": "p2"}]} @@ -587,7 +591,7 @@ class NovaScenarioTestCase(test.ScenarioTestCase): ) neutronclient.list_floatingips.assert_called_once_with( - tenant_id="fake_tenant") + floating_ip_address=fip_ip) # it is an old behavior. let's check that it was not called self.assertFalse(self.server.add_floating_ip.called) @@ -596,8 +600,10 @@ class NovaScenarioTestCase(test.ScenarioTestCase): "nova.associate_floating_ip") def test__dissociate_floating_ip(self): - nova_scenario = utils.NovaScenario(context=self.context) - neutronclient = nova_scenario.clients("neutron") + clients = mock.MagicMock() + nova_scenario = utils.NovaScenario(context=self.context, + clients=clients) + neutronclient = clients.neutron.return_value fip_ip = "172.168.0.1" fip_id = "some" @@ -628,8 +634,10 @@ class NovaScenarioTestCase(test.ScenarioTestCase): "nova.dissociate_floating_ip", count=2) def test__disassociate_floating_ip_deprecated_behavior(self): - nova_scenario = utils.NovaScenario(context=self.context) - neutronclient = nova_scenario.clients("neutron") + clients = mock.MagicMock() + nova_scenario = utils.NovaScenario(context=self.context, + clients=clients) + neutronclient = clients.neutron.return_value fip_id = "fip1" fip_ip = "172.168.0.1" @@ -645,7 +653,7 @@ class NovaScenarioTestCase(test.ScenarioTestCase): ) neutronclient.list_floatingips.assert_called_once_with( - tenant_id="fake_tenant") + floating_ip_address=fip_ip) self._test_atomic_action_timer(nova_scenario.atomic_actions(), "nova.dissociate_floating_ip")