Esempio n. 1
0
    def test_find_reallocatable_ips_does_not_raise(self):
        """Regression testing

        A patch recently introduced a bug wherein addressses
        could not be returned to the ip_address_find call in
        attempt_to_reallocate_ip. Adding this test to prevent
        a future regression.
        """

        network = dict(name="public", tenant_id="fake")
        ipnet = netaddr.IPNetwork("0.0.0.0/24")
        next_ip = ipnet.ipv6().first + 2
        subnet = dict(id=1, cidr="0.0.0.0/24", next_auto_assign_ip=next_ip,
                      ip_policy=None, tenant_id="fake")

        with self._stubs(network, subnet) as net:
            ip_kwargs = {
                "network_id": net["id"], "reuse_after": 14400,
                "deallocated": True, "scope": db_api.ONE,
                "lock_mode": True, "version": 4,
                "order_by": "address"}

            try:
                db_api.ip_address_find(self.context, **ip_kwargs)
            except Exception:
                self.fail("This should not have raised")
Esempio n. 2
0
 def test_ip_address_find_ip_address_list(self):
     ip_address = netaddr.IPAddress("192.168.10.1")
     try:
         db_api.ip_address_find(self.context, ip_address=[ip_address],
                                scope=db_api.ONE)
     except Exception as e:
         self.fail("Expected no exceptions: %s" % e)
Esempio n. 3
0
    def test_find_reallocatable_ips_does_not_raise(self):
        """Regression testing

        A patch recently introduced a bug wherein addressses
        could not be returned to the ip_address_find call in
        attempt_to_reallocate_ip. Adding this test to prevent
        a future regression.
        """

        network = dict(name="public", tenant_id="fake")
        ipnet = netaddr.IPNetwork("0.0.0.0/24")
        next_ip = ipnet.ipv6().first + 2
        subnet = dict(id=1,
                      cidr="0.0.0.0/24",
                      next_auto_assign_ip=next_ip,
                      ip_policy=None,
                      tenant_id="fake")

        with self._stubs(network, subnet) as net:
            ip_kwargs = {
                "network_id": net["id"],
                "reuse_after": 14400,
                "deallocated": True,
                "scope": db_api.ONE,
                "lock_mode": True,
                "version": 4,
                "order_by": "address"
            }

            try:
                db_api.ip_address_find(self.context, **ip_kwargs)
            except Exception:
                self.fail("This should not have raised")
Esempio n. 4
0
    def allocate_ip_address(self, context, net_id, port_id, reuse_after,
                            version=None, ip_address=None):
        elevated = context.elevated()
        if ip_address:
            ip_address = netaddr.IPAddress(ip_address)

        address = db_api.ip_address_find(
            elevated, network_id=net_id, reuse_after=reuse_after,
            deallocated=True, scope=db_api.ONE, ip_address=ip_address)
        if address:
            return db_api.ip_address_update(
                elevated, address, deallocated=False, deallocated_at=None)

        subnet = self._choose_available_subnet(
            elevated, net_id, ip_address=ip_address, version=version)

        # Creating this IP for the first time
        next_ip = None
        if ip_address:
            next_ip = ip_address
        else:
            address = True
            while address:
                next_ip_int = int(subnet["next_auto_assign_ip"])
                next_ip = netaddr.IPAddress(next_ip_int)
                if subnet["ip_version"] == 4:
                    next_ip = next_ip.ipv4()
                subnet["next_auto_assign_ip"] = next_ip_int + 1
                address = db_api.ip_address_find(
                    elevated,
                    network_id=net_id,
                    ip_address=next_ip,
                    tenant_id=elevated.tenant_id,
                    scope=db_api.ONE)

        # TODO(mdietz): this is a hack until we have IP policies
        ip_int = int(next_ip)
        first_ip = netaddr.IPAddress(int(subnet["first_ip"]))
        last_ip = netaddr.IPAddress(int(subnet["last_ip"]))
        if subnet["ip_version"] == 4:
            first_ip = first_ip.ipv4()
            last_ip = last_ip.ipv4()
        first_ip = int(first_ip)
        last_ip = int(last_ip)

        diff = ip_int - first_ip
        if diff < 2:
            next_ip = netaddr.IPAddress(ip_int + (2 - diff))
        if ip_int == last_ip:
            raise exceptions.IpAddressGenerationFailure(net_id=net_id)
        if next_ip not in netaddr.IPNetwork(subnet["cidr"]):
            raise exceptions.IpAddressGenerationFailure(net_id=net_id)

        address = db_api.ip_address_create(
            elevated, address=next_ip, subnet_id=subnet["id"],
            version=subnet["ip_version"], network_id=net_id)

        return address
Esempio n. 5
0
    def test_ip_address_find_device_id(self):
        query_mock = mock.Mock()
        filter_mock = mock.Mock()

        self.context.session.query = query_mock
        query_mock.return_value = filter_mock

        db_api.ip_address_find(self.context, device_id="foo")
        self.assertEqual(filter_mock.filter.call_count, 1)
Esempio n. 6
0
def post_update_port(context, id, port):
    LOG.info("post_update_port %s for tenant %s" % (id, context.tenant_id))
    if not port.get("port"):
        raise exceptions.BadRequest(resource="ports", msg="Port body required")

    port_db = db_api.port_find(context, id=id, scope=db_api.ONE)
    if not port_db:
        raise exceptions.PortNotFound(port_id=id, net_id="")

    port = port["port"]
    if "fixed_ips" in port and port["fixed_ips"]:
        for ip in port["fixed_ips"]:
            address = None
            ipam_driver = ipam.IPAM_REGISTRY.get_strategy(
                port_db["network"]["ipam_strategy"])
            if ip:
                if "ip_id" in ip:
                    ip_id = ip["ip_id"]
                    address = db_api.ip_address_find(
                        context,
                        id=ip_id,
                        tenant_id=context.tenant_id,
                        scope=db_api.ONE)
                elif "ip_address" in ip:
                    ip_address = ip["ip_address"]
                    net_address = netaddr.IPAddress(ip_address)
                    address = db_api.ip_address_find(
                        context,
                        ip_address=net_address,
                        network_id=port_db["network_id"],
                        tenant_id=context.tenant_id,
                        scope=db_api.ONE)
                    if not address:
                        address = ipam_driver.allocate_ip_address(
                            context,
                            port_db["network_id"],
                            id,
                            CONF.QUARK.ipam_reuse_after,
                            ip_address=ip_address)
            else:
                address = ipam_driver.allocate_ip_address(
                    context, port_db["network_id"], id,
                    CONF.QUARK.ipam_reuse_after)

        address["deallocated"] = 0

        already_contained = False
        for port_address in port_db["ip_addresses"]:
            if address["id"] == port_address["id"]:
                already_contained = True
                break

        if not already_contained:
            port_db["ip_addresses"].append(address)
    return v._make_port_dict(port_db)
Esempio n. 7
0
    def post_update_port(self, context, id, port):
        LOG.info("post_update_port %s for tenant %s" % (id, context.tenant_id))
        port_db = db_api.port_find(context, id=id, scope=db_api.ONE)
        if not port_db:
            raise exceptions.PortNotFound(port_id=id, net_id="")

        if "port" not in port or not port["port"]:
            raise exceptions.BadRequest()
        port = port["port"]

        if "fixed_ips" in port and port["fixed_ips"]:
            for ip in port["fixed_ips"]:
                address = None
                if ip:
                    if "ip_id" in ip:
                        ip_id = ip["ip_id"]
                        address = db_api.ip_address_find(
                            context,
                            id=ip_id,
                            tenant_id=context.tenant_id,
                            scope=db_api.ONE)
                    elif "ip_address" in ip:
                        ip_address = ip["ip_address"]
                        net_address = netaddr.IPAddress(ip_address)
                        address = db_api.ip_address_find(
                            context,
                            ip_address=net_address,
                            network_id=port_db["network_id"],
                            tenant_id=context.tenant_id,
                            scope=db_api.ONE)
                        if not address:
                            address = self.ipam_driver.allocate_ip_address(
                                context,
                                port_db["network_id"],
                                id,
                                self.ipam_reuse_after,
                                ip_address=ip_address)
                else:
                    address = self.ipam_driver.allocate_ip_address(
                        context,
                        port_db["network_id"],
                        id,
                        self.ipam_reuse_after)

            address["deallocated"] = 0

            already_contained = False
            for port_address in port_db["ip_addresses"]:
                if address["id"] == port_address["id"]:
                    already_contained = True
                    break

            if not already_contained:
                port_db["ip_addresses"].append(address)
        return self._make_port_dict(port_db)
Esempio n. 8
0
    def test_ip_address_find_port_id_as_admin(self):
        self.context.session.query = mock.MagicMock()
        final_query_mock = self.context.session.query.return_value

        db_api.ip_address_find(self.context.elevated(), port_id="foo")
        # NOTE(thomasem): Creates sqlalchemy.sql.elements.BinaryExpression
        # when using SQLAlchemy models in expressions.
        expected_filter = models.IPAddress.ports.any(models.Port.id == "foo")
        self.assertEqual(len(final_query_mock.filter.call_args[0]), 1)
        self.assertEqual(str(expected_filter), str(
            final_query_mock.filter.call_args[0][0]))
Esempio n. 9
0
def post_update_port(context, id, port):
    LOG.info("post_update_port %s for tenant %s" % (id, context.tenant_id))
    if not port.get("port"):
        raise exceptions.BadRequest(resource="ports", msg="Port body required")

    with context.session.begin():
        port_db = db_api.port_find(context, id=id, scope=db_api.ONE)
        if not port_db:
            raise exceptions.PortNotFound(port_id=id, net_id="")

        port = port["port"]
        if "fixed_ips" in port and port["fixed_ips"]:
            for ip in port["fixed_ips"]:
                address = None
                ipam_driver = ipam.IPAM_REGISTRY.get_strategy(port_db["network"]["ipam_strategy"])
                if ip:
                    if "ip_id" in ip:
                        ip_id = ip["ip_id"]
                        address = db_api.ip_address_find(
                            context, id=ip_id, tenant_id=context.tenant_id, scope=db_api.ONE
                        )
                    elif "ip_address" in ip:
                        ip_address = ip["ip_address"]
                        net_address = netaddr.IPAddress(ip_address)
                        address = db_api.ip_address_find(
                            context,
                            ip_address=net_address,
                            network_id=port_db["network_id"],
                            tenant_id=context.tenant_id,
                            scope=db_api.ONE,
                        )
                        if not address:
                            address = ipam_driver.allocate_ip_address(
                                context, port_db["network_id"], id, CONF.QUARK.ipam_reuse_after, ip_address=ip_address
                            )
                else:
                    address = ipam_driver.allocate_ip_address(
                        context, port_db["network_id"], id, CONF.QUARK.ipam_reuse_after
                    )

            address["deallocated"] = 0

            already_contained = False
            for port_address in port_db["ip_addresses"]:
                if address["id"] == port_address["id"]:
                    already_contained = True
                    break

            if not already_contained:
                port_db["ip_addresses"].append(address)
    return v._make_port_dict(port_db)
Esempio n. 10
0
    def test_ip_address_find_address_type_as_admin(self):
        self.context.session.query = mock.MagicMock()
        filter_mock = self.context.session.query.return_value

        db_api.ip_address_find(self.context.elevated(), address_type="foo")
        # NOTE(thomasem): Creates sqlalchemy.sql.elements.BinaryExpression
        # when using SQLAlchemy models in expressions.
        expected_filter = models.IPAddress.address_type == "foo"
        self.assertEqual(len(filter_mock.filter.call_args[0]), 1)
        # NOTE(thomasem): Unfortunately BinaryExpression.compare isn't
        # showing to be a reliable comparison, so using the string
        # representation which dumps the associated SQL for the filter.
        self.assertEqual(str(expected_filter), str(
            filter_mock.filter.call_args[0][0]))
Esempio n. 11
0
 def test_reserve_ip_non_admin(self):
     with self._stubs() as ip:
         deallocated_ip = ip_addr.update_ip_address(self.context, ip["id"],
                                                    self.ip_address_dealloc)
         ip_address = db_api.ip_address_find(self.context,
                                             id=deallocated_ip["id"],
                                             scope=db_api.ONE)
         self.assertEqual(ip_address["deallocated"], True)
         deallocated_ip = ip_addr.update_ip_address(self.context, ip["id"],
                                                    self.ip_address_reserve)
         ip_address = db_api.ip_address_find(self.context,
                                             id=deallocated_ip["id"],
                                             scope=db_api.ONE)
         self.assertEqual(ip_address["deallocated"], True)
Esempio n. 12
0
def get_ip_addresses(context, **filters):
    LOG.info("get_ip_addresses for tenant %s" % context.tenant_id)
    if not filters:
        filters = {}
    if 'type' in filters:
        filters['address_type'] = filters['type']
    if context.is_admin:
        if 'deallocated' in filters:
            if filters['deallocated'] == 'True':
                filters["_deallocated"] = True
            elif filters['deallocated'] == 'False':
                filters["_deallocated"] = False
            elif filters['deallocated'].lower() == 'both':
                pass
            else:
                filters['_deallocated'] = False
        else:
            filters['_deallocated'] = False
    else:
        filters["_deallocated"] = False

    if 'deallocated' in filters:
        # this needs to be removed or it corrupts the filters passed to the db
        # model. Only _deallocated needs to be present
        del filters['deallocated']

    addrs = db_api.ip_address_find(context, scope=db_api.ALL, **filters)
    return [v._make_ip_dict(ip, context.is_admin) for ip in addrs]
Esempio n. 13
0
    def _allocate_ips_from_subnets(self, context, net_id, subnets,
                                   ip_address=None):
        new_addresses = []
        for subnet in subnets:
            ip_policy_cidrs = models.IPPolicy.get_ip_policy_cidrs(subnet)
            # Creating this IP for the first time
            next_ip = None
            if ip_address:
                next_ip = ip_address
                address = db_api.ip_address_find(
                    context, network_id=net_id, ip_address=next_ip,
                    used_by_tenant_id=context.tenant_id, scope=db_api.ONE)
                if address:
                    raise exceptions.IpAddressGenerationFailure(
                        net_id=net_id)
            else:
                next_ip = self._iterate_until_available_ip(
                    context, subnet, net_id, ip_policy_cidrs)

            context.session.add(subnet)
            address = db_api.ip_address_create(
                context, address=next_ip, subnet_id=subnet["id"],
                version=subnet["ip_version"], network_id=net_id)
            address["deallocated"] = 0
            new_addresses.append(address)
        return new_addresses
Esempio n. 14
0
 def test_ip_address_find_ip_address_object_list_none(self):
     with self._stubs():
         ip_addresses = db_api.ip_address_find(
             self.context,
             ip_address=[netaddr.IPAddress("192.168.10.2")],
             scope=db_api.ALL)
         self.assertEqual(len(ip_addresses), 0)
Esempio n. 15
0
def get_ip_addresses(context, **filters):
    LOG.info("get_ip_addresses for tenant %s" % context.tenant_id)
    if not filters:
        filters = {}
    if 'type' in filters:
        filters['address_type'] = filters['type']
    if context.is_admin:
        if 'deallocated' in filters:
            if filters['deallocated'] == 'True':
                filters["_deallocated"] = True
            elif filters['deallocated'] == 'False':
                filters["_deallocated"] = False
            elif filters['deallocated'].lower() == 'both':
                pass
            else:
                filters['_deallocated'] = False
        else:
            filters['_deallocated'] = False
    else:
        filters["_deallocated"] = False

    if 'deallocated' in filters:
        # this needs to be removed or it corrupts the filters passed to the db
        # model. Only _deallocated needs to be present
        del filters['deallocated']

    addrs = db_api.ip_address_find(context, scope=db_api.ALL, **filters)
    return [v._make_ip_dict(ip, context.is_admin) for ip in addrs]
Esempio n. 16
0
    def test_get_deallocated_ips_non_admin_both(self):
        with self._stubs() as (ip1, ip2):
            reserved_ip = ip_addr.update_ip_address(self.context, ip2["id"],
                                                    self.ip_address_dealloc)
            self.assertNotIn('_deallocated', reserved_ip)

            ip_addresses = db_api.ip_address_find(self.context,
                                                  scope=db_api.ALL)
            self.assertEqual(len(ip_addresses), 2)
            for ip in ip_addresses:
                if ip["id"] == ip1["id"]:
                    self.assertEqual(ip["_deallocated"], False)
                elif ip["id"] == ip2["id"]:
                    self.assertEqual(ip["_deallocated"], True)

            deallocated_ips = ip_addr.get_ip_addresses(self.context)
            self.assertEqual(len(deallocated_ips), 1)
            self.assertEqual(ip1['id'], deallocated_ips[0]['id'])
            self.assertNotIn('_deallocated', deallocated_ips[0])

            filters = {'deallocated': 'True'}
            deallocated_ips1 = ip_addr.get_ip_addresses(
                self.context, **filters)
            self.assertEqual(len(deallocated_ips1), 1)

            filters = {'deallocated': 'False'}
            deallocated_ips1 = ip_addr.get_ip_addresses(
                self.context, **filters)
            self.assertEqual(len(deallocated_ips1), 1)

            filters = {'deallocated': 'both'}
            deallocated_ips = ip_addr.get_ip_addresses(self.context, **filters)
            self.assertEqual(len(deallocated_ips), 1)
            self.assertEqual(ip1['id'], deallocated_ips[0]['id'])
            self.assertNotIn('_deallocated', deallocated_ips[0])
Esempio n. 17
0
def delete_ip_address(context, id):
    """Delete an ip address.

    : param context: neutron api request context
    : param id: UUID representing the ip address to delete.
    """
    LOG.info("delete_ip_address %s for tenant %s" % (id, context.tenant_id))
    with context.session.begin():
        ip_address = db_api.ip_address_find(
            context, id=id, scope=db_api.ONE)
        if not ip_address or ip_address.deallocated:
            raise q_exc.IpAddressNotFound(addr_id=id)

        iptype = ip_address.address_type
        if iptype == ip_types.FIXED and not CONF.QUARK.ipaddr_allow_fixed_ip:
            raise n_exc.BadRequest(
                resource="ip_addresses",
                msg="Fixed ips cannot be updated using this interface.")

        if ip_address.has_any_shared_owner():
            raise q_exc.PortRequiresDisassociation()

        db_api.update_port_associations_for_ip(context, [], ip_address)

        ipam_driver.deallocate_ip_address(context, ip_address)
Esempio n. 18
0
def update_port_for_ip_address(context, ip_id, id, port):
    """Update values of a port.

    : param context: neutron api request context
    : param ip_id: UUID representing the ip associated with port to update
    : 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
        neutron/api/v2/attributes.py.
    """
    LOG.info("update_port %s for tenant %s" % (id, context.tenant_id))
    sanitize_list = ['service']
    with context.session.begin():
        addr = db_api.ip_address_find(context, id=ip_id, scope=db_api.ONE)
        if not addr:
            raise q_exc.IpAddressNotFound(addr_id=ip_id)
        port_db = db_api.port_find(context, id=id, scope=db_api.ONE)
        if not port_db:
            raise q_exc.PortNotFound(port_id=id)
        port_dict = {k: port['port'][k] for k in sanitize_list}

        require_da = False
        service = port_dict.get('service')

        if require_da and _shared_ip_and_active(addr, except_port=id):
            raise q_exc.PortRequiresDisassociation()
        addr.set_service_for_port(port_db, service)
        context.session.add(addr)
    return v._make_port_for_ip_dict(addr, port_db)
Esempio n. 19
0
def get_port_for_ip_address(context, ip_id, id, fields=None):
    """Retrieve a port.

    : param context: neutron 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 neutron/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))
    addr = db_api.ip_address_find(context, id=ip_id, scope=db_api.ONE)
    if not addr:
        raise q_exc.IpAddressNotFound(addr_id=ip_id)

    filters = {'ip_address_id': [ip_id]}
    results = db_api.port_find(context,
                               id=id,
                               fields=fields,
                               scope=db_api.ONE,
                               **filters)

    if not results:
        raise n_exc.PortNotFound(port_id=id)

    return v._make_port_for_ip_dict(addr, results)
Esempio n. 20
0
    def attempt_to_reallocate_ip(self, context, net_id, port_id, reuse_after,
                                 version=None, ip_address=None):
        version = version or [4, 6]
        elevated = context.elevated()

        # We never want to take the chance of an infinite loop here. Instead,
        # we'll clean up multiple bad IPs if we find them (assuming something
        # is really wrong)
        for times in xrange(3):
            with context.session.begin(subtransactions=True):
                address = db_api.ip_address_find(
                    elevated, network_id=net_id, reuse_after=reuse_after,
                    deallocated=True, scope=db_api.ONE, ip_address=ip_address,
                    lock_mode=True, version=version, order_by="address")

                if address:
                    #NOTE(mdietz): We should always be in the CIDR but we've
                    #              also said that before :-/
                    if address.get("subnet"):
                        cidr = netaddr.IPNetwork(address["subnet"]["cidr"])
                        addr = netaddr.IPAddress(int(address["address"]),
                                                 version=int(cidr.version))
                        if addr in cidr:
                            updated_address = db_api.ip_address_update(
                                elevated, address, deallocated=False,
                                deallocated_at=None,
                                allocated_at=timeutils.utcnow())
                            return [updated_address]
                        else:
                            # Make sure we never find it again
                            context.session.delete(address)
                            continue
                break
        return []
Esempio n. 21
0
def get_ip_address(context, id):
    LOG.info("get_ip_address %s for tenant %s" %
             (id, context.tenant_id))
    addr = db_api.ip_address_find(context, id=id, scope=db_api.ONE)
    if not addr:
        raise quark_exceptions.IpAddressNotFound(addr_id=id)
    return v._make_ip_dict(addr)
Esempio n. 22
0
def update_port_for_ip_address(context, ip_id, id, port):
    """Update values of a port.

    : param context: neutron api request context
    : param ip_id: UUID representing the ip associated with port to update
    : 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
        neutron/api/v2/attributes.py.
    """
    LOG.info("update_port %s for tenant %s" % (id, context.tenant_id))
    sanitize_list = ['service']
    with context.session.begin():
        addr = db_api.ip_address_find(context, id=ip_id, scope=db_api.ONE)
        if not addr:
            raise q_exc.IpAddressNotFound(addr_id=ip_id)
        port_db = db_api.port_find(context, id=id, scope=db_api.ONE)
        if not port_db:
            raise q_exc.PortNotFound(port_id=id)
        port_dict = {k: port['port'][k] for k in sanitize_list}

        require_da = False
        service = port_dict.get('service')

        if require_da and _shared_ip_and_active(addr, except_port=id):
            raise q_exc.PortRequiresDisassociation()
        addr.set_service_for_port(port_db, service)
        context.session.add(addr)
    return v._make_port_for_ip_dict(addr, port_db)
Esempio n. 23
0
 def test_reserve_ip_non_admin(self):
     with self._stubs() as ip:
         deallocated_ip = ip_addr.update_ip_address(self.context, ip["id"],
                                                    self.ip_address_dealloc)
         ip_address = db_api.ip_address_find(
             self.context,
             id=deallocated_ip["id"],
             scope=db_api.ONE)
         self.assertEqual(ip_address["deallocated"], True)
         deallocated_ip = ip_addr.update_ip_address(self.context, ip["id"],
                                                    self.ip_address_reserve)
         ip_address = db_api.ip_address_find(
             self.context,
             id=deallocated_ip["id"],
             scope=db_api.ONE)
         self.assertEqual(ip_address["deallocated"], True)
Esempio n. 24
0
def update_ip_address(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 v._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])
    else:
        address["deallocated"] = 1

    return v._make_ip_dict(address)
Esempio n. 25
0
    def test_address_not_in_cidr(self):
        self.network_db = self.insert_network()
        self.subnet_v4_db = self.insert_subnet(
            self.network_db, "192.168.0.0/24")
        self.ip_address_v4 = netaddr.IPAddress("192.168.1.1")
        ip_address_db = self.insert_ip_address(self.ip_address_v4,
                                               self.network_db,
                                               self.subnet_v4_db)
        self.transaction = self.insert_transaction()
        ip_kwargs = {
            "network_id": self.network_db["id"],
            "reuse_after": self.REUSE_AFTER,
            "deallocated": True,
            "version": 4,
        }
        reallocated = db_api.ip_address_reallocate(
            self.context,
            {"transaction_id": self.transaction.id},
            **ip_kwargs)
        self.assertTrue(reallocated)

        updated_address = db_api.ip_address_reallocate_find(
            self.context, self.transaction.id)
        self.assertIsNone(updated_address)

        self.context.session.flush()
        self.assertIsNone(db_api.ip_address_find(self.context,
                                                 id=ip_address_db.id,
                                                 scope=db_api.ONE))
Esempio n. 26
0
 def test_ip_address_find_ip_address_object_list_none(self):
     with self._stubs():
         ip_addresses = db_api.ip_address_find(
             self.context,
             ip_address=[netaddr.IPAddress("192.168.10.2")],
             scope=db_api.ALL)
         self.assertEqual(len(ip_addresses), 0)
Esempio n. 27
0
 def test_ip_address_find_ip_address_list_filter(self):
     with self._stubs():
         ip_addresses = db_api.ip_address_find(self.context,
                                               ip_address=[self.addr],
                                               scope=db_api.ALL)
         self.assertEqual(len(ip_addresses), 1)
         self.assertEqual(ip_addresses[0]["address"],
                          self.addr.ipv6().value)
Esempio n. 28
0
def get_ip_address(context, id):
    LOG.info("get_ip_address %s for tenant %s" % (id, context.tenant_id))
    filters = {}
    filters["_deallocated"] = False
    addr = db_api.ip_address_find(context, id=id, scope=db_api.ONE, **filters)
    if not addr:
        raise q_exc.IpAddressNotFound(addr_id=id)
    return v._make_ip_dict(addr)
Esempio n. 29
0
    def allocate_ip_address(self, context, net_id, port_id, reuse_after,
                            version=None, ip_address=None):
        elevated = context.elevated()
        if ip_address:
            ip_address = netaddr.IPAddress(ip_address)

        address = db_api.ip_address_find(
            elevated, network_id=net_id, reuse_after=reuse_after,
            deallocated=True, scope=db_api.ONE, ip_address=ip_address)
        if address:
            return db_api.ip_address_update(
                elevated, address, deallocated=False, deallocated_at=None)

        subnet = self._choose_available_subnet(
            elevated, net_id, ip_address=ip_address, version=version)
        ip_policy_rules = self.get_ip_policy_rule_set(subnet)

        # Creating this IP for the first time
        next_ip = None
        if ip_address:
            next_ip = ip_address
            address = db_api.ip_address_find(
                elevated, network_id=net_id, ip_address=next_ip,
                tenant_id=elevated.tenant_id, scope=db_api.ONE)
            if address:
                raise exceptions.IpAddressGenerationFailure(net_id=net_id)
        else:
            address = True
            while address:
                next_ip_int = int(subnet["next_auto_assign_ip"])
                next_ip = netaddr.IPAddress(next_ip_int)
                if subnet["ip_version"] == 4:
                    next_ip = next_ip.ipv4()
                subnet["next_auto_assign_ip"] = next_ip_int + 1
                if ip_policy_rules and next_ip in ip_policy_rules:
                    continue
                address = db_api.ip_address_find(
                    elevated, network_id=net_id, ip_address=next_ip,
                    tenant_id=elevated.tenant_id, scope=db_api.ONE)

        address = db_api.ip_address_create(
            elevated, address=next_ip, subnet_id=subnet["id"],
            version=subnet["ip_version"], network_id=net_id)
        address["deallocated"] = 0

        return address
Esempio n. 30
0
def get_ip_address(context, id):
    LOG.info("get_ip_address %s for tenant %s" %
             (id, context.tenant_id))
    filters = {}
    filters["_deallocated"] = False
    addr = db_api.ip_address_find(context, id=id, scope=db_api.ONE, **filters)
    if not addr:
        raise q_exc.IpAddressNotFound(addr_id=id)
    return v._make_ip_dict(addr)
Esempio n. 31
0
def get_ip_addresses(context, **filters):
    LOG.info("get_ip_addresses for tenant %s" % context.tenant_id)
    if not filters:
        filters = {}
    if 'type' in filters:
        filters['address_type'] = filters['type']
    filters["_deallocated"] = False
    addrs = db_api.ip_address_find(context, scope=db_api.ALL, **filters)
    return [v._make_ip_dict(ip) for ip in addrs]
Esempio n. 32
0
 def test_ip_address_find_ip_address_list_filter(self):
     with self._stubs():
         ip_addresses = db_api.ip_address_find(
             self.context,
             ip_address=[self.addr],
             scope=db_api.ALL)
         self.assertEqual(len(ip_addresses), 1)
         self.assertEqual(ip_addresses[0]["address"],
                          self.addr.ipv6().value)
Esempio n. 33
0
    def attempt_to_reallocate_ip(self, context, net_id, port_id, reuse_after,
                                 version=None, ip_address=None,
                                 segment_id=None, subnets=None):
        version = version or [4, 6]
        elevated = context.elevated()

        # We never want to take the chance of an infinite loop here. Instead,
        # we'll clean up multiple bad IPs if we find them (assuming something
        # is really wrong)

        #TODO(mdietz & mpath): Perhaps remove, select for update might quash
        for times in xrange(3):
            with context.session.begin(subtransactions=True):

                sub_ids = []
                if subnets:
                    sub_ids = subnets
                else:
                    if segment_id:
                        subnets = db_api.subnet_find(elevated,
                                                     network_id=net_id,
                                                     segment_id=segment_id)
                        sub_ids = [s["id"] for s in subnets]
                        if not sub_ids:
                            raise exceptions.IpAddressGenerationFailure(
                                net_id=net_id)

                ip_kwargs = {
                    "network_id": net_id, "reuse_after": reuse_after,
                    "deallocated": True, "scope": db_api.ONE,
                    "ip_address": ip_address, "lock_mode": True,
                    "version": version, "order_by": "address"}

                if sub_ids:
                    ip_kwargs["subnet_id"] = sub_ids

                address = db_api.ip_address_find(elevated, **ip_kwargs)

                if address:
                    #NOTE(mdietz): We should always be in the CIDR but we've
                    #              also said that before :-/
                    if address.get("subnet"):
                        cidr = netaddr.IPNetwork(address["subnet"]["cidr"])
                        addr = netaddr.IPAddress(int(address["address"]),
                                                 version=int(cidr.version))
                        if addr in cidr:
                            updated_address = db_api.ip_address_update(
                                elevated, address, deallocated=False,
                                deallocated_at=None,
                                allocated_at=timeutils.utcnow())
                            return [updated_address]
                        else:
                            # Make sure we never find it again
                            context.session.delete(address)
                            continue
                break
        return []
Esempio n. 34
0
def get_ip_addresses(context, **filters):
    LOG.info("get_ip_addresses for tenant %s" % context.tenant_id)
    if not filters:
        filters = {}
    if 'type' in filters:
        filters['address_type'] = filters['type']
    filters["_deallocated"] = False
    addrs = db_api.ip_address_find(context, scope=db_api.ALL, **filters)
    return [v._make_ip_dict(ip) for ip in addrs]
Esempio n. 35
0
 def test_get_single_deallocated_ip_non_admin_raises(self):
     with self._stubs() as ip:
         reserved_ip = ip_addr.update_ip_address(self.context, ip["id"],
                                                 self.ip_address_dealloc)
         ip_address = db_api.ip_address_find(self.context,
                                             id=reserved_ip["id"],
                                             scope=db_api.ONE)
         self.assertEqual(ip_address["deallocated"], True)
         with self.assertRaises(q_exc.IpAddressNotFound):
             ip_addr.get_ip_address(self.context, ip_address['id'])
Esempio n. 36
0
 def test_get_deallocated_ips_non_admin_empty(self):
     with self._stubs() as ip:
         reserved_ip = ip_addr.update_ip_address(self.context, ip["id"],
                                                 self.ip_address_dealloc)
         ip_address = db_api.ip_address_find(self.context,
                                             id=reserved_ip["id"],
                                             scope=db_api.ONE)
         self.assertEqual(ip_address["deallocated"], True)
         deallocated_ips = ip_addr.get_ip_addresses(self.context)
         self.assertEqual(len(deallocated_ips), 0)
Esempio n. 37
0
    def test_ip_address_find_port_id(self):
        self.context.session.query = mock.MagicMock()
        final_query_mock = self.context.session.query.return_value

        db_api.ip_address_find(self.context, port_id="foo")
        # NOTE(thomasem): Creates sqlalchemy.sql.elements.BinaryExpression
        # when using SQLAlchemy models in expressions.
        tenant_filter = (models.IPAddress.used_by_tenant_id.in_(
                         [self.context.tenant_id]))
        port_filter = models.IPAddress.ports.any(models.Port.id == "foo")
        self.assertEqual(len(final_query_mock.filter.call_args[0]), 2)
        port_found = False
        tenant_found = False
        for model_filter in list(final_query_mock.filter.call_args[0]):
            if str(port_filter) == str(model_filter):
                port_found = True
            elif str(tenant_filter) == str(model_filter):
                tenant_found = True
        self.assertTrue(tenant_found)
        self.assertTrue(port_found)
Esempio n. 38
0
    def _allocate_from_v6_subnet(self, context, net_id, subnet,
                                 port_id, ip_address=None, **kwargs):
        """This attempts to allocate v6 addresses as per RFC2462 and RFC3041.

        To accomodate this, we effectively treat all v6 assignment as a
        first time allocation utilizing the MAC address of the VIF. Because
        we recycle MACs, we will eventually attempt to recreate a previously
        generated v6 address. Instead of failing, we've opted to handle
        reallocating that address in this method.

        This should provide a performance boost over attempting to check
        each and every subnet in the existing reallocate logic, as we'd
        have to iterate over each and every subnet returned
        """

        if not (ip_address is None and "mac_address" in kwargs and
                kwargs["mac_address"]):
            return self._allocate_from_subnet(context, net_id, subnet,
                                              ip_address, **kwargs)
        else:
            ip_policy_cidrs = models.IPPolicy.get_ip_policy_cidrs(subnet)
            for tries, ip_address in enumerate(
                generate_v6(kwargs["mac_address"]["address"], port_id,
                            subnet["cidr"])):
                if tries > CONF.QUARK.v6_allocation_attempts - 1:
                    raise exceptions.IpAddressGenerationFailure(
                        net_id=net_id)

                ip_address = netaddr.IPAddress(ip_address)

                # NOTE(mdietz): treating the IPSet as a boolean caused netaddr
                #              to attempt to enumerate the entire set!
                if (ip_policy_cidrs is not None and
                        ip_address in ip_policy_cidrs):
                    continue

                with context.session.begin():
                    address = db_api.ip_address_find(
                        context, network_id=net_id, ip_address=ip_address,
                        used_by_tenant_id=context.tenant_id, scope=db_api.ONE,
                        lock_mode=True)

                    if address:
                        return db_api.ip_address_update(
                            context, address, deallocated=False,
                            deallocated_at=None,
                            used_by_tenant_id=context.tenant_id,
                            allocated_at=timeutils.utcnow())

                with context.session.begin():
                    return db_api.ip_address_create(
                        context, address=ip_address,
                        subnet_id=subnet["id"],
                        version=subnet["ip_version"], network_id=net_id)
Esempio n. 39
0
    def allocate_ip_address(self, context, net_id, port_id, reuse_after,
                            version=None, ip_address=None):
        elevated = context.elevated()
        if ip_address:
            ip_address = netaddr.IPAddress(ip_address)

        new_addresses = []
        realloc_ips = self.attempt_to_reallocate_ip(context, net_id,
                                                    port_id, reuse_after,
                                                    version=None,
                                                    ip_address=None)
        if self.is_strategy_satisfied(realloc_ips):
            return realloc_ips
        new_addresses.extend(realloc_ips)
        with context.session.begin(subtransactions=True):
            subnets = self._choose_available_subnet(
                elevated, net_id, version, ip_address=ip_address,
                reallocated_ips=realloc_ips)
            for subnet in subnets:
                ip_policy_rules = models.IPPolicy.get_ip_policy_rule_set(
                    subnet)
                # Creating this IP for the first time
                next_ip = None
                if ip_address:
                    next_ip = ip_address
                    address = db_api.ip_address_find(
                        elevated, network_id=net_id, ip_address=next_ip,
                        used_by_tenant_id=elevated.tenant_id, scope=db_api.ONE)
                    if address:
                        raise exceptions.IpAddressGenerationFailure(
                            net_id=net_id)
                else:
                    next_ip = self._iterate_until_available_ip(
                        elevated, subnet, net_id, ip_policy_rules)

                context.session.add(subnet)
                address = db_api.ip_address_create(
                    elevated, address=next_ip, subnet_id=subnet["id"],
                    version=subnet["ip_version"], network_id=net_id)
                address["deallocated"] = 0
                new_addresses.append(address)

        for addr in new_addresses:
            payload = dict(used_by_tenant_id=addr["used_by_tenant_id"],
                           ip_block_id=addr["subnet_id"],
                           ip_address=addr["address_readable"],
                           device_ids=[p["device_id"] for p in addr["ports"]],
                           created_at=addr["created_at"])
            notifier_api.notify(context,
                                notifier_api.publisher_id("network"),
                                "ip_block.address.create",
                                notifier_api.CONF.default_notification_level,
                                payload)
        return new_addresses
Esempio n. 40
0
 def test_get_deallocated_ips_non_admin_empty(self):
     with self._stubs() as ip:
         reserved_ip = ip_addr.update_ip_address(self.context, ip["id"],
                                                 self.ip_address_dealloc)
         ip_address = db_api.ip_address_find(
             self.context,
             id=reserved_ip["id"],
             scope=db_api.ONE)
         self.assertEqual(ip_address["deallocated"], True)
         deallocated_ips = ip_addr.get_ip_addresses(self.context)
         self.assertEqual(len(deallocated_ips), 0)
Esempio n. 41
0
 def test_get_single_deallocated_ip_non_admin_raises(self):
     with self._stubs() as ip:
         reserved_ip = ip_addr.update_ip_address(self.context, ip["id"],
                                                 self.ip_address_dealloc)
         ip_address = db_api.ip_address_find(
             self.context,
             id=reserved_ip["id"],
             scope=db_api.ONE)
         self.assertEqual(ip_address["deallocated"], True)
         with self.assertRaises(q_exc.IpAddressNotFound):
             ip_addr.get_ip_address(self.context,
                                    ip_address['id'])
Esempio n. 42
0
 def test_get_single_deallocated_ip_admin(self):
     self.context.is_admin = True
     with self._stubs() as ip:
         reserved_ip = ip_addr.update_ip_address(self.context, ip["id"],
                                                 self.ip_address_dealloc)
         ip_address = db_api.ip_address_find(self.context,
                                             id=reserved_ip["id"],
                                             scope=db_api.ONE)
         self.assertEqual(ip_address["deallocated"], True)
         deallocated_ip = ip_addr.get_ip_address(self.context,
                                                 ip_address['id'])
         self.assertEqual(reserved_ip['id'], deallocated_ip['id'])
         self.assertEqual(deallocated_ip['_deallocated'], True)
Esempio n. 43
0
 def test_get_single_deallocated_ip_admin(self):
     self.context.is_admin = True
     with self._stubs() as ip:
         reserved_ip = ip_addr.update_ip_address(self.context, ip["id"],
                                                 self.ip_address_dealloc)
         ip_address = db_api.ip_address_find(
             self.context,
             id=reserved_ip["id"],
             scope=db_api.ONE)
         self.assertEqual(ip_address["deallocated"], True)
         deallocated_ip = ip_addr.get_ip_address(self.context,
                                                 ip_address['id'])
         self.assertEqual(reserved_ip['id'], deallocated_ip['id'])
         self.assertEqual(deallocated_ip['_deallocated'], True)
Esempio n. 44
0
 def test_get_deallocated_ips_admin(self):
     self.context.is_admin = True
     with self._stubs() as ip:
         reserved_ip = ip_addr.update_ip_address(self.context, ip["id"],
                                                 self.ip_address_dealloc)
         ip_address = db_api.ip_address_find(self.context,
                                             id=reserved_ip["id"],
                                             scope=db_api.ONE)
         self.assertEqual(ip_address["deallocated"], True)
         filters = {'deallocated': 'True'}
         deallocated_ips = ip_addr.get_ip_addresses(self.context, **filters)
         self.assertEqual(len(deallocated_ips), 1)
         self.assertEqual(reserved_ip['id'], deallocated_ips[0]['id'])
         self.assertEqual(deallocated_ips[0]['_deallocated'], True)
Esempio n. 45
0
    def test_ip_address_find_address_type(self):
        self.context.session.query = mock.MagicMock()
        filter_mock = self.context.session.query.return_value

        db_api.ip_address_find(self.context, address_type="foo")
        # NOTE(thomasem): Creates sqlalchemy.sql.elements.BinaryExpression
        # when using SQLAlchemy models in expressions.
        tenant_filter = (models.IPAddress.used_by_tenant_id.in_(
                         [self.context.tenant_id]))
        type_filter = models.IPAddress.address_type == "foo"
        self.assertEqual(len(filter_mock.filter.call_args[0]), 2)
        # NOTE(thomasem): Unfortunately BinaryExpression.compare isn't
        # showing to be a reliable comparison, so using the string
        # representation which dumps the associated SQL for the filter.
        type_found = False
        tenant_found = False
        for model_filter in list(filter_mock.filter.call_args[0]):
            if str(type_filter) == str(model_filter):
                type_found = True
            elif str(tenant_filter) == str(model_filter):
                tenant_found = True
        self.assertTrue(tenant_found)
        self.assertTrue(type_found)
Esempio n. 46
0
    def test_create_locks_address_doesnt_exist(self):
        network = db_api.network_create(self.context)
        subnet = db_api.subnet_create(
            self.context,
            network=network,
            cidr=self.cidr,
            ip_version=4)
        self.context.session.flush()

        addresses = netaddr.IPSet(netaddr.IPNetwork(self.sub_cidr))
        null_routes.create_locks(self.context, [network.id], addresses)
        address = db_api.ip_address_find(
            self.context, subnet_id=subnet.id, scope=db_api.ONE)
        self.assertIsNotNone(address)
        self.assertIsNotNone(address.lock_id)
Esempio n. 47
0
def update_ip_address(context, id, ip_address):
    LOG.info("update_ip_address %s for tenant %s" % (id, context.tenant_id))

    with context.session.begin():
        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)

        reset = ip_address['ip_address'].get('reset_allocation_time', False)
        if reset and address['deallocated'] == 1:
            if context.is_admin:
                LOG.info("IP's deallocated time being manually reset")
                address['deallocated_at'] = _get_deallocated_override()
            else:
                msg = "Modification of reset_allocation_time requires admin"
                raise webob.exc.HTTPForbidden(detail=msg)

        old_ports = address['ports']
        port_ids = ip_address['ip_address'].get('port_ids')
        if port_ids is None:
            return v._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])
        else:
            address["deallocated"] = 1

    return v._make_ip_dict(address)
Esempio n. 48
0
def get_ports_for_ip_address(context,
                             ip_id,
                             limit=None,
                             sorts=None,
                             marker=None,
                             page_reverse=False,
                             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: neutron 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 neutron/api/v2/attributes.py.  Values in this dictionary
        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 neutron/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))
    addr = db_api.ip_address_find(context, id=ip_id, scope=db_api.ONE)
    if not addr:
        raise q_exc.IpAddressNotFound(addr_id=ip_id)

    if filters is None:
        filters = {}

    filters['ip_address_id'] = [ip_id]

    ports = db_api.port_find(context,
                             limit,
                             sorts,
                             marker,
                             fields=fields,
                             join_security_groups=True,
                             **filters)
    return v._make_ip_ports_list(addr, ports, fields)
Esempio n. 49
0
def _find_or_create_address(context, network_ids, address):
    address_model = db_api.ip_address_find(context,
                                           network_id=network_ids,
                                           address=_to_int(address),
                                           scope=db_api.ONE)
    if not address_model:
        query = context.session.query(models.Subnet)
        query = query.filter(models.Subnet.network_id.in_(network_ids))
        query = query.filter(models.Subnet.ip_version == address.version)
        query = query.filter(_to_int(address) >= models.Subnet.first_ip)
        query = query.filter(_to_int(address) <= models.Subnet.last_ip)
        subnet = query.one()
        address_model = db_api.ip_address_create(
            context,
            address=address,
            subnet_id=subnet["id"],
            version=subnet["ip_version"],
            network_id=subnet["network_id"],
            address_type=ip_types.FIXED)
        address_model["deallocated"] = 1
        context.session.add(address_model)
    return address_model
Esempio n. 50
0
    def test_has_shared_owner_detection(self):
        with self._stubs(self.network, self.subnet,
                         self.ports_info4) as (net, sub, ports):

            port_ids = [ports[0]['id'], ports[1]['id']]
            p_id = ports[0]['id']

            shared_ip = {
                'ip_address':
                dict(port_ids=port_ids, network_id=net['id'], version=4)
            }
            ip = ip_api.create_ip_address(self.context, shared_ip)
            self.assertEqual(ip_types.SHARED, ip['type'])
            ip_db = db_api.ip_address_find(self.context,
                                           id=ip['id'],
                                           scope=db_api.ONE)
            self.assertFalse(ip_db.has_any_shared_owner())

            port_ip_update = ip_api.update_port_for_ip_address
            port_ip_update(self.context, ip['id'], p_id,
                           self._make_port_body('derp'))

            self.assertTrue(ip_db.has_any_shared_owner())
Esempio n. 51
0
def delete_ip_address(context, id):
    """Delete an ip address.

    : param context: neutron api request context
    : param id: UUID representing the ip address to delete.
    """
    LOG.info("delete_ip_address %s for tenant %s" % (id, context.tenant_id))
    with context.session.begin():
        ip_address = db_api.ip_address_find(context, id=id, scope=db_api.ONE)
        if not ip_address or ip_address.deallocated:
            raise q_exc.IpAddressNotFound(addr_id=id)

        iptype = ip_address.address_type
        if iptype == ip_types.FIXED and not CONF.QUARK.ipaddr_allow_fixed_ip:
            raise n_exc.BadRequest(
                resource="ip_addresses",
                msg="Fixed ips cannot be updated using this interface.")

        if ip_address.has_any_shared_owner():
            raise q_exc.PortRequiresDisassociation()

        db_api.update_port_associations_for_ip(context, [], ip_address)

        ipam_driver.deallocate_ip_address(context, ip_address)
Esempio n. 52
0
def update_ip_address(context, id, ip_address):
    """Due to NCP-1592 ensure that address_type cannot change after update."""
    LOG.info("update_ip_address %s for tenant %s" % (id, context.tenant_id))
    ports = []
    if 'ip_address' not in ip_address:
        raise n_exc.BadRequest(resource="ip_addresses",
                               msg="Invalid request body.")
    with context.session.begin():
        address = db_api.ip_address_find(context, id=id, scope=db_api.ONE)
        if not address:
            raise q_exc.IpAddressNotFound(addr_id=id)
        iptype = address.address_type
        if iptype == ip_types.FIXED and not CONF.QUARK.ipaddr_allow_fixed_ip:
            raise n_exc.BadRequest(
                resource="ip_addresses",
                msg="Fixed ips cannot be updated using this interface.")

        reset = ip_address['ip_address'].get('reset_allocation_time', False)
        if reset and address['deallocated'] == 1:
            if context.is_admin:
                LOG.info("IP's deallocated time being manually reset")
                address['deallocated_at'] = _get_deallocated_override()
            else:
                msg = "Modification of reset_allocation_time requires admin"
                raise webob.exc.HTTPForbidden(detail=msg)

        port_ids = ip_address['ip_address'].get('port_ids', None)

        if port_ids is not None and not port_ids:
            raise n_exc.BadRequest(
                resource="ip_addresses",
                msg="Cannot be updated with empty port_id list")

        if iptype == ip_types.SHARED:
            has_owner = address.has_any_shared_owner()

        if port_ids:
            if iptype == ip_types.FIXED and len(port_ids) > 1:
                raise n_exc.BadRequest(
                    resource="ip_addresses",
                    msg="Fixed ips cannot be updated with more than one port.")

            _raise_if_shared_and_enabled(ip_address, address)
            ports = db_api.port_find(context,
                                     tenant_id=context.tenant_id,
                                     id=port_ids,
                                     scope=db_api.ALL)
            # NOTE(name): could be considered inefficient because we're
            # converting to a list to check length. Maybe revisit
            if len(ports) != len(port_ids):
                raise n_exc.PortNotFound(port_id=port_ids)

            validate_and_fetch_segment(ports, address["network_id"])
            validate_port_ip_quotas(context, address.network_id, ports)

            if iptype == ip_types.SHARED and has_owner:
                for assoc in address.associations:
                    pid = assoc.port_id
                    if pid not in port_ids and 'none' != assoc.service:
                        raise q_exc.PortRequiresDisassociation()

            LOG.info("Updating IP address, %s, to only be used by the"
                     "following ports:  %s" %
                     (address.address_readable, [p.id for p in ports]))
            new_address = db_api.update_port_associations_for_ip(
                context, ports, address)
        elif iptype == ip_types.SHARED and has_owner:
            raise q_exc.PortRequiresDisassociation()
        else:
            ipam_driver.deallocate_ip_address(context, address)
            return v._make_ip_dict(address)
    return v._make_ip_dict(new_address)