text: #    Licensed under the Apache License, Version 2.0 (the "License"); you may
  #    not use this file except in compliance with the License. You may obtain
  #    a copy of the License at
  #
  #         http://www.apache.org/licenses/LICENSE-2.0
  #
  #    Unless required by applicable law or agreed to in writing, software
  #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  #    License for the specific language governing permissions and limitations
  #    under the License.
  
  import itertools
  
  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.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 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
      def get_router_agents_count(cls, context):
          # 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())
  
          query = (context.session.query(l3.Router, sub_query.c.count).
                   outerjoin(sub_query))
  
          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
      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]
  
  
  @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.0'
  
      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),
      }
  
      synthetic_fields = ['extra_attributes']
  
      fields_no_update = ['project_id']
  
      @classmethod
      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())
  
  
  @base.NeutronObjectRegistry.register
  class FloatingIP(base.NeutronDbObject):
      # Version 1.0: Initial version
      # Version 1.1: Added qos_policy_id field
      VERSION = '1.1'
  
      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),
          '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',
                          ]
  
      @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')
  
          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)
  
      @classmethod
      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))
          return cls._unique_floatingip_iterator(context, query)
  
      @classmethod
      def _unique_floatingip_iterator(cls, context, query):
          """Iterates over only one row per floating ip. Ignores others."""
          # Group rows by fip id. They must be sorted by same.
          q = query.order_by(l3.FloatingIP.id)
          keyfunc = lambda row: row[0]['id']
          group_iterator = itertools.groupby(q, keyfunc)
  
          # Just hit the first row of each group
          for key, value in group_iterator:
              # pylint: disable=stop-iteration-return
              row = list(next(value))
              yield (cls._load_object(context, row[0]), row[1])
  
  
  @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(),
      }
  