Files
quark/quark/plugin.py
Jason Kölker fb777fa1f8 Merge pull request #33 from Cerberus98/mergeup
Adds IP Address tests
2013-03-25 15:52:42 -07:00

749 lines
31 KiB
Python

# 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.
"""
v2 Quantum Plug-in API Quark Implementation
"""
import inspect
import netaddr
from oslo.config import cfg
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy import event
from zope import sqlalchemy as zsa
from quantum.common import exceptions
from quantum.db import api as quantum_db_api
from quantum import quantum_plugin_base_v2
from quantum.openstack.common import importutils
from quantum.openstack.common import log as logging
from quantum.openstack.common import uuidutils
from quark.api import extensions
from quark.db import api as db_api
from quark.db import models
from quark import exceptions as quark_exceptions
LOG = logging.getLogger("quantum.quark")
CONF = cfg.CONF
quark_opts = [
cfg.StrOpt('net_driver',
default='quark.drivers.base.BaseDriver',
help=_('The client to use to talk to the backend')),
cfg.StrOpt('ipam_driver', default='quark.ipam.QuarkIpam',
help=_('IPAM Implementation to use')),
cfg.BoolOpt('ipam_reuse_after', default=7200,
help=_("Time in seconds til IP and MAC reuse"
"after deallocation.")),
cfg.StrOpt('net_driver_cfg', default='/etc/quantum/quark.ini',
help=_("Path to the config for the net driver"))
]
def append_quark_extensions(conf):
"""Adds the Quark API Extensions to the extension path.
Pulled out for test coveage.
"""
if 'api_extensions_path' in conf:
conf.set_override('api_extensions_path', ":".join(extensions.__path__))
CONF.register_opts(quark_opts, "QUARK")
append_quark_extensions(CONF)
# NOTE(jkoelker) init event listener that will ensure id is filled in
# on object creation (prior to commit).
def perhaps_generate_id(target, args, kwargs):
if hasattr(target, 'id') and target.id is None:
target.id = uuidutils.generate_uuid()
class Plugin(quantum_plugin_base_v2.QuantumPluginBaseV2):
# NOTE(mdietz): I hate this
supported_extension_aliases = ["mac_address_ranges", "routes",
"ip_addresses"]
def _initDBMaker(self):
# This needs to be called after _ENGINE is configured
session_maker = sessionmaker(bind=quantum_db_api._ENGINE,
extension=zsa.ZopeTransactionExtension())
quantum_db_api._MAKER = scoped_session(session_maker)
def __init__(self):
# NOTE(jkoelker) Register the event on all models that have ids
for _name, klass in inspect.getmembers(models, inspect.isclass):
if klass is models.HasId:
continue
if models.HasId in klass.mro():
event.listen(klass, "init", perhaps_generate_id)
quantum_db_api.configure_db()
self._initDBMaker()
self.net_driver = (importutils.import_class(CONF.QUARK.net_driver))()
self.net_driver.load_config(CONF.QUARK.net_driver_cfg)
self.ipam_driver = (importutils.import_class(CONF.QUARK.ipam_driver))()
self.ipam_reuse_after = CONF.QUARK.ipam_reuse_after
models.BASEV2.metadata.create_all(quantum_db_api._ENGINE)
def _make_network_dict(self, network, fields=None):
res = {'id': network.get('id'),
'name': network.get('name'),
'tenant_id': network.get('tenant_id'),
'admin_state_up': network.get('admin_state_up'),
'status': network.get('status'),
'shared': network.get('shared'),
#TODO(mdietz): this is the expected return. Then the client
# foolishly turns around and asks for the entire
# subnet list anyway! Plz2fix
'subnets': [s["id"] for s in network.get("subnets", [])]}
#'subnets': [self._make_subnet_dict(subnet)
# for subnet in network.get('subnets', [])]}
return res
def _subnet_dict(self, subnet, fields=None):
# TODO(mdietz): this is a hack to get nova to boot. We want to get the
# "default" route out of the database and use that
gateway_ip = "0.0.0.0"
return {"id": subnet.get('id'),
"name": subnet.get('id'),
"tenant_id": subnet.get('tenant_id'),
"network_id": subnet.get('network_id'),
"ip_version": subnet.get('ip_version'),
"allocation_pools": subnet.get("allocation_pools") or [],
"dns_nameservers": subnet.get("dns_nameservers") or [],
"cidr": subnet.get('cidr'),
"enable_dhcp": subnet.get('enable_dhcp'),
"gateway_ip": gateway_ip}
def _make_subnet_dict(self, subnet, fields=None):
res = self._subnet_dict(subnet, fields)
res["routes"] = [self._make_route_dict(r) for r in subnet["routes"]]
return res
def _port_dict(self, port, fields=None):
res = {"id": port.get("id"),
"name": port.get('id'),
"network_id": port.get("network_id"),
"tenant_id": port.get('tenant_id'),
"mac_address": port.get("mac_address"),
"admin_state_up": port.get("admin_state_up"),
"status": port.get("status"),
"device_id": port.get("device_id"),
"device_owner": port.get("device_owner")}
if isinstance(res["mac_address"], (int, long)):
res["mac_address"] = str(netaddr.EUI(res["mac_address"],
dialect=netaddr.mac_unix))
return res
def _make_port_address_dict(self, ip):
return {'subnet_id': ip.get("subnet_id"),
'ip_address': ip.formatted()}
def _make_port_dict(self, port, fields=None):
res = self._port_dict(port)
res["fixed_ips"] = [self._make_port_address_dict(ip)
for ip in port.ip_addresses]
return res
def _make_ports_list(self, query, fields=None):
ports = []
for port in query:
port_dict = self._port_dict(port, fields)
port_dict["fixed_ips"] = [self._make_port_address_dict(addr)
for addr in port.ip_addresses]
ports.append(port_dict)
return ports
def _make_subnets_list(self, query, fields=None):
subnets = []
for res in query:
subnet_dict = self._subnet_dict(res)
subnet_dict["routes"] = [self._make_route_dict(route)
for route in res.routes]
subnets.append(subnet_dict)
return subnets
def _make_mac_range_dict(self, mac_range):
return {"id": mac_range["id"],
"cidr": mac_range["cidr"]}
def _make_route_dict(self, route):
return {"id": route["id"],
"cidr": route["cidr"],
"gateway": route["gateway"],
"subnet_id": route["subnet_id"]}
def _make_ip_dict(self, address):
return {"id": address["id"],
"network_id": address["network_id"],
"address": address.formatted(),
"port_ids": [port["id"] for port in address["ports"]],
"subnet_id": address["subnet_id"]}
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: quantum api request context
: param subnet: dictionary describing the subnet, with keys
as listed in the RESOURCE_ATTRIBUTE_MAP object in
quantum/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, net_id, scope=db_api.ONE)
if not net:
raise exceptions.NetworkNotFound(net_id=net_id)
new_subnet = db_api.subnet_create(context, **subnet["subnet"])
subnet_dict = self._make_subnet_dict(new_subnet)
return subnet_dict
def update_subnet(self, context, id, subnet):
"""Update values of a subnet.
: param context: quantum 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
quantum/api/v2/attributes.py.
Raises NotImplemented, as there are no attributes one can safely
update on a subnet
"""
raise NotImplementedError()
def get_subnet(self, context, id, fields=None):
"""Retrieve a subnet.
: param context: quantum 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 quantum/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)
return self._make_subnet_dict(subnet)
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: quantum 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 quantum/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 quantum/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 self._make_subnets_list(subnets, fields)
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: quantum 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 quantum/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: quantum 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 create_network(self, context, network):
"""Create a network.
Create a network which represents an L2 network segment which
can have a set of subnets and ports associated with it.
: param context: quantum api request context
: param network: dictionary describing the network, with keys
as listed in the RESOURCE_ATTRIBUTE_MAP object in
quantum/api/v2/attributes.py. All keys will be populated.
"""
LOG.info("create_network for tenant %s" % context.tenant_id)
# Generate a uuid that we're going to hand to the backend and db
net_uuid = uuidutils.generate_uuid()
#NOTE(mdietz): probably want to abstract this out as we're getting
# too tied to the implementation here
self.net_driver.create_network(context,
network["network"]["name"],
network_id=net_uuid)
subnets = []
if network["network"].get("subnets"):
subnets = network["network"].pop("subnets")
network["network"]["id"] = net_uuid
network["network"]["tenant_id"] = context.tenant_id
new_net = db_api.network_create(context, **network["network"])
new_subnets = []
for sub in subnets:
sub["subnet"]["network_id"] = new_net["id"]
sub["subnet"]["tenant_id"] = context.tenant_id
s = db_api.subnet_create(context, **sub["subnet"])
new_subnets.append(s)
new_net["subnets"] = new_subnets
return self._make_network_dict(new_net)
def update_network(self, context, id, network):
"""Update values of a network.
: param context: quantum api request context
: param id: UUID representing the network to update.
: param network: 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
quantum/api/v2/attributes.py.
"""
LOG.info("update_network %s for tenant %s" %
(id, context.tenant_id))
net = db_api.network_find(context, id=id, scope=db_api.ONE)
if not net:
raise exceptions.NetworkNotFound(net_id=id)
net = db_api.network_update(context, net, **network["network"])
return self._make_network_dict(net)
def get_network(self, context, id, fields=None):
"""Retrieve a network.
: param context: quantum api request context
: param id: UUID representing the network to fetch.
: param fields: a list of strings that are valid keys in a
network dictionary as listed in the RESOURCE_ATTRIBUTE_MAP
object in quantum/api/v2/attributes.py. Only these fields
will be returned.
"""
LOG.info("get_network %s for tenant %s fields %s" %
(id, context.tenant_id, fields))
network = db_api.network_find(context, id=id, scope=db_api.ONE)
if not network:
raise exceptions.NetworkNotFound(net_id=id)
return self._make_network_dict(network)
def get_networks(self, context, filters=None, fields=None):
"""Retrieve a list of networks.
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: quantum 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 quantum/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
network dictionary as listed in the RESOURCE_ATTRIBUTE_MAP
object in quantum/api/v2/attributes.py. Only these fields
will be returned.
"""
LOG.info("get_networks for tenant %s with filters %s, fields %s" %
(context.tenant_id, filters, fields))
nets = db_api.network_find(context, **filters)
return [self._make_network_dict(net) for net in nets]
def get_networks_count(self, context, filters=None):
"""Return the number of networks.
The result depends on the identity of the user making the request
(as indicated by the context) as well as any filters.
: param context: quantum 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 quantum/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_networks_count for tenant %s filters %s" %
(context.tenant_id, filters))
return db_api.network_count_all(context)
def delete_network(self, context, id):
"""Delete a network.
: param context: quantum api request context
: param id: UUID representing the network to delete.
"""
LOG.info("delete_network %s for tenant %s" % (id, context.tenant_id))
net = db_api.network_find(context, id=id, scope=db_api.ONE)
if not net:
raise exceptions.NetworkNotFound(net_id=id)
if net.ports:
raise exceptions.NetworkInUse(net_id=id)
self.net_driver.delete_network(context, id)
for subnet in net["subnets"]:
self._delete_subnet(context, subnet)
db_api.network_delete(context, net)
def create_port(self, context, port):
"""Create a port
Create a port which is a connection point of a device (e.g., a VM
NIC) to attach to a L2 Quantum network.
: param context: quantum api request context
: param port: dictionary describing the port, with keys
as listed in the RESOURCE_ATTRIBUTE_MAP object in
quantum/api/v2/attributes.py. All keys will be populated.
"""
LOG.info("create_port for tenant %s" % context.tenant_id)
#TODO(mdietz): do something clever with these
garbage = ["fixed_ips", "mac_address", "device_owner"]
for k in garbage:
if k in port["port"]:
port["port"].pop(k)
addresses = []
port_id = uuidutils.generate_uuid()
net_id = port["port"]["network_id"]
net = db_api.network_find(context, id=net_id)
if not net:
raise exceptions.NetworkNotFound(net_id=net_id)
addresses.append(self.ipam_driver.allocate_ip_address(
context, net_id, port_id, self.ipam_reuse_after))
mac = self.ipam_driver.allocate_mac_address(context,
net_id,
port_id,
self.ipam_reuse_after)
backend_port = self.net_driver.create_port(context, net_id,
port_id=port_id)
port["port"]["id"] = port_id
new_port = db_api.port_create(
context, addresses=addresses, mac_address=mac["address"],
backend_key=backend_port["uuid"], **port["port"])
new_port["mac_address"] = str(netaddr.EUI(new_port["mac_address"],
dialect=netaddr.mac_unix))
LOG.debug("Port created %s" % new_port)
return self._make_port_dict(new_port)
def update_port(self, context, id, port):
"""Update values of a port.
: param context: quantum api request context
: param id: UUID representing the port to update.
: param port: 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
quantum/api/v2/attributes.py.
"""
raise NotImplementedError()
def get_port(self, context, id, fields=None):
"""Retrieve a port.
: param context: quantum api request context
: param id: UUID representing the port to fetch.
: param fields: a list of strings that are valid keys in a
port dictionary as listed in the RESOURCE_ATTRIBUTE_MAP
object in quantum/api/v2/attributes.py. Only these fields
will be returned.
"""
LOG.info("get_port %s for tenant %s fields %s" %
(id, context.tenant_id, fields))
results = db_api.port_find(context, id=id, fields=fields,
scope=db_api.ONE)
if not results:
raise exceptions.PortNotFound(port_id=id, net_id='')
return self._make_port_dict(results)
def get_ports(self, context, filters=None, fields=None):
"""Retrieve a list of ports.
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: quantum api request context
: param filters: a dictionary with keys that are valid keys for
a port as listed in the RESOURCE_ATTRIBUTE_MAP object
in quantum/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
port dictionary as listed in the RESOURCE_ATTRIBUTE_MAP
object in quantum/api/v2/attributes.py. Only these fields
will be returned.
"""
LOG.info("get_ports for tenant %s filters %s fields %s" %
(context.tenant_id, filters, fields))
query = db_api.port_find(context, fields, filters)
return self._make_ports_list(query, fields)
def get_ports_count(self, context, filters=None):
"""Return the number of ports.
The result depends on the identity of the user making the request
(as indicated by the context) as well as any filters.
: param context: quantum 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 quantum/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_ports_count for tenant %s filters %s" %
(context.tenant_id, filters))
return db_api.port_count_all(context, **filters)
def delete_port(self, context, id):
"""Delete a port.
: param context: quantum api request context
: param id: UUID representing the port to delete.
"""
LOG.info("delete_port %s for tenant %s" %
(id, context.tenant_id))
port = db_api.port_find(context, id=id, scope=db_api.ONE)
if not port:
raise exceptions.PortNotFound(net_id=id)
backend_key = port["backend_key"]
mac_address = netaddr.EUI(port["mac_address"]).value
self.ipam_driver.deallocate_mac_address(context,
mac_address)
self.ipam_driver.deallocate_ip_address(
context, port, ipam_reuse_after=self.ipam_reuse_after)
db_api.port_delete(context, port)
self.net_driver.delete_port(context, backend_key)
def get_mac_address_ranges(self, context):
LOG.info("get_mac_address_ranges for tenant %s" % context.tenant_id)
ranges = db_api.mac_address_range_find(context)
return [self._make_mac_range_dict(m) for m in ranges]
def create_mac_address_range(self, context, mac_range):
LOG.info("create_mac_address_range for tenant %s" % context.tenant_id)
cidr = mac_range["mac_address_range"]["cidr"]
cidr, first_address, last_address = self._to_mac_range(cidr)
new_range = db_api.mac_address_range_create(
context, cidr=cidr, first_address=first_address,
last_address=last_address)
return self._make_mac_range_dict(new_range)
def _to_mac_range(self, val):
cidr_parts = val.split("/")
prefix = cidr_parts[0]
prefix = prefix.replace(':', '')
prefix = prefix.replace('-', '')
prefix_length = len(prefix)
if prefix_length < 6 or prefix_length > 10:
raise quark_exceptions.InvalidMacAddressRange(cidr=val)
diff = 12 - len(prefix)
if len(cidr_parts) > 1:
mask = int(cidr_parts[1])
else:
mask = 48 - diff * 4
mask_size = 1 << (48 - mask)
prefix = "%s%s" % (prefix, "0" * diff)
try:
cidr = "%s/%s" % (str(netaddr.EUI(prefix)).replace("-", ":"), mask)
except netaddr.AddrFormatError:
raise quark_exceptions.InvalidMacAddressRange(cidr=val)
prefix_int = int(prefix, base=16)
return cidr, prefix_int, prefix_int + mask_size
def get_route(self, context, id):
LOG.info("get_route %s for tenant %s" % (id, context.tenant_id))
route = db_api.route_find(context, id=id)
if not route:
raise quark_exceptions.RouteNotFound(route_id=id)
return self._make_route_dict(route)
def get_routes(self, context):
LOG.info("get_routes for tenant %s" % context.tenant_id)
routes = db_api.route_find(context)
return [self._make_route_dict(r) for r in routes]
def create_route(self, context, route):
LOG.info("create_route for tenant %s" % context.tenant_id)
route = route["route"]
subnet_id = route["subnet_id"]
subnet = db_api.subnet_find(context, id)
if not subnet:
raise exceptions.SubnetNotFound(subnet_id=subnet_id)
# TODO(anyone): May need to denormalize the cidr values to achieve
# single db lookup
route_cidr = netaddr.IPNetwork(route["cidr"])
subnet_routes = db_api.route_find(context, subnet_id=subnet_id)
for sub_route in subnet_routes:
sub_route_cidr = netaddr.IPNetwork(sub_route["cidr"])
if route_cidr in sub_route_cidr or sub_route_cidr in route_cidr:
raise quark_exceptions.RouteConflict(route_id=sub_route["id"],
cidr=str(route_cidr))
new_route = db_api.route_create(context, **route)
return self._make_route_dict(new_route)
def delete_route(self, context, id):
#TODO(mdietz): This is probably where we check to see that someone is
# admin and only filter on tenant if they aren't. Correct
# for all the above later
LOG.info("delete_route %s for tenant %s" % (id, context.tenant_id))
route = db_api.route_find(context, id)
if not route:
raise quark_exceptions.RouteNotFound(route_id=id)
db_api.route_delete(context, route)
def get_ip_addresses(self, context, **filters):
LOG.info("get_ip_addresses for tenant %s" % context.tenant_id)
addrs = db_api.ip_address_find(context, **filters)
return [self._make_ip_dict(ip) for ip in addrs]
def get_ip_address(self, context, id):
LOG.info("get_ip_address %s for tenant %s" %
(id, context.tenant_id))
addr = db_api.ip_address_find(context, id=id)
if not addr:
raise quark_exceptions.IpAddressNotFound(addr_id=id)
return self._make_ip_dict(addr)
def create_ip_address(self, context, ip_address):
LOG.info("create_ip_address for tenant %s" % context.tenant_id)
port = None
ip_dict = ip_address["ip_address"]
port_id = ip_dict.get('port_id')
network_id = ip_dict.get('network_id')
device_id = ip_dict.get('device_id')
ip_version = ip_dict.get('version')
ip_address = ip_dict.get('ip_address')
if network_id and device_id:
port = db_api.port_find(
context, network_id=network_id, device_id=device_id,
tenant_id=context.tenant_id, scope=db_api.ONE)
elif port_id:
port = db_api.port_find(context, id=port_id, scope=db_api.ONE)
if not port:
raise exceptions.PortNotFound(port_id=port_id,
net_id=network_id)
address = self.ipam_driver.allocate_ip_address(
context,
port['network_id'],
port['id'],
self.ipam_reuse_after,
ip_version,
ip_address)
port["ip_addresses"].append(address)
return self._make_ip_dict(address)
def update_ip_address(self, context, id, ip_address):
LOG.info("update_ip_address %s for tenant %s" %
(id, context.tenant_id))
address = db_api.ip_address_find(
context, id=id, tenant_id=context.tenant_id, scope=db_api.ONE)
if not address:
raise exceptions.NotFound(
message="No IP address found with id=%s" % id)
old_ports = address['ports']
port_ids = ip_address['ip_address'].get('port_ids')
if port_ids is None:
return self._make_ip_dict(address)
for port in old_ports:
port['ip_addresses'].remove(address)
if port_ids:
ports = db_api.port_find(
context, tenant_id=context.tenant_id, id=port_ids,
scope=db_api.ALL)
# NOTE: could be considered inefficient because we're converting
# to a list to check length. Maybe revisit
if len(ports) != len(port_ids):
raise exceptions.NotFound(
message="No ports not found with ids=%s" % port_ids)
for port in ports:
port['ip_addresses'].extend([address])
return self._make_ip_dict(address)