Moves subnets into a submodule
Refactors plugin.py by moving subnets functions and their associated tests into submodules for better plugin readability.
This commit is contained in:
266
quark/plugin.py
266
quark/plugin.py
@@ -16,14 +16,11 @@
|
|||||||
"""
|
"""
|
||||||
v2 Neutron Plug-in API Quark Implementation
|
v2 Neutron Plug-in API Quark Implementation
|
||||||
"""
|
"""
|
||||||
import netaddr
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from sqlalchemy.orm import sessionmaker, scoped_session
|
from sqlalchemy.orm import sessionmaker, scoped_session
|
||||||
from zope import sqlalchemy as zsa
|
from zope import sqlalchemy as zsa
|
||||||
|
|
||||||
#FIXME(mdietz): remove once all resources have moved into submods
|
|
||||||
from neutron.common import config as neutron_cfg
|
|
||||||
from neutron.common import exceptions
|
from neutron.common import exceptions
|
||||||
from neutron.db import api as neutron_db_api
|
from neutron.db import api as neutron_db_api
|
||||||
from neutron.extensions import providernet as pnet
|
from neutron.extensions import providernet as pnet
|
||||||
@@ -45,6 +42,7 @@ from quark.plugin_modules import mac_address_ranges
|
|||||||
from quark.plugin_modules import ports
|
from quark.plugin_modules import ports
|
||||||
from quark.plugin_modules import routes
|
from quark.plugin_modules import routes
|
||||||
from quark.plugin_modules import security_groups
|
from quark.plugin_modules import security_groups
|
||||||
|
from quark.plugin_modules import subnets
|
||||||
from quark import plugin_views as v
|
from quark import plugin_views as v
|
||||||
from quark import utils
|
from quark import utils
|
||||||
|
|
||||||
@@ -88,244 +86,6 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2,
|
|||||||
self.ipam_reuse_after = CONF.QUARK.ipam_reuse_after
|
self.ipam_reuse_after = CONF.QUARK.ipam_reuse_after
|
||||||
neutron_db_api.register_models(base=models.BASEV2)
|
neutron_db_api.register_models(base=models.BASEV2)
|
||||||
|
|
||||||
def _validate_subnet_cidr(self, context, network_id, new_subnet_cidr):
|
|
||||||
"""Validate the CIDR for a subnet.
|
|
||||||
|
|
||||||
Verifies the specified CIDR does not overlap with the ones defined
|
|
||||||
for the other subnets specified for this network, or with any other
|
|
||||||
CIDR if overlapping IPs are disabled.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if neutron_cfg.cfg.CONF.allow_overlapping_ips:
|
|
||||||
return
|
|
||||||
|
|
||||||
new_subnet_ipset = netaddr.IPSet([new_subnet_cidr])
|
|
||||||
|
|
||||||
# Using admin context here, in case we actually share networks later
|
|
||||||
subnet_list = db_api.subnet_find(context.elevated(),
|
|
||||||
network_id=network_id)
|
|
||||||
for subnet in subnet_list:
|
|
||||||
if (netaddr.IPSet([subnet.cidr]) & new_subnet_ipset):
|
|
||||||
# don't give out details of the overlapping subnet
|
|
||||||
err_msg = (_("Requested subnet with cidr: %(cidr)s for "
|
|
||||||
"network: %(network_id)s overlaps with another "
|
|
||||||
"subnet") %
|
|
||||||
{'cidr': new_subnet_cidr,
|
|
||||||
'network_id': network_id})
|
|
||||||
LOG.error(_("Validation for CIDR: %(new_cidr)s failed - "
|
|
||||||
"overlaps with subnet %(subnet_id)s "
|
|
||||||
"(CIDR: %(cidr)s)"),
|
|
||||||
{'new_cidr': new_subnet_cidr,
|
|
||||||
'subnet_id': subnet.id,
|
|
||||||
'cidr': subnet.cidr})
|
|
||||||
raise exceptions.InvalidInput(error_message=err_msg)
|
|
||||||
|
|
||||||
def create_subnet(self, context, subnet):
|
|
||||||
"""Create a subnet.
|
|
||||||
|
|
||||||
Create a subnet which represents a range of IP addresses
|
|
||||||
that can be allocated to devices
|
|
||||||
|
|
||||||
: param context: neutron api request context
|
|
||||||
: param subnet: dictionary describing the subnet, with keys
|
|
||||||
as listed in the RESOURCE_ATTRIBUTE_MAP object in
|
|
||||||
neutron/api/v2/attributes.py. All keys will be populated.
|
|
||||||
"""
|
|
||||||
LOG.info("create_subnet for tenant %s" % context.tenant_id)
|
|
||||||
net_id = subnet["subnet"]["network_id"]
|
|
||||||
|
|
||||||
net = db_api.network_find(context, id=net_id, scope=db_api.ONE)
|
|
||||||
if not net:
|
|
||||||
raise exceptions.NetworkNotFound(net_id=net_id)
|
|
||||||
|
|
||||||
sub_attrs = subnet["subnet"]
|
|
||||||
|
|
||||||
self._validate_subnet_cidr(context, net_id, sub_attrs["cidr"])
|
|
||||||
|
|
||||||
cidr = netaddr.IPNetwork(sub_attrs["cidr"])
|
|
||||||
gateway_ip = utils.pop_param(sub_attrs, "gateway_ip", str(cidr[1]))
|
|
||||||
dns_ips = utils.pop_param(sub_attrs, "dns_nameservers", [])
|
|
||||||
host_routes = utils.pop_param(sub_attrs, "host_routes", [])
|
|
||||||
allocation_pools = utils.pop_param(sub_attrs, "allocation_pools", [])
|
|
||||||
|
|
||||||
new_subnet = db_api.subnet_create(context, **sub_attrs)
|
|
||||||
|
|
||||||
default_route = None
|
|
||||||
for route in host_routes:
|
|
||||||
netaddr_route = netaddr.IPNetwork(route["destination"])
|
|
||||||
if netaddr_route.value == routes.DEFAULT_ROUTE.value:
|
|
||||||
default_route = route
|
|
||||||
gateway_ip = default_route["nexthop"]
|
|
||||||
new_subnet["routes"].append(db_api.route_create(
|
|
||||||
context, cidr=route["destination"], gateway=route["nexthop"]))
|
|
||||||
|
|
||||||
if default_route is None:
|
|
||||||
new_subnet["routes"].append(db_api.route_create(
|
|
||||||
context, cidr=str(routes.DEFAULT_ROUTE), gateway=gateway_ip))
|
|
||||||
|
|
||||||
for dns_ip in dns_ips:
|
|
||||||
new_subnet["dns_nameservers"].append(db_api.dns_create(
|
|
||||||
context, ip=netaddr.IPAddress(dns_ip)))
|
|
||||||
|
|
||||||
if allocation_pools:
|
|
||||||
exclude = netaddr.IPSet([cidr])
|
|
||||||
for p in allocation_pools:
|
|
||||||
x = netaddr.IPSet(netaddr.IPRange(p["start"], p["end"]))
|
|
||||||
exclude = exclude - x
|
|
||||||
new_subnet["ip_policy"] = db_api.ip_policy_create(context,
|
|
||||||
exclude=exclude)
|
|
||||||
# HACK(amir): force backref for ip_policy
|
|
||||||
if not new_subnet["network"]:
|
|
||||||
new_subnet["network"] = net
|
|
||||||
subnet_dict = v._make_subnet_dict(new_subnet,
|
|
||||||
default_route=routes.DEFAULT_ROUTE)
|
|
||||||
subnet_dict["gateway_ip"] = gateway_ip
|
|
||||||
return subnet_dict
|
|
||||||
|
|
||||||
def update_subnet(self, context, id, subnet):
|
|
||||||
"""Update values of a subnet.
|
|
||||||
|
|
||||||
: param context: neutron api request context
|
|
||||||
: param id: UUID representing the subnet to update.
|
|
||||||
: param subnet: dictionary with keys indicating fields to update.
|
|
||||||
valid keys are those that have a value of True for 'allow_put'
|
|
||||||
as listed in the RESOURCE_ATTRIBUTE_MAP object in
|
|
||||||
neutron/api/v2/attributes.py.
|
|
||||||
"""
|
|
||||||
LOG.info("update_subnet %s for tenant %s" %
|
|
||||||
(id, context.tenant_id))
|
|
||||||
|
|
||||||
subnet_db = db_api.subnet_find(context, id=id, scope=db_api.ONE)
|
|
||||||
if not subnet_db:
|
|
||||||
raise exceptions.SubnetNotFound(id=id)
|
|
||||||
|
|
||||||
s = subnet["subnet"]
|
|
||||||
|
|
||||||
dns_ips = s.pop("dns_nameservers", [])
|
|
||||||
host_routes = s.pop("host_routes", [])
|
|
||||||
gateway_ip = s.pop("gateway_ip", None)
|
|
||||||
|
|
||||||
if gateway_ip:
|
|
||||||
default_route = None
|
|
||||||
for route in host_routes:
|
|
||||||
netaddr_route = netaddr.IPNetwork(route["destination"])
|
|
||||||
if netaddr_route.value == routes.DEFAULT_ROUTE.value:
|
|
||||||
default_route = route
|
|
||||||
break
|
|
||||||
if default_route is None:
|
|
||||||
route_model = db_api.route_find(
|
|
||||||
context, cidr=str(routes.DEFAULT_ROUTE), subnet_id=id,
|
|
||||||
scope=db_api.ONE)
|
|
||||||
if route_model:
|
|
||||||
db_api.route_update(context, route_model,
|
|
||||||
gateway=gateway_ip)
|
|
||||||
else:
|
|
||||||
db_api.route_create(context,
|
|
||||||
cidr=str(routes.DEFAULT_ROUTE),
|
|
||||||
gateway=gateway_ip, subnet_id=id)
|
|
||||||
|
|
||||||
if dns_ips:
|
|
||||||
subnet_db["dns_nameservers"] = []
|
|
||||||
for dns_ip in dns_ips:
|
|
||||||
subnet_db["dns_nameservers"].append(db_api.dns_create(
|
|
||||||
context,
|
|
||||||
ip=netaddr.IPAddress(dns_ip)))
|
|
||||||
|
|
||||||
if host_routes:
|
|
||||||
subnet_db["routes"] = []
|
|
||||||
for route in host_routes:
|
|
||||||
subnet_db["routes"].append(db_api.route_create(
|
|
||||||
context, cidr=route["destination"], gateway=route["nexthop"]))
|
|
||||||
|
|
||||||
subnet = db_api.subnet_update(context, subnet_db, **s)
|
|
||||||
return v._make_subnet_dict(subnet, default_route=routes.DEFAULT_ROUTE)
|
|
||||||
|
|
||||||
def get_subnet(self, context, id, fields=None):
|
|
||||||
"""Retrieve a subnet.
|
|
||||||
|
|
||||||
: param context: neutron api request context
|
|
||||||
: param id: UUID representing the subnet to fetch.
|
|
||||||
: param fields: a list of strings that are valid keys in a
|
|
||||||
subnet dictionary as listed in the RESOURCE_ATTRIBUTE_MAP
|
|
||||||
object in neutron/api/v2/attributes.py. Only these fields
|
|
||||||
will be returned.
|
|
||||||
"""
|
|
||||||
LOG.info("get_subnet %s for tenant %s with fields %s" %
|
|
||||||
(id, context.tenant_id, fields))
|
|
||||||
subnet = db_api.subnet_find(context, id=id, scope=db_api.ONE)
|
|
||||||
if not subnet:
|
|
||||||
raise exceptions.SubnetNotFound(subnet_id=id)
|
|
||||||
|
|
||||||
# Check the network_id against the strategies
|
|
||||||
net_id = subnet["network_id"]
|
|
||||||
net_id = STRATEGY.get_parent_network(net_id)
|
|
||||||
subnet["network_id"] = net_id
|
|
||||||
|
|
||||||
return v._make_subnet_dict(subnet, default_route=routes.DEFAULT_ROUTE)
|
|
||||||
|
|
||||||
def get_subnets(self, context, filters=None, fields=None):
|
|
||||||
"""Retrieve a list of subnets.
|
|
||||||
|
|
||||||
The contents of the list depends on the identity of the user
|
|
||||||
making the request (as indicated by the context) as well as any
|
|
||||||
filters.
|
|
||||||
: param context: neutron api request context
|
|
||||||
: param filters: a dictionary with keys that are valid keys for
|
|
||||||
a subnet as listed in the RESOURCE_ATTRIBUTE_MAP object
|
|
||||||
in neutron/api/v2/attributes.py. Values in this dictiontary
|
|
||||||
are an iterable containing values that will be used for an exact
|
|
||||||
match comparison for that value. Each result returned by this
|
|
||||||
function will have matched one of the values for each key in
|
|
||||||
filters.
|
|
||||||
: param fields: a list of strings that are valid keys in a
|
|
||||||
subnet dictionary as listed in the RESOURCE_ATTRIBUTE_MAP
|
|
||||||
object in neutron/api/v2/attributes.py. Only these fields
|
|
||||||
will be returned.
|
|
||||||
"""
|
|
||||||
LOG.info("get_subnets for tenant %s with filters %s fields %s" %
|
|
||||||
(context.tenant_id, filters, fields))
|
|
||||||
subnets = db_api.subnet_find(context, **filters)
|
|
||||||
return v._make_subnets_list(subnets, fields=fields,
|
|
||||||
default_route=routes.DEFAULT_ROUTE)
|
|
||||||
|
|
||||||
def get_subnets_count(self, context, filters=None):
|
|
||||||
"""Return the number of subnets.
|
|
||||||
|
|
||||||
The result depends on the identity of the user making the request
|
|
||||||
(as indicated by the context) as well as any filters.
|
|
||||||
: param context: neutron api request context
|
|
||||||
: param filters: a dictionary with keys that are valid keys for
|
|
||||||
a network as listed in the RESOURCE_ATTRIBUTE_MAP object
|
|
||||||
in neutron/api/v2/attributes.py. Values in this dictiontary
|
|
||||||
are an iterable containing values that will be used for an exact
|
|
||||||
match comparison for that value. Each result returned by this
|
|
||||||
function will have matched one of the values for each key in
|
|
||||||
filters.
|
|
||||||
|
|
||||||
NOTE: this method is optional, as it was not part of the originally
|
|
||||||
defined plugin API.
|
|
||||||
"""
|
|
||||||
LOG.info("get_subnets_count for tenant %s with filters %s" %
|
|
||||||
(context.tenant_id, filters))
|
|
||||||
return db_api.subnet_count_all(context, **filters)
|
|
||||||
|
|
||||||
def _delete_subnet(self, context, subnet):
|
|
||||||
if subnet.allocated_ips:
|
|
||||||
raise exceptions.SubnetInUse(subnet_id=subnet["id"])
|
|
||||||
db_api.subnet_delete(context, subnet)
|
|
||||||
|
|
||||||
def delete_subnet(self, context, id):
|
|
||||||
"""Delete a subnet.
|
|
||||||
|
|
||||||
: param context: neutron api request context
|
|
||||||
: param id: UUID representing the subnet to delete.
|
|
||||||
"""
|
|
||||||
LOG.info("delete_subnet %s for tenant %s" % (id, context.tenant_id))
|
|
||||||
subnet = db_api.subnet_find(context, id=id, scope=db_api.ONE)
|
|
||||||
if not subnet:
|
|
||||||
raise exceptions.SubnetNotFound(subnet_id=id)
|
|
||||||
self._delete_subnet(context, subnet)
|
|
||||||
|
|
||||||
def _adapt_provider_nets(self, context, network):
|
def _adapt_provider_nets(self, context, network):
|
||||||
#TODO(mdietz) going to ignore all the boundary and network
|
#TODO(mdietz) going to ignore all the boundary and network
|
||||||
# type checking for now.
|
# type checking for now.
|
||||||
@@ -366,14 +126,14 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2,
|
|||||||
phys_type=pnet_type,
|
phys_type=pnet_type,
|
||||||
phys_net=phys_net, segment_id=seg_id)
|
phys_net=phys_net, segment_id=seg_id)
|
||||||
|
|
||||||
subnets = net_attrs.pop("subnets", [])
|
subs = net_attrs.pop("subnets", [])
|
||||||
|
|
||||||
net_attrs["id"] = net_uuid
|
net_attrs["id"] = net_uuid
|
||||||
net_attrs["tenant_id"] = context.tenant_id
|
net_attrs["tenant_id"] = context.tenant_id
|
||||||
new_net = db_api.network_create(context, **net_attrs)
|
new_net = db_api.network_create(context, **net_attrs)
|
||||||
|
|
||||||
new_subnets = []
|
new_subnets = []
|
||||||
for sub in subnets:
|
for sub in subs:
|
||||||
sub["subnet"]["network_id"] = new_net["id"]
|
sub["subnet"]["network_id"] = new_net["id"]
|
||||||
sub["subnet"]["tenant_id"] = context.tenant_id
|
sub["subnet"]["tenant_id"] = context.tenant_id
|
||||||
s = db_api.subnet_create(context, **sub["subnet"])
|
s = db_api.subnet_create(context, **sub["subnet"])
|
||||||
@@ -483,7 +243,7 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2,
|
|||||||
raise exceptions.NetworkInUse(net_id=id)
|
raise exceptions.NetworkInUse(net_id=id)
|
||||||
self.net_driver.delete_network(context, id)
|
self.net_driver.delete_network(context, id)
|
||||||
for subnet in net["subnets"]:
|
for subnet in net["subnets"]:
|
||||||
self._delete_subnet(context, subnet)
|
subnets._delete_subnet(context, subnet)
|
||||||
db_api.network_delete(context, net)
|
db_api.network_delete(context, net)
|
||||||
|
|
||||||
def get_mac_address_range(self, context, id, fields=None):
|
def get_mac_address_range(self, context, id, fields=None):
|
||||||
@@ -594,3 +354,21 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2,
|
|||||||
|
|
||||||
def delete_route(self, context, id):
|
def delete_route(self, context, id):
|
||||||
routes.delete_route(context, id)
|
routes.delete_route(context, id)
|
||||||
|
|
||||||
|
def create_subnet(self, context, subnet):
|
||||||
|
return subnets.create_subnet(context, subnet)
|
||||||
|
|
||||||
|
def update_subnet(self, context, id, subnet):
|
||||||
|
return subnets.update_subnet(context, id, subnet)
|
||||||
|
|
||||||
|
def get_subnet(self, context, id, fields=None):
|
||||||
|
return subnets.get_subnet(context, id, fields)
|
||||||
|
|
||||||
|
def get_subnets(self, context, filters=None, fields=None):
|
||||||
|
return subnets.get_subnets(context, filters, fields)
|
||||||
|
|
||||||
|
def get_subnets_count(self, context, filters=None):
|
||||||
|
return subnets.get_subnets_count(context, filters)
|
||||||
|
|
||||||
|
def delete_subnet(self, context, id):
|
||||||
|
return subnets.delete_subnet(context, id)
|
||||||
|
|||||||
281
quark/plugin_modules/subnets.py
Normal file
281
quark/plugin_modules/subnets.py
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
# Copyright 2013 Openstack Foundation
|
||||||
|
# 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 neutron.common import config as neutron_cfg
|
||||||
|
from neutron.common import exceptions
|
||||||
|
from neutron.openstack.common import importutils
|
||||||
|
from neutron.openstack.common import log as logging
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
from quark.db import api as db_api
|
||||||
|
from quark import network_strategy
|
||||||
|
from quark.plugin_modules import routes
|
||||||
|
from quark import plugin_views as v
|
||||||
|
from quark import utils
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
DEFAULT_ROUTE = netaddr.IPNetwork("0.0.0.0/0")
|
||||||
|
LOG = logging.getLogger("neutron.quark")
|
||||||
|
STRATEGY = network_strategy.STRATEGY
|
||||||
|
|
||||||
|
ipam_driver = (importutils.import_class(CONF.QUARK.ipam_driver))()
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_subnet_cidr(context, network_id, new_subnet_cidr):
|
||||||
|
"""Validate the CIDR for a subnet.
|
||||||
|
|
||||||
|
Verifies the specified CIDR does not overlap with the ones defined
|
||||||
|
for the other subnets specified for this network, or with any other
|
||||||
|
CIDR if overlapping IPs are disabled.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if neutron_cfg.cfg.CONF.allow_overlapping_ips:
|
||||||
|
return
|
||||||
|
|
||||||
|
new_subnet_ipset = netaddr.IPSet([new_subnet_cidr])
|
||||||
|
|
||||||
|
# Using admin context here, in case we actually share networks later
|
||||||
|
subnet_list = db_api.subnet_find(context.elevated(),
|
||||||
|
network_id=network_id)
|
||||||
|
for subnet in subnet_list:
|
||||||
|
if (netaddr.IPSet([subnet.cidr]) & new_subnet_ipset):
|
||||||
|
# don't give out details of the overlapping subnet
|
||||||
|
err_msg = (_("Requested subnet with cidr: %(cidr)s for "
|
||||||
|
"network: %(network_id)s overlaps with another "
|
||||||
|
"subnet") %
|
||||||
|
{'cidr': new_subnet_cidr,
|
||||||
|
'network_id': network_id})
|
||||||
|
LOG.error(_("Validation for CIDR: %(new_cidr)s failed - "
|
||||||
|
"overlaps with subnet %(subnet_id)s "
|
||||||
|
"(CIDR: %(cidr)s)"),
|
||||||
|
{'new_cidr': new_subnet_cidr,
|
||||||
|
'subnet_id': subnet.id,
|
||||||
|
'cidr': subnet.cidr})
|
||||||
|
raise exceptions.InvalidInput(error_message=err_msg)
|
||||||
|
|
||||||
|
|
||||||
|
def create_subnet(context, subnet):
|
||||||
|
"""Create a subnet.
|
||||||
|
|
||||||
|
Create a subnet which represents a range of IP addresses
|
||||||
|
that can be allocated to devices
|
||||||
|
|
||||||
|
: param context: neutron api request context
|
||||||
|
: param subnet: dictionary describing the subnet, with keys
|
||||||
|
as listed in the RESOURCE_ATTRIBUTE_MAP object in
|
||||||
|
neutron/api/v2/attributes.py. All keys will be populated.
|
||||||
|
"""
|
||||||
|
LOG.info("create_subnet for tenant %s" % context.tenant_id)
|
||||||
|
net_id = subnet["subnet"]["network_id"]
|
||||||
|
|
||||||
|
net = db_api.network_find(context, id=net_id, scope=db_api.ONE)
|
||||||
|
if not net:
|
||||||
|
raise exceptions.NetworkNotFound(net_id=net_id)
|
||||||
|
|
||||||
|
sub_attrs = subnet["subnet"]
|
||||||
|
|
||||||
|
_validate_subnet_cidr(context, net_id, sub_attrs["cidr"])
|
||||||
|
|
||||||
|
cidr = netaddr.IPNetwork(sub_attrs["cidr"])
|
||||||
|
gateway_ip = utils.pop_param(sub_attrs, "gateway_ip", str(cidr[1]))
|
||||||
|
dns_ips = utils.pop_param(sub_attrs, "dns_nameservers", [])
|
||||||
|
host_routes = utils.pop_param(sub_attrs, "host_routes", [])
|
||||||
|
allocation_pools = utils.pop_param(sub_attrs, "allocation_pools", [])
|
||||||
|
|
||||||
|
new_subnet = db_api.subnet_create(context, **sub_attrs)
|
||||||
|
|
||||||
|
default_route = None
|
||||||
|
for route in host_routes:
|
||||||
|
netaddr_route = netaddr.IPNetwork(route["destination"])
|
||||||
|
if netaddr_route.value == routes.DEFAULT_ROUTE.value:
|
||||||
|
default_route = route
|
||||||
|
gateway_ip = default_route["nexthop"]
|
||||||
|
new_subnet["routes"].append(db_api.route_create(
|
||||||
|
context, cidr=route["destination"], gateway=route["nexthop"]))
|
||||||
|
|
||||||
|
if default_route is None:
|
||||||
|
new_subnet["routes"].append(db_api.route_create(
|
||||||
|
context, cidr=str(routes.DEFAULT_ROUTE), gateway=gateway_ip))
|
||||||
|
|
||||||
|
for dns_ip in dns_ips:
|
||||||
|
new_subnet["dns_nameservers"].append(db_api.dns_create(
|
||||||
|
context, ip=netaddr.IPAddress(dns_ip)))
|
||||||
|
|
||||||
|
if allocation_pools:
|
||||||
|
exclude = netaddr.IPSet([cidr])
|
||||||
|
for p in allocation_pools:
|
||||||
|
x = netaddr.IPSet(netaddr.IPRange(p["start"], p["end"]))
|
||||||
|
exclude = exclude - x
|
||||||
|
new_subnet["ip_policy"] = db_api.ip_policy_create(context,
|
||||||
|
exclude=exclude)
|
||||||
|
# HACK(amir): force backref for ip_policy
|
||||||
|
if not new_subnet["network"]:
|
||||||
|
new_subnet["network"] = net
|
||||||
|
subnet_dict = v._make_subnet_dict(new_subnet,
|
||||||
|
default_route=routes.DEFAULT_ROUTE)
|
||||||
|
subnet_dict["gateway_ip"] = gateway_ip
|
||||||
|
return subnet_dict
|
||||||
|
|
||||||
|
|
||||||
|
def update_subnet(context, id, subnet):
|
||||||
|
"""Update values of a subnet.
|
||||||
|
|
||||||
|
: param context: neutron api request context
|
||||||
|
: param id: UUID representing the subnet to update.
|
||||||
|
: param subnet: dictionary with keys indicating fields to update.
|
||||||
|
valid keys are those that have a value of True for 'allow_put'
|
||||||
|
as listed in the RESOURCE_ATTRIBUTE_MAP object in
|
||||||
|
neutron/api/v2/attributes.py.
|
||||||
|
"""
|
||||||
|
LOG.info("update_subnet %s for tenant %s" %
|
||||||
|
(id, context.tenant_id))
|
||||||
|
|
||||||
|
subnet_db = db_api.subnet_find(context, id=id, scope=db_api.ONE)
|
||||||
|
if not subnet_db:
|
||||||
|
raise exceptions.SubnetNotFound(id=id)
|
||||||
|
|
||||||
|
s = subnet["subnet"]
|
||||||
|
|
||||||
|
dns_ips = s.pop("dns_nameservers", [])
|
||||||
|
host_routes = s.pop("host_routes", [])
|
||||||
|
gateway_ip = s.pop("gateway_ip", None)
|
||||||
|
|
||||||
|
if gateway_ip:
|
||||||
|
default_route = None
|
||||||
|
for route in host_routes:
|
||||||
|
netaddr_route = netaddr.IPNetwork(route["destination"])
|
||||||
|
if netaddr_route.value == routes.DEFAULT_ROUTE.value:
|
||||||
|
default_route = route
|
||||||
|
break
|
||||||
|
if default_route is None:
|
||||||
|
route_model = db_api.route_find(
|
||||||
|
context, cidr=str(routes.DEFAULT_ROUTE), subnet_id=id,
|
||||||
|
scope=db_api.ONE)
|
||||||
|
if route_model:
|
||||||
|
db_api.route_update(context, route_model,
|
||||||
|
gateway=gateway_ip)
|
||||||
|
else:
|
||||||
|
db_api.route_create(context,
|
||||||
|
cidr=str(routes.DEFAULT_ROUTE),
|
||||||
|
gateway=gateway_ip, subnet_id=id)
|
||||||
|
|
||||||
|
if dns_ips:
|
||||||
|
subnet_db["dns_nameservers"] = []
|
||||||
|
for dns_ip in dns_ips:
|
||||||
|
subnet_db["dns_nameservers"].append(db_api.dns_create(
|
||||||
|
context,
|
||||||
|
ip=netaddr.IPAddress(dns_ip)))
|
||||||
|
|
||||||
|
if host_routes:
|
||||||
|
subnet_db["routes"] = []
|
||||||
|
for route in host_routes:
|
||||||
|
subnet_db["routes"].append(db_api.route_create(
|
||||||
|
context, cidr=route["destination"], gateway=route["nexthop"]))
|
||||||
|
|
||||||
|
subnet = db_api.subnet_update(context, subnet_db, **s)
|
||||||
|
return v._make_subnet_dict(subnet, default_route=routes.DEFAULT_ROUTE)
|
||||||
|
|
||||||
|
|
||||||
|
def get_subnet(context, id, fields=None):
|
||||||
|
"""Retrieve a subnet.
|
||||||
|
|
||||||
|
: param context: neutron api request context
|
||||||
|
: param id: UUID representing the subnet to fetch.
|
||||||
|
: param fields: a list of strings that are valid keys in a
|
||||||
|
subnet dictionary as listed in the RESOURCE_ATTRIBUTE_MAP
|
||||||
|
object in neutron/api/v2/attributes.py. Only these fields
|
||||||
|
will be returned.
|
||||||
|
"""
|
||||||
|
LOG.info("get_subnet %s for tenant %s with fields %s" %
|
||||||
|
(id, context.tenant_id, fields))
|
||||||
|
subnet = db_api.subnet_find(context, id=id, scope=db_api.ONE)
|
||||||
|
if not subnet:
|
||||||
|
raise exceptions.SubnetNotFound(subnet_id=id)
|
||||||
|
|
||||||
|
# Check the network_id against the strategies
|
||||||
|
net_id = subnet["network_id"]
|
||||||
|
net_id = STRATEGY.get_parent_network(net_id)
|
||||||
|
subnet["network_id"] = net_id
|
||||||
|
|
||||||
|
return v._make_subnet_dict(subnet, default_route=routes.DEFAULT_ROUTE)
|
||||||
|
|
||||||
|
|
||||||
|
def get_subnets(context, filters=None, fields=None):
|
||||||
|
"""Retrieve a list of subnets.
|
||||||
|
|
||||||
|
The contents of the list depends on the identity of the user
|
||||||
|
making the request (as indicated by the context) as well as any
|
||||||
|
filters.
|
||||||
|
: param context: neutron api request context
|
||||||
|
: param filters: a dictionary with keys that are valid keys for
|
||||||
|
a subnet as listed in the RESOURCE_ATTRIBUTE_MAP object
|
||||||
|
in neutron/api/v2/attributes.py. Values in this dictiontary
|
||||||
|
are an iterable containing values that will be used for an exact
|
||||||
|
match comparison for that value. Each result returned by this
|
||||||
|
function will have matched one of the values for each key in
|
||||||
|
filters.
|
||||||
|
: param fields: a list of strings that are valid keys in a
|
||||||
|
subnet dictionary as listed in the RESOURCE_ATTRIBUTE_MAP
|
||||||
|
object in neutron/api/v2/attributes.py. Only these fields
|
||||||
|
will be returned.
|
||||||
|
"""
|
||||||
|
LOG.info("get_subnets for tenant %s with filters %s fields %s" %
|
||||||
|
(context.tenant_id, filters, fields))
|
||||||
|
subnets = db_api.subnet_find(context, **filters)
|
||||||
|
return v._make_subnets_list(subnets, fields=fields,
|
||||||
|
default_route=routes.DEFAULT_ROUTE)
|
||||||
|
|
||||||
|
|
||||||
|
def get_subnets_count(context, filters=None):
|
||||||
|
"""Return the number of subnets.
|
||||||
|
|
||||||
|
The result depends on the identity of the user making the request
|
||||||
|
(as indicated by the context) as well as any filters.
|
||||||
|
: param context: neutron api request context
|
||||||
|
: param filters: a dictionary with keys that are valid keys for
|
||||||
|
a network as listed in the RESOURCE_ATTRIBUTE_MAP object
|
||||||
|
in neutron/api/v2/attributes.py. Values in this dictiontary
|
||||||
|
are an iterable containing values that will be used for an exact
|
||||||
|
match comparison for that value. Each result returned by this
|
||||||
|
function will have matched one of the values for each key in
|
||||||
|
filters.
|
||||||
|
|
||||||
|
NOTE: this method is optional, as it was not part of the originally
|
||||||
|
defined plugin API.
|
||||||
|
"""
|
||||||
|
LOG.info("get_subnets_count for tenant %s with filters %s" %
|
||||||
|
(context.tenant_id, filters))
|
||||||
|
return db_api.subnet_count_all(context, **filters)
|
||||||
|
|
||||||
|
|
||||||
|
def _delete_subnet(context, subnet):
|
||||||
|
if subnet.allocated_ips:
|
||||||
|
raise exceptions.SubnetInUse(subnet_id=subnet["id"])
|
||||||
|
db_api.subnet_delete(context, subnet)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_subnet(context, id):
|
||||||
|
"""Delete a subnet.
|
||||||
|
|
||||||
|
: param context: neutron api request context
|
||||||
|
: param id: UUID representing the subnet to delete.
|
||||||
|
"""
|
||||||
|
LOG.info("delete_subnet %s for tenant %s" % (id, context.tenant_id))
|
||||||
|
subnet = db_api.subnet_find(context, id=id, scope=db_api.ONE)
|
||||||
|
if not subnet:
|
||||||
|
raise exceptions.SubnetNotFound(subnet_id=id)
|
||||||
|
_delete_subnet(context, subnet)
|
||||||
670
quark/tests/plugin_modules/test_subnets.py
Normal file
670
quark/tests/plugin_modules/test_subnets.py
Normal file
@@ -0,0 +1,670 @@
|
|||||||
|
# Copyright 2013 Openstack Foundation
|
||||||
|
# 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 contextlib
|
||||||
|
import copy
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from neutron.api.v2 import attributes as neutron_attrs
|
||||||
|
from neutron.common import exceptions
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
|
from quark.db import models
|
||||||
|
from quark.tests import test_quark_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class TestQuarkGetSubnetCount(test_quark_plugin.TestQuarkPlugin):
|
||||||
|
def test_get_subnet_count(self):
|
||||||
|
"""This isn't really testable."""
|
||||||
|
with mock.patch("quark.db.api.subnet_count_all"):
|
||||||
|
self.plugin.get_subnets_count(self.context, {})
|
||||||
|
|
||||||
|
|
||||||
|
class TestQuarkGetSubnets(test_quark_plugin.TestQuarkPlugin):
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def _stubs(self, subnets=None, routes=None):
|
||||||
|
if routes is None:
|
||||||
|
routes = []
|
||||||
|
route_models = []
|
||||||
|
for route in routes:
|
||||||
|
r = models.Route()
|
||||||
|
r.update(route)
|
||||||
|
route_models.append(r)
|
||||||
|
|
||||||
|
if isinstance(subnets, list):
|
||||||
|
subnet_models = []
|
||||||
|
for subnet in subnets:
|
||||||
|
s_dict = subnet.copy()
|
||||||
|
s_dict["routes"] = route_models
|
||||||
|
s = models.Subnet(network=models.Network())
|
||||||
|
s.update(s_dict)
|
||||||
|
subnet_models.append(s)
|
||||||
|
elif subnets:
|
||||||
|
mod = models.Subnet(network=models.Network())
|
||||||
|
mod.update(subnets)
|
||||||
|
mod["routes"] = route_models
|
||||||
|
subnet_models = mod
|
||||||
|
else:
|
||||||
|
subnet_models = None
|
||||||
|
|
||||||
|
with mock.patch("quark.db.api.subnet_find") as subnet_find:
|
||||||
|
subnet_find.return_value = subnet_models
|
||||||
|
yield
|
||||||
|
|
||||||
|
def test_subnets_list(self):
|
||||||
|
subnet_id = str(uuid.uuid4())
|
||||||
|
route = dict(id=1, cidr="0.0.0.0/0", gateway="192.168.0.1")
|
||||||
|
|
||||||
|
subnet = dict(id=subnet_id, network_id=1, name=subnet_id,
|
||||||
|
tenant_id=self.context.tenant_id, ip_version=4,
|
||||||
|
cidr="192.168.0.0/24", gateway_ip="192.168.0.1",
|
||||||
|
dns_nameservers=[],
|
||||||
|
enable_dhcp=None)
|
||||||
|
expected_route = dict(destination=route["cidr"],
|
||||||
|
nexthop=route["gateway"])
|
||||||
|
|
||||||
|
with self._stubs(subnets=[subnet], routes=[route]):
|
||||||
|
res = self.plugin.get_subnets(self.context, {}, {})
|
||||||
|
# Compare routes separately
|
||||||
|
routes = res[0].pop("host_routes")
|
||||||
|
for key in subnet.keys():
|
||||||
|
self.assertEqual(res[0][key], subnet[key])
|
||||||
|
for key in expected_route.keys():
|
||||||
|
self.assertEqual(routes[0][key], expected_route[key])
|
||||||
|
|
||||||
|
def test_subnet_show_fail(self):
|
||||||
|
with self._stubs():
|
||||||
|
with self.assertRaises(exceptions.SubnetNotFound):
|
||||||
|
self.plugin.get_subnet(self.context, 1)
|
||||||
|
|
||||||
|
def test_subnet_show(self):
|
||||||
|
subnet_id = str(uuid.uuid4())
|
||||||
|
route = dict(id=1, cidr="0.0.0.0/0", gateway="192.168.0.1",
|
||||||
|
subnet_id=subnet_id)
|
||||||
|
|
||||||
|
expected_route = dict(destination=route["cidr"],
|
||||||
|
nexthop=route["gateway"])
|
||||||
|
|
||||||
|
subnet = dict(id=subnet_id, network_id=1, name=subnet_id,
|
||||||
|
tenant_id=self.context.tenant_id, ip_version=4,
|
||||||
|
cidr="192.168.0.0/24", gateway_ip="192.168.0.1",
|
||||||
|
dns_nameservers=[],
|
||||||
|
enable_dhcp=None)
|
||||||
|
|
||||||
|
with self._stubs(subnets=subnet, routes=[route]):
|
||||||
|
res = self.plugin.get_subnet(self.context, subnet_id)
|
||||||
|
|
||||||
|
# Compare routes separately
|
||||||
|
routes = res.pop("host_routes")
|
||||||
|
for key in subnet.keys():
|
||||||
|
self.assertEqual(res[key], subnet[key])
|
||||||
|
for key in expected_route.keys():
|
||||||
|
self.assertEqual(routes[0][key], expected_route[key])
|
||||||
|
|
||||||
|
|
||||||
|
class TestQuarkCreateSubnetOverlapping(test_quark_plugin.TestQuarkPlugin):
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def _stubs(self, subnets=None):
|
||||||
|
if subnets is None:
|
||||||
|
subnets = []
|
||||||
|
subnet_models = []
|
||||||
|
for subnet in subnets:
|
||||||
|
s = models.Subnet()
|
||||||
|
s.update(subnet)
|
||||||
|
subnet_models.append(s)
|
||||||
|
network = models.Network()
|
||||||
|
network.update(dict(id=1, subnets=subnet_models))
|
||||||
|
with contextlib.nested(
|
||||||
|
mock.patch("quark.db.api.network_find"),
|
||||||
|
mock.patch("quark.db.api.subnet_find"),
|
||||||
|
mock.patch("quark.db.api.subnet_create")
|
||||||
|
) as (net_find, subnet_find, subnet_create):
|
||||||
|
net_find.return_value = network
|
||||||
|
subnet_find.return_value = subnet_models
|
||||||
|
subnet_create.return_value = models.Subnet(
|
||||||
|
network=models.Network(),
|
||||||
|
cidr="192.168.1.1/24")
|
||||||
|
yield subnet_create
|
||||||
|
|
||||||
|
def test_create_subnet_overlapping_true(self):
|
||||||
|
cfg.CONF.set_override('allow_overlapping_ips', True)
|
||||||
|
with self._stubs() as subnet_create:
|
||||||
|
s = dict(subnet=dict(
|
||||||
|
gateway_ip=neutron_attrs.ATTR_NOT_SPECIFIED,
|
||||||
|
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
|
||||||
|
cidr="192.168.1.1/8",
|
||||||
|
network_id=1))
|
||||||
|
self.plugin.create_subnet(self.context, s)
|
||||||
|
self.assertEqual(subnet_create.call_count, 1)
|
||||||
|
|
||||||
|
def test_create_subnet_overlapping_false(self):
|
||||||
|
cfg.CONF.set_override('allow_overlapping_ips', False)
|
||||||
|
with self._stubs() as subnet_create:
|
||||||
|
s = dict(subnet=dict(
|
||||||
|
gateway_ip=neutron_attrs.ATTR_NOT_SPECIFIED,
|
||||||
|
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
|
||||||
|
cidr="192.168.1.1/8",
|
||||||
|
network_id=1))
|
||||||
|
self.plugin.create_subnet(self.context, s)
|
||||||
|
self.assertEqual(subnet_create.call_count, 1)
|
||||||
|
|
||||||
|
def test_create_subnet_overlapping_conflict(self):
|
||||||
|
cfg.CONF.set_override('allow_overlapping_ips', False)
|
||||||
|
with self._stubs(subnets=[dict(cidr="192.168.10.1/24")]):
|
||||||
|
with self.assertRaises(exceptions.InvalidInput):
|
||||||
|
s = dict(subnet=dict(cidr="192.168.1.1/8",
|
||||||
|
network_id=1))
|
||||||
|
self.plugin.create_subnet(self.context, s)
|
||||||
|
|
||||||
|
|
||||||
|
class TestQuarkCreateSubnetAllocationPools(test_quark_plugin.TestQuarkPlugin):
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def _stubs(self, subnet):
|
||||||
|
s = models.Subnet(network=models.Network(id=1, subnets=[]))
|
||||||
|
s.update(subnet)
|
||||||
|
|
||||||
|
with contextlib.nested(
|
||||||
|
mock.patch("quark.db.api.network_find"),
|
||||||
|
mock.patch("quark.db.api.subnet_find"),
|
||||||
|
mock.patch("quark.db.api.subnet_create")
|
||||||
|
) as (net_find, subnet_find, subnet_create):
|
||||||
|
net_find.return_value = s["network"]
|
||||||
|
subnet_find.return_value = []
|
||||||
|
subnet_create.return_value = s
|
||||||
|
yield subnet_create
|
||||||
|
|
||||||
|
def test_create_subnet_allocation_pools_zero(self):
|
||||||
|
s = dict(subnet=dict(
|
||||||
|
cidr="192.168.1.1/24",
|
||||||
|
network_id=1))
|
||||||
|
with self._stubs(s["subnet"]) as subnet_create:
|
||||||
|
resp = self.plugin.create_subnet(self.context, s)
|
||||||
|
self.assertEqual(subnet_create.call_count, 1)
|
||||||
|
self.assertEqual(resp["allocation_pools"],
|
||||||
|
[dict(start="192.168.1.0", end="192.168.1.255")])
|
||||||
|
|
||||||
|
def test_create_subnet_allocation_pools_one(self):
|
||||||
|
pools = [dict(start="192.168.1.10", end="192.168.1.20")]
|
||||||
|
s = dict(subnet=dict(
|
||||||
|
allocation_pools=pools,
|
||||||
|
cidr="192.168.1.1/24",
|
||||||
|
network_id=1))
|
||||||
|
with self._stubs(s["subnet"]) as subnet_create:
|
||||||
|
resp = self.plugin.create_subnet(self.context, s)
|
||||||
|
self.assertEqual(subnet_create.call_count, 1)
|
||||||
|
self.assertEqual(resp["allocation_pools"], pools)
|
||||||
|
|
||||||
|
def test_create_subnet_allocation_pools_two(self):
|
||||||
|
pools = [dict(start="192.168.1.10", end="192.168.1.20"),
|
||||||
|
dict(start="192.168.1.40", end="192.168.1.50")]
|
||||||
|
s = dict(subnet=dict(
|
||||||
|
allocation_pools=pools,
|
||||||
|
cidr="192.168.1.1/24",
|
||||||
|
network_id=1))
|
||||||
|
with self._stubs(s["subnet"]) as subnet_create:
|
||||||
|
resp = self.plugin.create_subnet(self.context, s)
|
||||||
|
self.assertEqual(subnet_create.call_count, 1)
|
||||||
|
self.assertEqual(resp["allocation_pools"], pools)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO(amir): Refactor the tests to test individual subnet attributes.
|
||||||
|
# * copy.deepcopy was necessary to maintain tests on keys, which is a bit ugly.
|
||||||
|
# * workaround is also in place for lame ATTR_NOT_SPECIFIED object()
|
||||||
|
class TestQuarkCreateSubnet(test_quark_plugin.TestQuarkPlugin):
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def _stubs(self, subnet=None, network=None, routes=None, dns=None):
|
||||||
|
subnet_mod = models.Subnet(network=models.Network())
|
||||||
|
dns_ips = subnet.pop("dns_nameservers", [])
|
||||||
|
host_routes = subnet.pop("host_routes", [])
|
||||||
|
subnet_mod.update(subnet)
|
||||||
|
subnet["dns_nameservers"] = dns_ips
|
||||||
|
subnet["host_routes"] = host_routes
|
||||||
|
routes = routes or []
|
||||||
|
dns = dns or []
|
||||||
|
route_models = [models.Route(**r) for r in routes]
|
||||||
|
dns_models = [models.DNSNameserver(**d) for d in dns]
|
||||||
|
|
||||||
|
with contextlib.nested(
|
||||||
|
mock.patch("quark.db.api.subnet_create"),
|
||||||
|
mock.patch("quark.db.api.network_find"),
|
||||||
|
mock.patch("quark.db.api.dns_create"),
|
||||||
|
mock.patch("quark.db.api.route_create"),
|
||||||
|
) as (subnet_create, net_find, dns_create, route_create):
|
||||||
|
subnet_create.return_value = subnet_mod
|
||||||
|
net_find.return_value = network
|
||||||
|
route_create.side_effect = route_models
|
||||||
|
dns_create.side_effect = dns_models
|
||||||
|
yield subnet_create, dns_create, route_create
|
||||||
|
|
||||||
|
def test_create_subnet(self):
|
||||||
|
routes = [dict(cidr="0.0.0.0/0", gateway="0.0.0.0")]
|
||||||
|
subnet = dict(
|
||||||
|
subnet=dict(network_id=1,
|
||||||
|
tenant_id=self.context.tenant_id, ip_version=4,
|
||||||
|
cidr="172.16.0.0/24", gateway_ip="0.0.0.0",
|
||||||
|
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
|
||||||
|
host_routes=neutron_attrs.ATTR_NOT_SPECIFIED,
|
||||||
|
enable_dhcp=None))
|
||||||
|
network = dict(network_id=1)
|
||||||
|
with self._stubs(
|
||||||
|
subnet=subnet["subnet"],
|
||||||
|
network=network,
|
||||||
|
routes=routes
|
||||||
|
) as (subnet_create, dns_create, route_create):
|
||||||
|
dns_nameservers = subnet["subnet"].pop("dns_nameservers")
|
||||||
|
host_routes = subnet["subnet"].pop("host_routes")
|
||||||
|
subnet_request = copy.deepcopy(subnet)
|
||||||
|
subnet_request["subnet"]["dns_nameservers"] = dns_nameservers
|
||||||
|
subnet_request["subnet"]["host_routes"] = host_routes
|
||||||
|
res = self.plugin.create_subnet(self.context,
|
||||||
|
subnet_request)
|
||||||
|
self.assertEqual(subnet_create.call_count, 1)
|
||||||
|
self.assertEqual(dns_create.call_count, 0)
|
||||||
|
self.assertEqual(route_create.call_count, 1)
|
||||||
|
for key in subnet["subnet"].keys():
|
||||||
|
if key == "host_routes":
|
||||||
|
self.assertEqual(res[key][0]["destination"], "0.0.0.0/0")
|
||||||
|
self.assertEqual(res[key][0]["nexthop"], "0.0.0.0")
|
||||||
|
else:
|
||||||
|
self.assertEqual(res[key], subnet["subnet"][key])
|
||||||
|
|
||||||
|
def test_create_subnet_no_network_fails(self):
|
||||||
|
subnet = dict(subnet=dict(network_id=1))
|
||||||
|
with self._stubs(subnet=dict(), network=None):
|
||||||
|
with self.assertRaises(exceptions.NetworkNotFound):
|
||||||
|
self.plugin.create_subnet(self.context, subnet)
|
||||||
|
|
||||||
|
def test_create_subnet_no_gateway_ip_defaults(self):
|
||||||
|
routes = [dict(cidr="0.0.0.0/0", gateway="172.16.0.1")]
|
||||||
|
subnet = dict(
|
||||||
|
subnet=dict(network_id=1,
|
||||||
|
tenant_id=self.context.tenant_id, ip_version=4,
|
||||||
|
cidr="172.16.0.0/24",
|
||||||
|
gateway_ip=neutron_attrs.ATTR_NOT_SPECIFIED,
|
||||||
|
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
|
||||||
|
enable_dhcp=None))
|
||||||
|
network = dict(network_id=1)
|
||||||
|
with self._stubs(
|
||||||
|
subnet=subnet["subnet"],
|
||||||
|
network=network,
|
||||||
|
routes=routes
|
||||||
|
) as (subnet_create, dns_create, route_create):
|
||||||
|
dns_nameservers = subnet["subnet"].pop("dns_nameservers")
|
||||||
|
gateway_ip = subnet["subnet"].pop("gateway_ip")
|
||||||
|
subnet_request = copy.deepcopy(subnet)
|
||||||
|
subnet_request["subnet"]["dns_nameservers"] = dns_nameservers
|
||||||
|
subnet_request["subnet"]["gateway_ip"] = gateway_ip
|
||||||
|
res = self.plugin.create_subnet(self.context, subnet_request)
|
||||||
|
self.assertEqual(subnet_create.call_count, 1)
|
||||||
|
self.assertEqual(dns_create.call_count, 0)
|
||||||
|
self.assertEqual(route_create.call_count, 1)
|
||||||
|
for key in subnet["subnet"].keys():
|
||||||
|
if key == "gateway_ip":
|
||||||
|
self.assertEqual(res[key], "172.16.0.1")
|
||||||
|
elif key == "host_routes":
|
||||||
|
self.assertEqual(res[key][0]["destination"], "0.0.0.0/0")
|
||||||
|
self.assertEqual(res[key][0]["nexthop"], "172.16.0.1")
|
||||||
|
else:
|
||||||
|
self.assertEqual(res[key], subnet["subnet"][key])
|
||||||
|
|
||||||
|
def test_create_subnet_dns_nameservers(self):
|
||||||
|
routes = [dict(cidr="0.0.0.0/0", gateway="0.0.0.0")]
|
||||||
|
dns_ns = [dict(ip="4.2.2.1"), dict(ip="4.2.2.2")]
|
||||||
|
subnet = dict(
|
||||||
|
subnet=dict(network_id=1,
|
||||||
|
tenant_id=self.context.tenant_id, ip_version=4,
|
||||||
|
cidr="172.16.0.0/24", gateway_ip="0.0.0.0",
|
||||||
|
dns_nameservers=["4.2.2.1", "4.2.2.2"],
|
||||||
|
enable_dhcp=None))
|
||||||
|
network = dict(network_id=1)
|
||||||
|
with self._stubs(
|
||||||
|
subnet=subnet["subnet"],
|
||||||
|
network=network,
|
||||||
|
routes=routes,
|
||||||
|
dns=dns_ns
|
||||||
|
) as (subnet_create, dns_create, route_create):
|
||||||
|
res = self.plugin.create_subnet(self.context,
|
||||||
|
copy.deepcopy(subnet))
|
||||||
|
self.assertEqual(subnet_create.call_count, 1)
|
||||||
|
self.assertEqual(dns_create.call_count, 2)
|
||||||
|
self.assertEqual(route_create.call_count, 1)
|
||||||
|
for key in subnet["subnet"].keys():
|
||||||
|
if key == "host_routes":
|
||||||
|
self.assertEqual(res[key][0]["destination"], "0.0.0.0/0")
|
||||||
|
self.assertEqual(res[key][0]["nexthop"], "0.0.0.0")
|
||||||
|
else:
|
||||||
|
self.assertEqual(res[key], subnet["subnet"][key])
|
||||||
|
|
||||||
|
def test_create_subnet_routes(self):
|
||||||
|
routes = [dict(cidr="1.1.1.1/8", gateway="172.16.0.4"),
|
||||||
|
dict(cidr="0.0.0.0/0", gateway="0.0.0.0")]
|
||||||
|
subnet = dict(
|
||||||
|
subnet=dict(network_id=1,
|
||||||
|
tenant_id=self.context.tenant_id, ip_version=4,
|
||||||
|
cidr="172.16.0.0/24", gateway_ip="0.0.0.0",
|
||||||
|
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
|
||||||
|
host_routes=[{"destination": "1.1.1.1/8",
|
||||||
|
"nexthop": "172.16.0.4"}],
|
||||||
|
enable_dhcp=None))
|
||||||
|
network = dict(network_id=1)
|
||||||
|
with self._stubs(
|
||||||
|
subnet=subnet["subnet"],
|
||||||
|
network=network,
|
||||||
|
routes=routes
|
||||||
|
) as (subnet_create, dns_create, route_create):
|
||||||
|
dns_nameservers = subnet["subnet"].pop("dns_nameservers")
|
||||||
|
subnet_request = copy.deepcopy(subnet)
|
||||||
|
subnet_request["subnet"]["dns_nameservers"] = dns_nameservers
|
||||||
|
res = self.plugin.create_subnet(self.context, subnet_request)
|
||||||
|
self.assertEqual(subnet_create.call_count, 1)
|
||||||
|
self.assertEqual(dns_create.call_count, 0)
|
||||||
|
self.assertEqual(route_create.call_count, 2)
|
||||||
|
for key in subnet["subnet"].keys():
|
||||||
|
if key == "host_routes":
|
||||||
|
res_tuples = [(r["destination"], r["nexthop"])
|
||||||
|
for r in res[key]]
|
||||||
|
self.assertIn(("1.1.1.1/8", "172.16.0.4"), res_tuples)
|
||||||
|
self.assertIn(("0.0.0.0/0", "0.0.0.0"), res_tuples)
|
||||||
|
self.assertEqual(2, len(res_tuples))
|
||||||
|
else:
|
||||||
|
self.assertEqual(res[key], subnet["subnet"][key])
|
||||||
|
|
||||||
|
def test_create_subnet_default_route(self):
|
||||||
|
routes = [dict(cidr="0.0.0.0/0", gateway="172.16.0.4")]
|
||||||
|
subnet = dict(
|
||||||
|
subnet=dict(network_id=1,
|
||||||
|
tenant_id=self.context.tenant_id, ip_version=4,
|
||||||
|
cidr="172.16.0.0/24",
|
||||||
|
gateway_ip=neutron_attrs.ATTR_NOT_SPECIFIED,
|
||||||
|
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
|
||||||
|
host_routes=[{"destination": "0.0.0.0/0",
|
||||||
|
"nexthop": "172.16.0.4"}],
|
||||||
|
enable_dhcp=None))
|
||||||
|
network = dict(network_id=1)
|
||||||
|
with self._stubs(
|
||||||
|
subnet=subnet["subnet"],
|
||||||
|
network=network,
|
||||||
|
routes=routes
|
||||||
|
) as (subnet_create, dns_create, route_create):
|
||||||
|
dns_nameservers = subnet["subnet"].pop("dns_nameservers")
|
||||||
|
gateway_ip = subnet["subnet"].pop("gateway_ip")
|
||||||
|
subnet_request = copy.deepcopy(subnet)
|
||||||
|
subnet_request["subnet"]["dns_nameservers"] = dns_nameservers
|
||||||
|
subnet_request["subnet"]["gateway_ip"] = gateway_ip
|
||||||
|
res = self.plugin.create_subnet(self.context, subnet_request)
|
||||||
|
self.assertEqual(subnet_create.call_count, 1)
|
||||||
|
self.assertEqual(dns_create.call_count, 0)
|
||||||
|
self.assertEqual(route_create.call_count, 1)
|
||||||
|
for key in subnet["subnet"].keys():
|
||||||
|
if key == "host_routes":
|
||||||
|
res_tuples = [(r["destination"], r["nexthop"])
|
||||||
|
for r in res[key]]
|
||||||
|
self.assertEqual([("0.0.0.0/0", "172.16.0.4")], res_tuples)
|
||||||
|
elif key == "gateway_ip":
|
||||||
|
self.assertEqual(res[key], "172.16.0.4")
|
||||||
|
else:
|
||||||
|
self.assertEqual(res[key], subnet["subnet"][key])
|
||||||
|
|
||||||
|
def test_create_subnet_default_route_gateway_ip(self):
|
||||||
|
"""If default route (host_routes) and gateway_ip are both provided,
|
||||||
|
then host_route takes precedence.
|
||||||
|
"""
|
||||||
|
routes = [dict(cidr="0.0.0.0/0", gateway="172.16.0.4")]
|
||||||
|
subnet = dict(
|
||||||
|
subnet=dict(network_id=1,
|
||||||
|
tenant_id=self.context.tenant_id, ip_version=4,
|
||||||
|
cidr="172.16.0.0/24",
|
||||||
|
gateway_ip="172.16.0.3",
|
||||||
|
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
|
||||||
|
host_routes=[{"destination": "0.0.0.0/0",
|
||||||
|
"nexthop": "172.16.0.4"}],
|
||||||
|
enable_dhcp=None))
|
||||||
|
network = dict(network_id=1)
|
||||||
|
with self._stubs(
|
||||||
|
subnet=subnet["subnet"],
|
||||||
|
network=network,
|
||||||
|
routes=routes
|
||||||
|
) as (subnet_create, dns_create, route_create):
|
||||||
|
dns_nameservers = subnet["subnet"].pop("dns_nameservers")
|
||||||
|
subnet_request = copy.deepcopy(subnet)
|
||||||
|
subnet_request["subnet"]["dns_nameservers"] = dns_nameservers
|
||||||
|
res = self.plugin.create_subnet(self.context, subnet_request)
|
||||||
|
self.assertEqual(subnet_create.call_count, 1)
|
||||||
|
self.assertEqual(dns_create.call_count, 0)
|
||||||
|
self.assertEqual(route_create.call_count, 1)
|
||||||
|
for key in subnet["subnet"].keys():
|
||||||
|
if key == "host_routes":
|
||||||
|
res_tuples = [(r["destination"], r["nexthop"])
|
||||||
|
for r in res[key]]
|
||||||
|
self.assertEqual([("0.0.0.0/0", "172.16.0.4")], res_tuples)
|
||||||
|
elif key == "gateway_ip":
|
||||||
|
self.assertEqual(res[key], "172.16.0.4")
|
||||||
|
else:
|
||||||
|
self.assertEqual(res[key], subnet["subnet"][key])
|
||||||
|
|
||||||
|
|
||||||
|
class TestQuarkUpdateSubnet(test_quark_plugin.TestQuarkPlugin):
|
||||||
|
DEFAULT_ROUTE = [dict(destination="0.0.0.0/0",
|
||||||
|
nexthop="172.16.0.1")]
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def _stubs(self, host_routes=None, new_routes=None, find_routes=True,
|
||||||
|
new_dns_servers=None):
|
||||||
|
if host_routes is None:
|
||||||
|
host_routes = []
|
||||||
|
if new_routes:
|
||||||
|
new_routes = [models.Route(cidr=r["destination"],
|
||||||
|
gateway=r["nexthop"],
|
||||||
|
subnet_id=1)
|
||||||
|
for r in new_routes]
|
||||||
|
if new_dns_servers:
|
||||||
|
new_dns_servers = [models.DNSNameserver(
|
||||||
|
ip=ip,
|
||||||
|
subnet_id=1) for ip in new_dns_servers]
|
||||||
|
|
||||||
|
subnet = dict(
|
||||||
|
id=1,
|
||||||
|
network_id=1,
|
||||||
|
tenant_id=self.context.tenant_id, ip_version=4,
|
||||||
|
cidr="172.16.0.0/24",
|
||||||
|
host_routes=host_routes,
|
||||||
|
dns_nameservers=["4.2.2.1", "4.2.2.2"],
|
||||||
|
enable_dhcp=None)
|
||||||
|
|
||||||
|
dns_ips = subnet.pop("dns_nameservers", [])
|
||||||
|
host_routes = subnet.pop("host_routes", [])
|
||||||
|
subnet_mod = models.Subnet()
|
||||||
|
|
||||||
|
subnet_mod.update(subnet)
|
||||||
|
|
||||||
|
subnet_mod["dns_nameservers"] = [models.DNSNameserver(ip=ip)
|
||||||
|
for ip in dns_ips]
|
||||||
|
subnet_mod["routes"] = [models.Route(cidr=r["destination"],
|
||||||
|
gateway=r["nexthop"],
|
||||||
|
subnet_id=subnet_mod["id"])
|
||||||
|
for r in host_routes]
|
||||||
|
with contextlib.nested(
|
||||||
|
mock.patch("quark.db.api.subnet_find"),
|
||||||
|
mock.patch("quark.db.api.subnet_update"),
|
||||||
|
mock.patch("quark.db.api.dns_create"),
|
||||||
|
mock.patch("quark.db.api.route_find"),
|
||||||
|
mock.patch("quark.db.api.route_update"),
|
||||||
|
mock.patch("quark.db.api.route_create"),
|
||||||
|
) as (subnet_find, subnet_update,
|
||||||
|
dns_create,
|
||||||
|
route_find, route_update, route_create):
|
||||||
|
subnet_find.return_value = subnet_mod
|
||||||
|
route_find.return_value = subnet_mod["routes"][0] \
|
||||||
|
if subnet_mod["routes"] and find_routes else None
|
||||||
|
new_subnet_mod = models.Subnet(network=models.Network())
|
||||||
|
new_subnet_mod.update(subnet_mod)
|
||||||
|
if new_routes:
|
||||||
|
new_subnet_mod["routes"] = new_routes
|
||||||
|
if new_dns_servers:
|
||||||
|
new_subnet_mod["dns_nameservers"] = new_dns_servers
|
||||||
|
subnet_update.return_value = new_subnet_mod
|
||||||
|
yield dns_create, route_update, route_create
|
||||||
|
|
||||||
|
def test_update_subnet_not_found(self):
|
||||||
|
with self.assertRaises(exceptions.SubnetNotFound):
|
||||||
|
self.plugin.update_subnet(self.context, 1, {})
|
||||||
|
|
||||||
|
def test_update_subnet_dns_nameservers(self):
|
||||||
|
new_dns_servers = ["1.1.1.2"]
|
||||||
|
with self._stubs(
|
||||||
|
host_routes=self.DEFAULT_ROUTE,
|
||||||
|
new_dns_servers=new_dns_servers
|
||||||
|
) as (dns_create, route_update, route_create):
|
||||||
|
req = dict(subnet=dict(dns_nameservers=new_dns_servers))
|
||||||
|
res = self.plugin.update_subnet(self.context,
|
||||||
|
1,
|
||||||
|
req)
|
||||||
|
self.assertEqual(dns_create.call_count, 1)
|
||||||
|
self.assertEqual(route_create.call_count, 0)
|
||||||
|
self.assertEqual(res["dns_nameservers"], new_dns_servers)
|
||||||
|
|
||||||
|
def test_update_subnet_routes(self):
|
||||||
|
new_routes = [dict(destination="10.0.0.0/24",
|
||||||
|
nexthop="1.1.1.1")]
|
||||||
|
with self._stubs(
|
||||||
|
host_routes=self.DEFAULT_ROUTE,
|
||||||
|
new_routes=new_routes
|
||||||
|
) as (dns_create, route_update, route_create):
|
||||||
|
req = dict(subnet=dict(
|
||||||
|
host_routes=new_routes))
|
||||||
|
res = self.plugin.update_subnet(self.context, 1, req)
|
||||||
|
self.assertEqual(dns_create.call_count, 0)
|
||||||
|
self.assertEqual(route_create.call_count, 1)
|
||||||
|
self.assertEqual(len(res["host_routes"]), 1)
|
||||||
|
self.assertEqual(res["host_routes"][0]["destination"],
|
||||||
|
"10.0.0.0/24")
|
||||||
|
self.assertEqual(res["host_routes"][0]["nexthop"],
|
||||||
|
"1.1.1.1")
|
||||||
|
self.assertIsNone(res["gateway_ip"])
|
||||||
|
|
||||||
|
def test_update_subnet_gateway_ip_with_default_route_in_db(self):
|
||||||
|
with self._stubs(
|
||||||
|
host_routes=self.DEFAULT_ROUTE,
|
||||||
|
new_routes=[dict(destination="0.0.0.0/0", nexthop="1.2.3.4")]
|
||||||
|
) as (dns_create, route_update, route_create):
|
||||||
|
req = dict(subnet=dict(gateway_ip="1.2.3.4"))
|
||||||
|
res = self.plugin.update_subnet(self.context, 1, req)
|
||||||
|
self.assertEqual(dns_create.call_count, 0)
|
||||||
|
self.assertEqual(route_create.call_count, 0)
|
||||||
|
self.assertEqual(route_update.call_count, 1)
|
||||||
|
self.assertEqual(len(res["host_routes"]), 1)
|
||||||
|
self.assertEqual(res["host_routes"][0]["destination"],
|
||||||
|
"0.0.0.0/0")
|
||||||
|
self.assertEqual(res["host_routes"][0]["nexthop"],
|
||||||
|
"1.2.3.4")
|
||||||
|
self.assertEqual(res["gateway_ip"], "1.2.3.4")
|
||||||
|
|
||||||
|
def test_update_subnet_gateway_ip_with_non_default_route_in_db(self):
|
||||||
|
with self._stubs(
|
||||||
|
host_routes=[dict(destination="1.1.1.1/8", nexthop="9.9.9.9")],
|
||||||
|
find_routes=False,
|
||||||
|
new_routes=[dict(destination="1.1.1.1/8", nexthop="9.9.9.9"),
|
||||||
|
dict(destination="0.0.0.0/0", nexthop="1.2.3.4")]
|
||||||
|
) as (dns_create, route_update, route_create):
|
||||||
|
req = dict(subnet=dict(gateway_ip="1.2.3.4"))
|
||||||
|
res = self.plugin.update_subnet(self.context, 1, req)
|
||||||
|
self.assertEqual(dns_create.call_count, 0)
|
||||||
|
self.assertEqual(route_create.call_count, 1)
|
||||||
|
|
||||||
|
self.assertEqual(res["gateway_ip"], "1.2.3.4")
|
||||||
|
|
||||||
|
self.assertEqual(len(res["host_routes"]), 2)
|
||||||
|
res_tuples = [(r["destination"], r["nexthop"])
|
||||||
|
for r in res["host_routes"]]
|
||||||
|
self.assertIn(("0.0.0.0/0", "1.2.3.4"), res_tuples)
|
||||||
|
self.assertIn(("1.1.1.1/8", "9.9.9.9"), res_tuples)
|
||||||
|
|
||||||
|
def test_update_subnet_gateway_ip_without_default_route_in_db(self):
|
||||||
|
with self._stubs(
|
||||||
|
host_routes=None,
|
||||||
|
new_routes=[dict(destination="0.0.0.0/0", nexthop="1.2.3.4")]
|
||||||
|
) as (dns_create, route_update, route_create):
|
||||||
|
req = dict(subnet=dict(gateway_ip="1.2.3.4"))
|
||||||
|
res = self.plugin.update_subnet(self.context, 1, req)
|
||||||
|
self.assertEqual(dns_create.call_count, 0)
|
||||||
|
self.assertEqual(route_create.call_count, 1)
|
||||||
|
self.assertEqual(len(res["host_routes"]), 1)
|
||||||
|
self.assertEqual(res["host_routes"][0]["destination"],
|
||||||
|
"0.0.0.0/0")
|
||||||
|
self.assertEqual(res["host_routes"][0]["nexthop"],
|
||||||
|
"1.2.3.4")
|
||||||
|
self.assertEqual(res["gateway_ip"], "1.2.3.4")
|
||||||
|
|
||||||
|
def test_update_subnet_gateway_ip_with_default_route_in_args(self):
|
||||||
|
new_routes = [dict(destination="0.0.0.0/0",
|
||||||
|
nexthop="4.3.2.1")]
|
||||||
|
with self._stubs(
|
||||||
|
host_routes=self.DEFAULT_ROUTE,
|
||||||
|
new_routes=new_routes
|
||||||
|
) as (dns_create, route_update, route_create):
|
||||||
|
req = dict(subnet=dict(
|
||||||
|
host_routes=new_routes,
|
||||||
|
gateway_ip="1.2.3.4"))
|
||||||
|
res = self.plugin.update_subnet(self.context, 1, req)
|
||||||
|
self.assertEqual(dns_create.call_count, 0)
|
||||||
|
self.assertEqual(route_create.call_count, 1)
|
||||||
|
self.assertEqual(len(res["host_routes"]), 1)
|
||||||
|
self.assertEqual(res["host_routes"][0]["destination"],
|
||||||
|
"0.0.0.0/0")
|
||||||
|
self.assertEqual(res["host_routes"][0]["nexthop"],
|
||||||
|
"4.3.2.1")
|
||||||
|
self.assertEqual(res["gateway_ip"], "4.3.2.1")
|
||||||
|
|
||||||
|
|
||||||
|
class TestQuarkDeleteSubnet(test_quark_plugin.TestQuarkPlugin):
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def _stubs(self, subnet, ips):
|
||||||
|
ip_mods = []
|
||||||
|
subnet_mod = None
|
||||||
|
if subnet:
|
||||||
|
subnet_mod = models.Subnet()
|
||||||
|
subnet_mod.update(subnet)
|
||||||
|
for ip in ips:
|
||||||
|
ip_mod = models.IPAddress()
|
||||||
|
ip_mod.update(ip)
|
||||||
|
ip_mods.append(ip_mod)
|
||||||
|
|
||||||
|
db_mod = "quark.db.api"
|
||||||
|
with contextlib.nested(
|
||||||
|
mock.patch("%s.subnet_find" % db_mod),
|
||||||
|
mock.patch("%s.subnet_delete" % db_mod)
|
||||||
|
) as (sub_find, sub_delete):
|
||||||
|
if subnet_mod:
|
||||||
|
subnet_mod.allocated_ips = ip_mods
|
||||||
|
sub_find.return_value = subnet_mod
|
||||||
|
yield sub_delete
|
||||||
|
|
||||||
|
def test_delete_subnet(self):
|
||||||
|
subnet = dict(id=1)
|
||||||
|
with self._stubs(subnet=subnet, ips=[]) as sub_delete:
|
||||||
|
self.plugin.delete_subnet(self.context, 1)
|
||||||
|
self.assertTrue(sub_delete.called)
|
||||||
|
|
||||||
|
def test_delete_subnet_no_subnet_fails(self):
|
||||||
|
with self._stubs(subnet=None, ips=[]):
|
||||||
|
with self.assertRaises(exceptions.SubnetNotFound):
|
||||||
|
self.plugin.delete_subnet(self.context, 1)
|
||||||
|
|
||||||
|
def test_delete_subnet_has_allocated_ips_fails(self):
|
||||||
|
subnet = dict(id=1)
|
||||||
|
with self._stubs(subnet=subnet, ips=[{}]):
|
||||||
|
with self.assertRaises(exceptions.SubnetInUse):
|
||||||
|
self.plugin.delete_subnet(self.context, 1)
|
||||||
@@ -13,12 +13,9 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import copy
|
|
||||||
import mock
|
import mock
|
||||||
from neutron.api.v2 import attributes as neutron_attrs
|
|
||||||
from neutron.common import exceptions
|
from neutron.common import exceptions
|
||||||
from neutron.db import api as db_api
|
from neutron.db import api as db_api
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
@@ -42,13 +39,6 @@ class TestQuarkPlugin(test_base.TestBase):
|
|||||||
db_api.clear_db()
|
db_api.clear_db()
|
||||||
|
|
||||||
|
|
||||||
class TestQuarkGetSubnetCount(TestQuarkPlugin):
|
|
||||||
def test_get_subnet_count(self):
|
|
||||||
"""This isn't really testable."""
|
|
||||||
with mock.patch("quark.db.api.subnet_count_all"):
|
|
||||||
self.plugin.get_subnets_count(self.context, {})
|
|
||||||
|
|
||||||
|
|
||||||
class TestQuarkAPIExtensions(TestQuarkPlugin):
|
class TestQuarkAPIExtensions(TestQuarkPlugin):
|
||||||
"""Adds coverage for appending the API extension path."""
|
"""Adds coverage for appending the API extension path."""
|
||||||
def test_append_quark_extensions(self):
|
def test_append_quark_extensions(self):
|
||||||
@@ -69,643 +59,6 @@ class TestQuarkAPIExtensions(TestQuarkPlugin):
|
|||||||
"apple:banana:carrot")
|
"apple:banana:carrot")
|
||||||
|
|
||||||
|
|
||||||
class TestQuarkGetSubnets(TestQuarkPlugin):
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def _stubs(self, subnets=None, routes=None):
|
|
||||||
if routes is None:
|
|
||||||
routes = []
|
|
||||||
route_models = []
|
|
||||||
for route in routes:
|
|
||||||
r = models.Route()
|
|
||||||
r.update(route)
|
|
||||||
route_models.append(r)
|
|
||||||
|
|
||||||
if isinstance(subnets, list):
|
|
||||||
subnet_models = []
|
|
||||||
for subnet in subnets:
|
|
||||||
s_dict = subnet.copy()
|
|
||||||
s_dict["routes"] = route_models
|
|
||||||
s = models.Subnet(network=models.Network())
|
|
||||||
s.update(s_dict)
|
|
||||||
subnet_models.append(s)
|
|
||||||
elif subnets:
|
|
||||||
mod = models.Subnet(network=models.Network())
|
|
||||||
mod.update(subnets)
|
|
||||||
mod["routes"] = route_models
|
|
||||||
subnet_models = mod
|
|
||||||
else:
|
|
||||||
subnet_models = None
|
|
||||||
|
|
||||||
with mock.patch("quark.db.api.subnet_find") as subnet_find:
|
|
||||||
subnet_find.return_value = subnet_models
|
|
||||||
yield
|
|
||||||
|
|
||||||
def test_subnets_list(self):
|
|
||||||
subnet_id = str(uuid.uuid4())
|
|
||||||
route = dict(id=1, cidr="0.0.0.0/0", gateway="192.168.0.1")
|
|
||||||
|
|
||||||
subnet = dict(id=subnet_id, network_id=1, name=subnet_id,
|
|
||||||
tenant_id=self.context.tenant_id, ip_version=4,
|
|
||||||
cidr="192.168.0.0/24", gateway_ip="192.168.0.1",
|
|
||||||
dns_nameservers=[],
|
|
||||||
enable_dhcp=None)
|
|
||||||
expected_route = dict(destination=route["cidr"],
|
|
||||||
nexthop=route["gateway"])
|
|
||||||
|
|
||||||
with self._stubs(subnets=[subnet], routes=[route]):
|
|
||||||
res = self.plugin.get_subnets(self.context, {}, {})
|
|
||||||
# Compare routes separately
|
|
||||||
routes = res[0].pop("host_routes")
|
|
||||||
for key in subnet.keys():
|
|
||||||
self.assertEqual(res[0][key], subnet[key])
|
|
||||||
for key in expected_route.keys():
|
|
||||||
self.assertEqual(routes[0][key], expected_route[key])
|
|
||||||
|
|
||||||
def test_subnet_show_fail(self):
|
|
||||||
with self._stubs():
|
|
||||||
with self.assertRaises(exceptions.SubnetNotFound):
|
|
||||||
self.plugin.get_subnet(self.context, 1)
|
|
||||||
|
|
||||||
def test_subnet_show(self):
|
|
||||||
subnet_id = str(uuid.uuid4())
|
|
||||||
route = dict(id=1, cidr="0.0.0.0/0", gateway="192.168.0.1",
|
|
||||||
subnet_id=subnet_id)
|
|
||||||
|
|
||||||
expected_route = dict(destination=route["cidr"],
|
|
||||||
nexthop=route["gateway"])
|
|
||||||
|
|
||||||
subnet = dict(id=subnet_id, network_id=1, name=subnet_id,
|
|
||||||
tenant_id=self.context.tenant_id, ip_version=4,
|
|
||||||
cidr="192.168.0.0/24", gateway_ip="192.168.0.1",
|
|
||||||
dns_nameservers=[],
|
|
||||||
enable_dhcp=None)
|
|
||||||
|
|
||||||
with self._stubs(subnets=subnet, routes=[route]):
|
|
||||||
res = self.plugin.get_subnet(self.context, subnet_id)
|
|
||||||
|
|
||||||
# Compare routes separately
|
|
||||||
routes = res.pop("host_routes")
|
|
||||||
for key in subnet.keys():
|
|
||||||
self.assertEqual(res[key], subnet[key])
|
|
||||||
for key in expected_route.keys():
|
|
||||||
self.assertEqual(routes[0][key], expected_route[key])
|
|
||||||
|
|
||||||
|
|
||||||
class TestQuarkCreateSubnetOverlapping(TestQuarkPlugin):
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def _stubs(self, subnets=None):
|
|
||||||
if subnets is None:
|
|
||||||
subnets = []
|
|
||||||
subnet_models = []
|
|
||||||
for subnet in subnets:
|
|
||||||
s = models.Subnet()
|
|
||||||
s.update(subnet)
|
|
||||||
subnet_models.append(s)
|
|
||||||
network = models.Network()
|
|
||||||
network.update(dict(id=1, subnets=subnet_models))
|
|
||||||
with contextlib.nested(
|
|
||||||
mock.patch("quark.db.api.network_find"),
|
|
||||||
mock.patch("quark.db.api.subnet_find"),
|
|
||||||
mock.patch("quark.db.api.subnet_create")
|
|
||||||
) as (net_find, subnet_find, subnet_create):
|
|
||||||
net_find.return_value = network
|
|
||||||
subnet_find.return_value = subnet_models
|
|
||||||
subnet_create.return_value = models.Subnet(
|
|
||||||
network=models.Network(),
|
|
||||||
cidr="192.168.1.1/24")
|
|
||||||
yield subnet_create
|
|
||||||
|
|
||||||
def test_create_subnet_overlapping_true(self):
|
|
||||||
cfg.CONF.set_override('allow_overlapping_ips', True)
|
|
||||||
with self._stubs() as subnet_create:
|
|
||||||
s = dict(subnet=dict(
|
|
||||||
gateway_ip=neutron_attrs.ATTR_NOT_SPECIFIED,
|
|
||||||
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
|
|
||||||
cidr="192.168.1.1/8",
|
|
||||||
network_id=1))
|
|
||||||
self.plugin.create_subnet(self.context, s)
|
|
||||||
self.assertEqual(subnet_create.call_count, 1)
|
|
||||||
|
|
||||||
def test_create_subnet_overlapping_false(self):
|
|
||||||
cfg.CONF.set_override('allow_overlapping_ips', False)
|
|
||||||
with self._stubs() as subnet_create:
|
|
||||||
s = dict(subnet=dict(
|
|
||||||
gateway_ip=neutron_attrs.ATTR_NOT_SPECIFIED,
|
|
||||||
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
|
|
||||||
cidr="192.168.1.1/8",
|
|
||||||
network_id=1))
|
|
||||||
self.plugin.create_subnet(self.context, s)
|
|
||||||
self.assertEqual(subnet_create.call_count, 1)
|
|
||||||
|
|
||||||
def test_create_subnet_overlapping_conflict(self):
|
|
||||||
cfg.CONF.set_override('allow_overlapping_ips', False)
|
|
||||||
with self._stubs(subnets=[dict(cidr="192.168.10.1/24")]):
|
|
||||||
with self.assertRaises(exceptions.InvalidInput):
|
|
||||||
s = dict(subnet=dict(cidr="192.168.1.1/8",
|
|
||||||
network_id=1))
|
|
||||||
self.plugin.create_subnet(self.context, s)
|
|
||||||
|
|
||||||
|
|
||||||
class TestQuarkCreateSubnetAllocationPools(TestQuarkPlugin):
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def _stubs(self, subnet):
|
|
||||||
s = models.Subnet(network=models.Network(id=1, subnets=[]))
|
|
||||||
s.update(subnet)
|
|
||||||
|
|
||||||
with contextlib.nested(
|
|
||||||
mock.patch("quark.db.api.network_find"),
|
|
||||||
mock.patch("quark.db.api.subnet_find"),
|
|
||||||
mock.patch("quark.db.api.subnet_create")
|
|
||||||
) as (net_find, subnet_find, subnet_create):
|
|
||||||
net_find.return_value = s["network"]
|
|
||||||
subnet_find.return_value = []
|
|
||||||
subnet_create.return_value = s
|
|
||||||
yield subnet_create
|
|
||||||
|
|
||||||
def test_create_subnet_allocation_pools_zero(self):
|
|
||||||
s = dict(subnet=dict(
|
|
||||||
cidr="192.168.1.1/24",
|
|
||||||
network_id=1))
|
|
||||||
with self._stubs(s["subnet"]) as subnet_create:
|
|
||||||
resp = self.plugin.create_subnet(self.context, s)
|
|
||||||
self.assertEqual(subnet_create.call_count, 1)
|
|
||||||
self.assertEqual(resp["allocation_pools"],
|
|
||||||
[dict(start="192.168.1.0", end="192.168.1.255")])
|
|
||||||
|
|
||||||
def test_create_subnet_allocation_pools_one(self):
|
|
||||||
pools = [dict(start="192.168.1.10", end="192.168.1.20")]
|
|
||||||
s = dict(subnet=dict(
|
|
||||||
allocation_pools=pools,
|
|
||||||
cidr="192.168.1.1/24",
|
|
||||||
network_id=1))
|
|
||||||
with self._stubs(s["subnet"]) as subnet_create:
|
|
||||||
resp = self.plugin.create_subnet(self.context, s)
|
|
||||||
self.assertEqual(subnet_create.call_count, 1)
|
|
||||||
self.assertEqual(resp["allocation_pools"], pools)
|
|
||||||
|
|
||||||
def test_create_subnet_allocation_pools_two(self):
|
|
||||||
pools = [dict(start="192.168.1.10", end="192.168.1.20"),
|
|
||||||
dict(start="192.168.1.40", end="192.168.1.50")]
|
|
||||||
s = dict(subnet=dict(
|
|
||||||
allocation_pools=pools,
|
|
||||||
cidr="192.168.1.1/24",
|
|
||||||
network_id=1))
|
|
||||||
with self._stubs(s["subnet"]) as subnet_create:
|
|
||||||
resp = self.plugin.create_subnet(self.context, s)
|
|
||||||
self.assertEqual(subnet_create.call_count, 1)
|
|
||||||
self.assertEqual(resp["allocation_pools"], pools)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO(amir): Refactor the tests to test individual subnet attributes.
|
|
||||||
# * copy.deepcopy was necessary to maintain tests on keys, which is a bit ugly.
|
|
||||||
# * workaround is also in place for lame ATTR_NOT_SPECIFIED object()
|
|
||||||
class TestQuarkCreateSubnet(TestQuarkPlugin):
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def _stubs(self, subnet=None, network=None, routes=None, dns=None):
|
|
||||||
subnet_mod = models.Subnet(network=models.Network())
|
|
||||||
dns_ips = subnet.pop("dns_nameservers", [])
|
|
||||||
host_routes = subnet.pop("host_routes", [])
|
|
||||||
subnet_mod.update(subnet)
|
|
||||||
subnet["dns_nameservers"] = dns_ips
|
|
||||||
subnet["host_routes"] = host_routes
|
|
||||||
routes = routes or []
|
|
||||||
dns = dns or []
|
|
||||||
route_models = [models.Route(**r) for r in routes]
|
|
||||||
dns_models = [models.DNSNameserver(**d) for d in dns]
|
|
||||||
|
|
||||||
with contextlib.nested(
|
|
||||||
mock.patch("quark.db.api.subnet_create"),
|
|
||||||
mock.patch("quark.db.api.network_find"),
|
|
||||||
mock.patch("quark.db.api.dns_create"),
|
|
||||||
mock.patch("quark.db.api.route_create"),
|
|
||||||
) as (subnet_create, net_find, dns_create, route_create):
|
|
||||||
subnet_create.return_value = subnet_mod
|
|
||||||
net_find.return_value = network
|
|
||||||
route_create.side_effect = route_models
|
|
||||||
dns_create.side_effect = dns_models
|
|
||||||
yield subnet_create, dns_create, route_create
|
|
||||||
|
|
||||||
def test_create_subnet(self):
|
|
||||||
routes = [dict(cidr="0.0.0.0/0", gateway="0.0.0.0")]
|
|
||||||
subnet = dict(
|
|
||||||
subnet=dict(network_id=1,
|
|
||||||
tenant_id=self.context.tenant_id, ip_version=4,
|
|
||||||
cidr="172.16.0.0/24", gateway_ip="0.0.0.0",
|
|
||||||
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
|
|
||||||
host_routes=neutron_attrs.ATTR_NOT_SPECIFIED,
|
|
||||||
enable_dhcp=None))
|
|
||||||
network = dict(network_id=1)
|
|
||||||
with self._stubs(
|
|
||||||
subnet=subnet["subnet"],
|
|
||||||
network=network,
|
|
||||||
routes=routes
|
|
||||||
) as (subnet_create, dns_create, route_create):
|
|
||||||
dns_nameservers = subnet["subnet"].pop("dns_nameservers")
|
|
||||||
host_routes = subnet["subnet"].pop("host_routes")
|
|
||||||
subnet_request = copy.deepcopy(subnet)
|
|
||||||
subnet_request["subnet"]["dns_nameservers"] = dns_nameservers
|
|
||||||
subnet_request["subnet"]["host_routes"] = host_routes
|
|
||||||
res = self.plugin.create_subnet(self.context,
|
|
||||||
subnet_request)
|
|
||||||
self.assertEqual(subnet_create.call_count, 1)
|
|
||||||
self.assertEqual(dns_create.call_count, 0)
|
|
||||||
self.assertEqual(route_create.call_count, 1)
|
|
||||||
for key in subnet["subnet"].keys():
|
|
||||||
if key == "host_routes":
|
|
||||||
self.assertEqual(res[key][0]["destination"], "0.0.0.0/0")
|
|
||||||
self.assertEqual(res[key][0]["nexthop"], "0.0.0.0")
|
|
||||||
else:
|
|
||||||
self.assertEqual(res[key], subnet["subnet"][key])
|
|
||||||
|
|
||||||
def test_create_subnet_no_network_fails(self):
|
|
||||||
subnet = dict(subnet=dict(network_id=1))
|
|
||||||
with self._stubs(subnet=dict(), network=None):
|
|
||||||
with self.assertRaises(exceptions.NetworkNotFound):
|
|
||||||
self.plugin.create_subnet(self.context, subnet)
|
|
||||||
|
|
||||||
def test_create_subnet_no_gateway_ip_defaults(self):
|
|
||||||
routes = [dict(cidr="0.0.0.0/0", gateway="172.16.0.1")]
|
|
||||||
subnet = dict(
|
|
||||||
subnet=dict(network_id=1,
|
|
||||||
tenant_id=self.context.tenant_id, ip_version=4,
|
|
||||||
cidr="172.16.0.0/24",
|
|
||||||
gateway_ip=neutron_attrs.ATTR_NOT_SPECIFIED,
|
|
||||||
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
|
|
||||||
enable_dhcp=None))
|
|
||||||
network = dict(network_id=1)
|
|
||||||
with self._stubs(
|
|
||||||
subnet=subnet["subnet"],
|
|
||||||
network=network,
|
|
||||||
routes=routes
|
|
||||||
) as (subnet_create, dns_create, route_create):
|
|
||||||
dns_nameservers = subnet["subnet"].pop("dns_nameservers")
|
|
||||||
gateway_ip = subnet["subnet"].pop("gateway_ip")
|
|
||||||
subnet_request = copy.deepcopy(subnet)
|
|
||||||
subnet_request["subnet"]["dns_nameservers"] = dns_nameservers
|
|
||||||
subnet_request["subnet"]["gateway_ip"] = gateway_ip
|
|
||||||
res = self.plugin.create_subnet(self.context, subnet_request)
|
|
||||||
self.assertEqual(subnet_create.call_count, 1)
|
|
||||||
self.assertEqual(dns_create.call_count, 0)
|
|
||||||
self.assertEqual(route_create.call_count, 1)
|
|
||||||
for key in subnet["subnet"].keys():
|
|
||||||
if key == "gateway_ip":
|
|
||||||
self.assertEqual(res[key], "172.16.0.1")
|
|
||||||
elif key == "host_routes":
|
|
||||||
self.assertEqual(res[key][0]["destination"], "0.0.0.0/0")
|
|
||||||
self.assertEqual(res[key][0]["nexthop"], "172.16.0.1")
|
|
||||||
else:
|
|
||||||
self.assertEqual(res[key], subnet["subnet"][key])
|
|
||||||
|
|
||||||
def test_create_subnet_dns_nameservers(self):
|
|
||||||
routes = [dict(cidr="0.0.0.0/0", gateway="0.0.0.0")]
|
|
||||||
dns_ns = [dict(ip="4.2.2.1"), dict(ip="4.2.2.2")]
|
|
||||||
subnet = dict(
|
|
||||||
subnet=dict(network_id=1,
|
|
||||||
tenant_id=self.context.tenant_id, ip_version=4,
|
|
||||||
cidr="172.16.0.0/24", gateway_ip="0.0.0.0",
|
|
||||||
dns_nameservers=["4.2.2.1", "4.2.2.2"],
|
|
||||||
enable_dhcp=None))
|
|
||||||
network = dict(network_id=1)
|
|
||||||
with self._stubs(
|
|
||||||
subnet=subnet["subnet"],
|
|
||||||
network=network,
|
|
||||||
routes=routes,
|
|
||||||
dns=dns_ns
|
|
||||||
) as (subnet_create, dns_create, route_create):
|
|
||||||
res = self.plugin.create_subnet(self.context,
|
|
||||||
copy.deepcopy(subnet))
|
|
||||||
self.assertEqual(subnet_create.call_count, 1)
|
|
||||||
self.assertEqual(dns_create.call_count, 2)
|
|
||||||
self.assertEqual(route_create.call_count, 1)
|
|
||||||
for key in subnet["subnet"].keys():
|
|
||||||
if key == "host_routes":
|
|
||||||
self.assertEqual(res[key][0]["destination"], "0.0.0.0/0")
|
|
||||||
self.assertEqual(res[key][0]["nexthop"], "0.0.0.0")
|
|
||||||
else:
|
|
||||||
self.assertEqual(res[key], subnet["subnet"][key])
|
|
||||||
|
|
||||||
def test_create_subnet_routes(self):
|
|
||||||
routes = [dict(cidr="1.1.1.1/8", gateway="172.16.0.4"),
|
|
||||||
dict(cidr="0.0.0.0/0", gateway="0.0.0.0")]
|
|
||||||
subnet = dict(
|
|
||||||
subnet=dict(network_id=1,
|
|
||||||
tenant_id=self.context.tenant_id, ip_version=4,
|
|
||||||
cidr="172.16.0.0/24", gateway_ip="0.0.0.0",
|
|
||||||
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
|
|
||||||
host_routes=[{"destination": "1.1.1.1/8",
|
|
||||||
"nexthop": "172.16.0.4"}],
|
|
||||||
enable_dhcp=None))
|
|
||||||
network = dict(network_id=1)
|
|
||||||
with self._stubs(
|
|
||||||
subnet=subnet["subnet"],
|
|
||||||
network=network,
|
|
||||||
routes=routes
|
|
||||||
) as (subnet_create, dns_create, route_create):
|
|
||||||
dns_nameservers = subnet["subnet"].pop("dns_nameservers")
|
|
||||||
subnet_request = copy.deepcopy(subnet)
|
|
||||||
subnet_request["subnet"]["dns_nameservers"] = dns_nameservers
|
|
||||||
res = self.plugin.create_subnet(self.context, subnet_request)
|
|
||||||
self.assertEqual(subnet_create.call_count, 1)
|
|
||||||
self.assertEqual(dns_create.call_count, 0)
|
|
||||||
self.assertEqual(route_create.call_count, 2)
|
|
||||||
for key in subnet["subnet"].keys():
|
|
||||||
if key == "host_routes":
|
|
||||||
res_tuples = [(r["destination"], r["nexthop"])
|
|
||||||
for r in res[key]]
|
|
||||||
self.assertIn(("1.1.1.1/8", "172.16.0.4"), res_tuples)
|
|
||||||
self.assertIn(("0.0.0.0/0", "0.0.0.0"), res_tuples)
|
|
||||||
self.assertEqual(2, len(res_tuples))
|
|
||||||
else:
|
|
||||||
self.assertEqual(res[key], subnet["subnet"][key])
|
|
||||||
|
|
||||||
def test_create_subnet_default_route(self):
|
|
||||||
routes = [dict(cidr="0.0.0.0/0", gateway="172.16.0.4")]
|
|
||||||
subnet = dict(
|
|
||||||
subnet=dict(network_id=1,
|
|
||||||
tenant_id=self.context.tenant_id, ip_version=4,
|
|
||||||
cidr="172.16.0.0/24",
|
|
||||||
gateway_ip=neutron_attrs.ATTR_NOT_SPECIFIED,
|
|
||||||
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
|
|
||||||
host_routes=[{"destination": "0.0.0.0/0",
|
|
||||||
"nexthop": "172.16.0.4"}],
|
|
||||||
enable_dhcp=None))
|
|
||||||
network = dict(network_id=1)
|
|
||||||
with self._stubs(
|
|
||||||
subnet=subnet["subnet"],
|
|
||||||
network=network,
|
|
||||||
routes=routes
|
|
||||||
) as (subnet_create, dns_create, route_create):
|
|
||||||
dns_nameservers = subnet["subnet"].pop("dns_nameservers")
|
|
||||||
gateway_ip = subnet["subnet"].pop("gateway_ip")
|
|
||||||
subnet_request = copy.deepcopy(subnet)
|
|
||||||
subnet_request["subnet"]["dns_nameservers"] = dns_nameservers
|
|
||||||
subnet_request["subnet"]["gateway_ip"] = gateway_ip
|
|
||||||
res = self.plugin.create_subnet(self.context, subnet_request)
|
|
||||||
self.assertEqual(subnet_create.call_count, 1)
|
|
||||||
self.assertEqual(dns_create.call_count, 0)
|
|
||||||
self.assertEqual(route_create.call_count, 1)
|
|
||||||
for key in subnet["subnet"].keys():
|
|
||||||
if key == "host_routes":
|
|
||||||
res_tuples = [(r["destination"], r["nexthop"])
|
|
||||||
for r in res[key]]
|
|
||||||
self.assertEqual([("0.0.0.0/0", "172.16.0.4")], res_tuples)
|
|
||||||
elif key == "gateway_ip":
|
|
||||||
self.assertEqual(res[key], "172.16.0.4")
|
|
||||||
else:
|
|
||||||
self.assertEqual(res[key], subnet["subnet"][key])
|
|
||||||
|
|
||||||
def test_create_subnet_default_route_gateway_ip(self):
|
|
||||||
"""If default route (host_routes) and gateway_ip are both provided,
|
|
||||||
then host_route takes precedence.
|
|
||||||
"""
|
|
||||||
routes = [dict(cidr="0.0.0.0/0", gateway="172.16.0.4")]
|
|
||||||
subnet = dict(
|
|
||||||
subnet=dict(network_id=1,
|
|
||||||
tenant_id=self.context.tenant_id, ip_version=4,
|
|
||||||
cidr="172.16.0.0/24",
|
|
||||||
gateway_ip="172.16.0.3",
|
|
||||||
dns_nameservers=neutron_attrs.ATTR_NOT_SPECIFIED,
|
|
||||||
host_routes=[{"destination": "0.0.0.0/0",
|
|
||||||
"nexthop": "172.16.0.4"}],
|
|
||||||
enable_dhcp=None))
|
|
||||||
network = dict(network_id=1)
|
|
||||||
with self._stubs(
|
|
||||||
subnet=subnet["subnet"],
|
|
||||||
network=network,
|
|
||||||
routes=routes
|
|
||||||
) as (subnet_create, dns_create, route_create):
|
|
||||||
dns_nameservers = subnet["subnet"].pop("dns_nameservers")
|
|
||||||
subnet_request = copy.deepcopy(subnet)
|
|
||||||
subnet_request["subnet"]["dns_nameservers"] = dns_nameservers
|
|
||||||
res = self.plugin.create_subnet(self.context, subnet_request)
|
|
||||||
self.assertEqual(subnet_create.call_count, 1)
|
|
||||||
self.assertEqual(dns_create.call_count, 0)
|
|
||||||
self.assertEqual(route_create.call_count, 1)
|
|
||||||
for key in subnet["subnet"].keys():
|
|
||||||
if key == "host_routes":
|
|
||||||
res_tuples = [(r["destination"], r["nexthop"])
|
|
||||||
for r in res[key]]
|
|
||||||
self.assertEqual([("0.0.0.0/0", "172.16.0.4")], res_tuples)
|
|
||||||
elif key == "gateway_ip":
|
|
||||||
self.assertEqual(res[key], "172.16.0.4")
|
|
||||||
else:
|
|
||||||
self.assertEqual(res[key], subnet["subnet"][key])
|
|
||||||
|
|
||||||
|
|
||||||
class TestQuarkUpdateSubnet(TestQuarkPlugin):
|
|
||||||
DEFAULT_ROUTE = [dict(destination="0.0.0.0/0",
|
|
||||||
nexthop="172.16.0.1")]
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def _stubs(self, host_routes=None, new_routes=None, find_routes=True,
|
|
||||||
new_dns_servers=None):
|
|
||||||
if host_routes is None:
|
|
||||||
host_routes = []
|
|
||||||
if new_routes:
|
|
||||||
new_routes = [models.Route(cidr=r["destination"],
|
|
||||||
gateway=r["nexthop"],
|
|
||||||
subnet_id=1)
|
|
||||||
for r in new_routes]
|
|
||||||
if new_dns_servers:
|
|
||||||
new_dns_servers = [models.DNSNameserver(
|
|
||||||
ip=ip,
|
|
||||||
subnet_id=1) for ip in new_dns_servers]
|
|
||||||
|
|
||||||
subnet = dict(
|
|
||||||
id=1,
|
|
||||||
network_id=1,
|
|
||||||
tenant_id=self.context.tenant_id, ip_version=4,
|
|
||||||
cidr="172.16.0.0/24",
|
|
||||||
host_routes=host_routes,
|
|
||||||
dns_nameservers=["4.2.2.1", "4.2.2.2"],
|
|
||||||
enable_dhcp=None)
|
|
||||||
|
|
||||||
dns_ips = subnet.pop("dns_nameservers", [])
|
|
||||||
host_routes = subnet.pop("host_routes", [])
|
|
||||||
subnet_mod = models.Subnet()
|
|
||||||
|
|
||||||
subnet_mod.update(subnet)
|
|
||||||
|
|
||||||
subnet_mod["dns_nameservers"] = [models.DNSNameserver(ip=ip)
|
|
||||||
for ip in dns_ips]
|
|
||||||
subnet_mod["routes"] = [models.Route(cidr=r["destination"],
|
|
||||||
gateway=r["nexthop"],
|
|
||||||
subnet_id=subnet_mod["id"])
|
|
||||||
for r in host_routes]
|
|
||||||
with contextlib.nested(
|
|
||||||
mock.patch("quark.db.api.subnet_find"),
|
|
||||||
mock.patch("quark.db.api.subnet_update"),
|
|
||||||
mock.patch("quark.db.api.dns_create"),
|
|
||||||
mock.patch("quark.db.api.route_find"),
|
|
||||||
mock.patch("quark.db.api.route_update"),
|
|
||||||
mock.patch("quark.db.api.route_create"),
|
|
||||||
) as (subnet_find, subnet_update,
|
|
||||||
dns_create,
|
|
||||||
route_find, route_update, route_create):
|
|
||||||
subnet_find.return_value = subnet_mod
|
|
||||||
route_find.return_value = subnet_mod["routes"][0] \
|
|
||||||
if subnet_mod["routes"] and find_routes else None
|
|
||||||
new_subnet_mod = models.Subnet(network=models.Network())
|
|
||||||
new_subnet_mod.update(subnet_mod)
|
|
||||||
if new_routes:
|
|
||||||
new_subnet_mod["routes"] = new_routes
|
|
||||||
if new_dns_servers:
|
|
||||||
new_subnet_mod["dns_nameservers"] = new_dns_servers
|
|
||||||
subnet_update.return_value = new_subnet_mod
|
|
||||||
yield dns_create, route_update, route_create
|
|
||||||
|
|
||||||
def test_update_subnet_not_found(self):
|
|
||||||
with self.assertRaises(exceptions.SubnetNotFound):
|
|
||||||
self.plugin.update_subnet(self.context, 1, {})
|
|
||||||
|
|
||||||
def test_update_subnet_dns_nameservers(self):
|
|
||||||
new_dns_servers = ["1.1.1.2"]
|
|
||||||
with self._stubs(
|
|
||||||
host_routes=self.DEFAULT_ROUTE,
|
|
||||||
new_dns_servers=new_dns_servers
|
|
||||||
) as (dns_create, route_update, route_create):
|
|
||||||
req = dict(subnet=dict(dns_nameservers=new_dns_servers))
|
|
||||||
res = self.plugin.update_subnet(self.context,
|
|
||||||
1,
|
|
||||||
req)
|
|
||||||
self.assertEqual(dns_create.call_count, 1)
|
|
||||||
self.assertEqual(route_create.call_count, 0)
|
|
||||||
self.assertEqual(res["dns_nameservers"], new_dns_servers)
|
|
||||||
|
|
||||||
def test_update_subnet_routes(self):
|
|
||||||
new_routes = [dict(destination="10.0.0.0/24",
|
|
||||||
nexthop="1.1.1.1")]
|
|
||||||
with self._stubs(
|
|
||||||
host_routes=self.DEFAULT_ROUTE,
|
|
||||||
new_routes=new_routes
|
|
||||||
) as (dns_create, route_update, route_create):
|
|
||||||
req = dict(subnet=dict(
|
|
||||||
host_routes=new_routes))
|
|
||||||
res = self.plugin.update_subnet(self.context, 1, req)
|
|
||||||
self.assertEqual(dns_create.call_count, 0)
|
|
||||||
self.assertEqual(route_create.call_count, 1)
|
|
||||||
self.assertEqual(len(res["host_routes"]), 1)
|
|
||||||
self.assertEqual(res["host_routes"][0]["destination"],
|
|
||||||
"10.0.0.0/24")
|
|
||||||
self.assertEqual(res["host_routes"][0]["nexthop"],
|
|
||||||
"1.1.1.1")
|
|
||||||
self.assertIsNone(res["gateway_ip"])
|
|
||||||
|
|
||||||
def test_update_subnet_gateway_ip_with_default_route_in_db(self):
|
|
||||||
with self._stubs(
|
|
||||||
host_routes=self.DEFAULT_ROUTE,
|
|
||||||
new_routes=[dict(destination="0.0.0.0/0", nexthop="1.2.3.4")]
|
|
||||||
) as (dns_create, route_update, route_create):
|
|
||||||
req = dict(subnet=dict(gateway_ip="1.2.3.4"))
|
|
||||||
res = self.plugin.update_subnet(self.context, 1, req)
|
|
||||||
self.assertEqual(dns_create.call_count, 0)
|
|
||||||
self.assertEqual(route_create.call_count, 0)
|
|
||||||
self.assertEqual(route_update.call_count, 1)
|
|
||||||
self.assertEqual(len(res["host_routes"]), 1)
|
|
||||||
self.assertEqual(res["host_routes"][0]["destination"],
|
|
||||||
"0.0.0.0/0")
|
|
||||||
self.assertEqual(res["host_routes"][0]["nexthop"],
|
|
||||||
"1.2.3.4")
|
|
||||||
self.assertEqual(res["gateway_ip"], "1.2.3.4")
|
|
||||||
|
|
||||||
def test_update_subnet_gateway_ip_with_non_default_route_in_db(self):
|
|
||||||
with self._stubs(
|
|
||||||
host_routes=[dict(destination="1.1.1.1/8", nexthop="9.9.9.9")],
|
|
||||||
find_routes=False,
|
|
||||||
new_routes=[dict(destination="1.1.1.1/8", nexthop="9.9.9.9"),
|
|
||||||
dict(destination="0.0.0.0/0", nexthop="1.2.3.4")]
|
|
||||||
) as (dns_create, route_update, route_create):
|
|
||||||
req = dict(subnet=dict(gateway_ip="1.2.3.4"))
|
|
||||||
res = self.plugin.update_subnet(self.context, 1, req)
|
|
||||||
self.assertEqual(dns_create.call_count, 0)
|
|
||||||
self.assertEqual(route_create.call_count, 1)
|
|
||||||
|
|
||||||
self.assertEqual(res["gateway_ip"], "1.2.3.4")
|
|
||||||
|
|
||||||
self.assertEqual(len(res["host_routes"]), 2)
|
|
||||||
res_tuples = [(r["destination"], r["nexthop"])
|
|
||||||
for r in res["host_routes"]]
|
|
||||||
self.assertIn(("0.0.0.0/0", "1.2.3.4"), res_tuples)
|
|
||||||
self.assertIn(("1.1.1.1/8", "9.9.9.9"), res_tuples)
|
|
||||||
|
|
||||||
def test_update_subnet_gateway_ip_without_default_route_in_db(self):
|
|
||||||
with self._stubs(
|
|
||||||
host_routes=None,
|
|
||||||
new_routes=[dict(destination="0.0.0.0/0", nexthop="1.2.3.4")]
|
|
||||||
) as (dns_create, route_update, route_create):
|
|
||||||
req = dict(subnet=dict(gateway_ip="1.2.3.4"))
|
|
||||||
res = self.plugin.update_subnet(self.context, 1, req)
|
|
||||||
self.assertEqual(dns_create.call_count, 0)
|
|
||||||
self.assertEqual(route_create.call_count, 1)
|
|
||||||
self.assertEqual(len(res["host_routes"]), 1)
|
|
||||||
self.assertEqual(res["host_routes"][0]["destination"],
|
|
||||||
"0.0.0.0/0")
|
|
||||||
self.assertEqual(res["host_routes"][0]["nexthop"],
|
|
||||||
"1.2.3.4")
|
|
||||||
self.assertEqual(res["gateway_ip"], "1.2.3.4")
|
|
||||||
|
|
||||||
def test_update_subnet_gateway_ip_with_default_route_in_args(self):
|
|
||||||
new_routes = [dict(destination="0.0.0.0/0",
|
|
||||||
nexthop="4.3.2.1")]
|
|
||||||
with self._stubs(
|
|
||||||
host_routes=self.DEFAULT_ROUTE,
|
|
||||||
new_routes=new_routes
|
|
||||||
) as (dns_create, route_update, route_create):
|
|
||||||
req = dict(subnet=dict(
|
|
||||||
host_routes=new_routes,
|
|
||||||
gateway_ip="1.2.3.4"))
|
|
||||||
res = self.plugin.update_subnet(self.context, 1, req)
|
|
||||||
self.assertEqual(dns_create.call_count, 0)
|
|
||||||
self.assertEqual(route_create.call_count, 1)
|
|
||||||
self.assertEqual(len(res["host_routes"]), 1)
|
|
||||||
self.assertEqual(res["host_routes"][0]["destination"],
|
|
||||||
"0.0.0.0/0")
|
|
||||||
self.assertEqual(res["host_routes"][0]["nexthop"],
|
|
||||||
"4.3.2.1")
|
|
||||||
self.assertEqual(res["gateway_ip"], "4.3.2.1")
|
|
||||||
|
|
||||||
|
|
||||||
class TestQuarkDeleteSubnet(TestQuarkPlugin):
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def _stubs(self, subnet, ips):
|
|
||||||
ip_mods = []
|
|
||||||
subnet_mod = None
|
|
||||||
if subnet:
|
|
||||||
subnet_mod = models.Subnet()
|
|
||||||
subnet_mod.update(subnet)
|
|
||||||
for ip in ips:
|
|
||||||
ip_mod = models.IPAddress()
|
|
||||||
ip_mod.update(ip)
|
|
||||||
ip_mods.append(ip_mod)
|
|
||||||
|
|
||||||
db_mod = "quark.db.api"
|
|
||||||
with contextlib.nested(
|
|
||||||
mock.patch("%s.subnet_find" % db_mod),
|
|
||||||
mock.patch("%s.subnet_delete" % db_mod)
|
|
||||||
) as (sub_find, sub_delete):
|
|
||||||
if subnet_mod:
|
|
||||||
subnet_mod.allocated_ips = ip_mods
|
|
||||||
sub_find.return_value = subnet_mod
|
|
||||||
yield sub_delete
|
|
||||||
|
|
||||||
def test_delete_subnet(self):
|
|
||||||
subnet = dict(id=1)
|
|
||||||
with self._stubs(subnet=subnet, ips=[]) as sub_delete:
|
|
||||||
self.plugin.delete_subnet(self.context, 1)
|
|
||||||
self.assertTrue(sub_delete.called)
|
|
||||||
|
|
||||||
def test_delete_subnet_no_subnet_fails(self):
|
|
||||||
with self._stubs(subnet=None, ips=[]):
|
|
||||||
with self.assertRaises(exceptions.SubnetNotFound):
|
|
||||||
self.plugin.delete_subnet(self.context, 1)
|
|
||||||
|
|
||||||
def test_delete_subnet_has_allocated_ips_fails(self):
|
|
||||||
subnet = dict(id=1)
|
|
||||||
with self._stubs(subnet=subnet, ips=[{}]):
|
|
||||||
with self.assertRaises(exceptions.SubnetInUse):
|
|
||||||
self.plugin.delete_subnet(self.context, 1)
|
|
||||||
|
|
||||||
|
|
||||||
class TestQuarkGetNetworks(TestQuarkPlugin):
|
class TestQuarkGetNetworks(TestQuarkPlugin):
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def _stubs(self, nets=None, subnets=None):
|
def _stubs(self, nets=None, subnets=None):
|
||||||
|
|||||||
Reference in New Issue
Block a user