# 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_lib.api.definitions import availability_zone as az_def from neutron_lib.api.validators import availability_zone as az_validator from neutron_lib import constants as n_const from neutron_lib.db import api as db_api from neutron_lib.objects import common_types from neutron_lib.utils import net as net_utils from oslo_utils import versionutils from oslo_versionedobjects import fields as obj_fields from sqlalchemy import func from sqlalchemy import sql from neutron.db.models import dvr as dvr_models from neutron.db.models import l3 from neutron.db.models import l3_attrs from neutron.db.models import l3agent as rb_model from neutron.db import models_v2 from neutron.objects import base from neutron.objects.qos import binding as qos_binding @base.NeutronObjectRegistry.register class RouterRoute(base.NeutronDbObject): # Version 1.0: Initial version VERSION = '1.0' db_model = l3.RouterRoute fields = { 'router_id': common_types.UUIDField(), 'destination': common_types.IPNetworkField(), 'nexthop': obj_fields.IPAddressField() } primary_keys = ['router_id', 'destination', 'nexthop'] foreign_keys = {'Router': {'router_id': 'id'}} @classmethod def modify_fields_from_db(cls, db_obj): result = super(RouterRoute, cls).modify_fields_from_db(db_obj) if 'destination' in result: result['destination'] = net_utils.AuthenticIPNetwork( result['destination']) if 'nexthop' in result: result['nexthop'] = netaddr.IPAddress(result['nexthop']) return result @classmethod def modify_fields_to_db(cls, fields): result = super(RouterRoute, cls).modify_fields_to_db(fields) if 'destination' in result: result['destination'] = cls.filter_to_str(result['destination']) if 'nexthop' in result: result['nexthop'] = cls.filter_to_str(result['nexthop']) return result @base.NeutronObjectRegistry.register class RouterExtraAttributes(base.NeutronDbObject): # Version 1.0: Initial version VERSION = '1.0' db_model = l3_attrs.RouterExtraAttributes fields = { 'router_id': common_types.UUIDField(), 'distributed': obj_fields.BooleanField(default=False), 'service_router': obj_fields.BooleanField(default=False), 'ha': obj_fields.BooleanField(default=False), 'ha_vr_id': obj_fields.IntegerField(nullable=True), 'availability_zone_hints': obj_fields.ListOfStringsField(nullable=True) } primary_keys = ['router_id'] foreign_keys = {'Router': {'router_id': 'id'}} @classmethod def modify_fields_from_db(cls, db_obj): result = super(RouterExtraAttributes, cls).modify_fields_from_db( db_obj) if az_def.AZ_HINTS in result: result[az_def.AZ_HINTS] = ( az_validator.convert_az_string_to_list( result[az_def.AZ_HINTS])) return result @classmethod def modify_fields_to_db(cls, fields): result = super(RouterExtraAttributes, cls).modify_fields_to_db(fields) if az_def.AZ_HINTS in result: result[az_def.AZ_HINTS] = ( az_validator.convert_az_list_to_string( result[az_def.AZ_HINTS])) return result @classmethod @db_api.CONTEXT_READER def get_router_agents_count(cls, context, ha=False, less_than=0): # TODO(sshank): This is pulled out from l3_agentschedulers_db.py # until a way to handle joins is figured out. binding_model = rb_model.RouterL3AgentBinding sub_query = (context.session.query( binding_model.router_id, func.count(binding_model.router_id).label('count')). join(l3_attrs.RouterExtraAttributes, binding_model.router_id == l3_attrs.RouterExtraAttributes.router_id). join(l3.Router). group_by(binding_model.router_id).subquery()) count = func.coalesce(sub_query.c.count, 0) query = (context.session.query(l3.Router, count). outerjoin(sub_query).join(l3_attrs.RouterExtraAttributes). filter(l3_attrs.RouterExtraAttributes.ha == ha)) if less_than > 0: query = query.filter(count < less_than) return list(query) @base.NeutronObjectRegistry.register class RouterPort(base.NeutronDbObject): # Version 1.0: Initial version VERSION = '1.0' db_model = l3.RouterPort primary_keys = ['router_id', 'port_id'] foreign_keys = {'Router': {'router_id': 'id'}, 'Port': {'port_id': 'id'}} fields = { 'router_id': common_types.UUIDField(), 'port_id': common_types.UUIDField(), 'port_type': obj_fields.StringField(nullable=True), } @classmethod @db_api.CONTEXT_READER def get_router_ids_by_subnetpool(cls, context, subnetpool_id): query = context.session.query(l3.RouterPort.router_id) query = query.join(models_v2.Port) query = query.join( models_v2.Subnet, models_v2.Subnet.network_id == models_v2.Port.network_id) query = query.filter( models_v2.Subnet.subnetpool_id == subnetpool_id, l3.RouterPort.port_type.in_(n_const.ROUTER_PORT_OWNERS)) query = query.distinct() return [r[0] for r in query] @classmethod @db_api.CONTEXT_READER def get_gw_port_ids_by_router_id(cls, context, router_id): query = context.session.query(l3.RouterPort) query = query.filter( l3.RouterPort.router_id == router_id, l3.RouterPort.port_type == n_const.DEVICE_OWNER_ROUTER_GW) return [rp.port_id for rp in query] @base.NeutronObjectRegistry.register class DVRMacAddress(base.NeutronDbObject): # Version 1.0: Initial version VERSION = '1.0' db_model = dvr_models.DistributedVirtualRouterMacAddress primary_keys = ['host'] fields = { 'host': obj_fields.StringField(), 'mac_address': common_types.MACAddressField() } @classmethod def modify_fields_from_db(cls, db_obj): fields = super(DVRMacAddress, cls).modify_fields_from_db(db_obj) if 'mac_address' in fields: # NOTE(tonytan4ever): Here uses AuthenticEUI to retain the format # passed from API. fields['mac_address'] = net_utils.AuthenticEUI( fields['mac_address']) return fields @classmethod def modify_fields_to_db(cls, fields): result = super(DVRMacAddress, cls).modify_fields_to_db(fields) if 'mac_address' in fields: result['mac_address'] = cls.filter_to_str(result['mac_address']) return result @base.NeutronObjectRegistry.register class Router(base.NeutronDbObject): # Version 1.0: Initial version # Version 1.1: Added "qos_policy_id" field VERSION = '1.1' db_model = l3.Router fields = { 'id': common_types.UUIDField(), 'project_id': obj_fields.StringField(nullable=True), 'name': obj_fields.StringField(nullable=True), 'status': common_types.RouterStatusEnumField(nullable=True), 'admin_state_up': obj_fields.BooleanField(nullable=True), 'gw_port_id': common_types.UUIDField(nullable=True), 'enable_snat': obj_fields.BooleanField(default=True), 'flavor_id': common_types.UUIDField(nullable=True), 'extra_attributes': obj_fields.ObjectField( 'RouterExtraAttributes', nullable=True), 'qos_policy_id': common_types.UUIDField(nullable=True, default=None), } synthetic_fields = ['extra_attributes', 'qos_policy_id', ] fields_no_update = ['project_id'] @classmethod @db_api.CONTEXT_READER def check_routers_not_owned_by_projects(cls, context, gw_ports, projects): """This method is to check whether routers that aren't owned by existing projects or not """ # TODO(hungpv) We may want to implement NOT semantic in get_object(s) query = context.session.query(l3.Router).filter( l3.Router.gw_port_id.in_(gw_ports)) query = query.filter( ~l3.Router.project_id.in_(projects)) return bool(query.count()) def _attach_qos_policy(self, qos_policy_id): qos_binding.QosPolicyRouterGatewayIPBinding.delete_objects( self.obj_context, router_id=self.id) if qos_policy_id: qos_binding.QosPolicyRouterGatewayIPBinding( self.obj_context, policy_id=qos_policy_id, router_id=self.id).create() self.qos_policy_id = qos_policy_id self.obj_reset_changes(['qos_policy_id']) def create(self): fields = self.obj_get_changes() with self.db_context_writer(self.obj_context): qos_policy_id = self.qos_policy_id super().create() if 'qos_policy_id' in fields: self._attach_qos_policy(qos_policy_id) def update(self): fields = self.obj_get_changes() with self.db_context_writer(self.obj_context): super().update() if 'qos_policy_id' in fields: self._attach_qos_policy(fields['qos_policy_id']) def from_db_object(self, db_obj): super().from_db_object(db_obj) fields_to_change = [] if db_obj.get('qos_policy_binding'): self.qos_policy_id = db_obj.qos_policy_binding.policy_id fields_to_change.append('qos_policy_id') self.obj_reset_changes(fields_to_change) def obj_make_compatible(self, primitive, target_version): _target_version = versionutils.convert_version_to_tuple(target_version) if _target_version < (1, 1): primitive.pop('qos_policy_id', None) @staticmethod @db_api.CONTEXT_READER def get_router_ids_without_router_std_attrs(context): r_attrs = l3_attrs.RouterExtraAttributes query = context.session.query(l3.Router) query = query.join(r_attrs, r_attrs.router_id == l3.Router.id, isouter=True) query = query.filter(r_attrs.router_id == sql.null()) return [r.id for r in query.all()] @base.NeutronObjectRegistry.register class FloatingIP(base.NeutronDbObject): # Version 1.0: Initial version # Version 1.1: Added qos_policy_id field # Version 1.2: Added qos_network_policy_id field VERSION = '1.2' db_model = l3.FloatingIP fields = { 'id': common_types.UUIDField(), 'project_id': obj_fields.StringField(nullable=True), 'floating_ip_address': obj_fields.IPAddressField(), 'floating_network_id': common_types.UUIDField(), 'floating_port_id': common_types.UUIDField(), 'fixed_port_id': common_types.UUIDField(nullable=True), 'fixed_ip_address': obj_fields.IPAddressField(nullable=True), 'qos_policy_id': common_types.UUIDField(nullable=True, default=None), 'qos_network_policy_id': common_types.UUIDField(nullable=True, default=None), 'router_id': common_types.UUIDField(nullable=True), 'last_known_router_id': common_types.UUIDField(nullable=True), 'status': common_types.FloatingIPStatusEnumField(nullable=True), 'dns': obj_fields.ObjectField('FloatingIPDNS', nullable=True), } fields_no_update = ['project_id', 'floating_ip_address', 'floating_network_id', 'floating_port_id'] synthetic_fields = ['dns', 'qos_policy_id', 'qos_network_policy_id', ] @classmethod def modify_fields_from_db(cls, db_obj): result = super(FloatingIP, cls).modify_fields_from_db(db_obj) if 'fixed_ip_address' in result: result['fixed_ip_address'] = netaddr.IPAddress( result['fixed_ip_address']) if 'floating_ip_address' in result: result['floating_ip_address'] = netaddr.IPAddress( result['floating_ip_address']) return result @classmethod def modify_fields_to_db(cls, fields): result = super(FloatingIP, cls).modify_fields_to_db(fields) if 'fixed_ip_address' in result: if result['fixed_ip_address'] is not None: result['fixed_ip_address'] = cls.filter_to_str( result['fixed_ip_address']) if 'floating_ip_address' in result: result['floating_ip_address'] = cls.filter_to_str( result['floating_ip_address']) return result def _attach_qos_policy(self, qos_policy_id): qos_binding.QosPolicyFloatingIPBinding.delete_objects( self.obj_context, fip_id=self.id) if qos_policy_id: fip_binding_obj = qos_binding.QosPolicyFloatingIPBinding( self.obj_context, policy_id=qos_policy_id, fip_id=self.id) fip_binding_obj.create() self.qos_policy_id = qos_policy_id self.obj_reset_changes(['qos_policy_id']) def create(self): fields = self.obj_get_changes() with self.db_context_writer(self.obj_context): qos_policy_id = self.qos_policy_id super(FloatingIP, self).create() if 'qos_policy_id' in fields: self._attach_qos_policy(qos_policy_id) def update(self): fields = self.obj_get_changes() with self.db_context_writer(self.obj_context): super(FloatingIP, self).update() if 'qos_policy_id' in fields: self._attach_qos_policy(fields['qos_policy_id']) def from_db_object(self, db_obj): super(FloatingIP, self).from_db_object(db_obj) fields_to_change = [] if db_obj.get('qos_policy_binding'): self.qos_policy_id = db_obj.qos_policy_binding.policy_id fields_to_change.append('qos_policy_id') if db_obj.get('qos_network_policy_binding'): self.qos_network_policy_id = ( db_obj.qos_network_policy_binding.policy_id) fields_to_change.append('qos_network_policy_binding') self.obj_reset_changes(fields_to_change) def obj_make_compatible(self, primitive, target_version): _target_version = versionutils.convert_version_to_tuple(target_version) if _target_version < (1, 1): primitive.pop('qos_policy_id', None) if _target_version < (1, 2): primitive.pop('qos_network_policy_id', None) @classmethod @db_api.CONTEXT_READER def get_scoped_floating_ips(cls, context, router_ids): query = context.session.query(l3.FloatingIP, models_v2.SubnetPool.address_scope_id) query = query.join( models_v2.Port, l3.FloatingIP.fixed_port_id == models_v2.Port.id) # Outer join of Subnet can cause each ip to have more than one row. query = query.outerjoin( models_v2.Subnet, models_v2.Subnet.network_id == models_v2.Port.network_id) query = query.filter(models_v2.Subnet.ip_version == 4) query = query.outerjoin( models_v2.SubnetPool, models_v2.Subnet.subnetpool_id == models_v2.SubnetPool.id) # Filter out on router_ids query = query.filter(l3.FloatingIP.router_id.in_(router_ids)) # Remove duplicate rows based on FIP IDs and the subnet pool address # scope. Only one subnet pool (per IP version, 4 in this case) can # be assigned to a subnet. The subnet pool address scope for a FIP is # unique. query = query.group_by(l3.FloatingIP.id, models_v2.SubnetPool.address_scope_id) for row in query: yield (cls._load_object(context, row[0]), row[1]) @classmethod @db_api.CONTEXT_READER def get_disassociated_ids_for_net(cls, context, network_id): query = context.session.query(cls.db_model.id) query = query.filter_by( floating_network_id=network_id, router_id=None, fixed_port_id=None) return [f.id for f in query] @base.NeutronObjectRegistry.register class DvrFipGatewayPortAgentBinding(base.NeutronDbObject): # Version 1.0: Initial version VERSION = '1.0' db_model = dvr_models.DvrFipGatewayPortAgentBinding primary_keys = ['network_id', 'agent_id'] fields = { 'network_id': common_types.UUIDField(), 'agent_id': common_types.UUIDField(), }