# Copyright 2015 VMware, Inc.
#
#    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.

"""
NSX data models.

This module defines data models used by the VMware NSX plugin family.

"""

from neutron_lib.db import model_base
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy import sql

from neutron.db import models_v2
from oslo_db.sqlalchemy import models

from vmware_nsxlib.v3 import nsx_constants


class TzNetworkBinding(model_base.BASEV2, models.TimestampMixin):
    """Represents a binding of a virtual network with a transport zone.

    This model class associates a Neutron network with a transport zone;
    optionally a vlan ID might be used if the binding type is 'bridge'
    """
    __tablename__ = 'tz_network_bindings'

    # TODO(arosen) - it might be worth while refactoring the how this data
    # is stored later so every column does not need to be a primary key.
    network_id = sa.Column(sa.String(36),
                           sa.ForeignKey('networks.id', ondelete="CASCADE"),
                           primary_key=True)
    # 'flat', 'vlan', 'stt', 'gre', 'l3_ext', 'vxlan', 'portgroup'
    binding_type = sa.Column(sa.Enum('flat', 'vlan', 'stt', 'gre', 'l3_ext',
                                     'vxlan', 'portgroup',
                                     name='tz_network_bindings_binding_type'),
                             nullable=False, primary_key=True)
    phy_uuid = sa.Column(sa.String(36), primary_key=True, default='')
    vlan_id = sa.Column(sa.Integer, primary_key=True,
                        autoincrement=False, default=0)

    def __init__(self, network_id, binding_type, phy_uuid, vlan_id):
        self.network_id = network_id
        self.binding_type = binding_type
        self.phy_uuid = phy_uuid
        self.vlan_id = vlan_id

    def __repr__(self):
        return "<NetworkBinding(%s,%s,%s,%s)>" % (self.network_id,
                                                  self.binding_type,
                                                  self.phy_uuid,
                                                  self.vlan_id)


class NeutronNsxNetworkMapping(model_base.BASEV2, models.TimestampMixin):
    """Maps neutron network identifiers to NSX identifiers.

    Because of chained logical switches more than one mapping might exist
    for a single Neutron network.
    For a VLAN network, one neutron network may map to multiple logical
    switches(port groups) created on multiple DVSes in the backend for
    NSX-V plugin. DVS-ID will store the moref of the DVS where the nsx
    id is being created. For other types and plugins, this value will
    remain null.
    """
    __tablename__ = 'neutron_nsx_network_mappings'
    neutron_id = sa.Column(sa.String(36),
                           sa.ForeignKey('networks.id', ondelete='CASCADE'),
                           primary_key=True)
    nsx_id = sa.Column(sa.String(36), primary_key=True)
    dvs_id = sa.Column(sa.String(36), nullable=True)


class NeutronNsxSecurityGroupMapping(model_base.BASEV2, models.TimestampMixin):
    """Backend mappings for Neutron Security Group identifiers.

    This class maps a neutron security group identifier to the corresponding
    NSX security profile identifier.
    """

    __tablename__ = 'neutron_nsx_security_group_mappings'
    neutron_id = sa.Column(sa.String(36),
                           sa.ForeignKey('securitygroups.id',
                                         ondelete="CASCADE"),
                           primary_key=True)
    nsx_id = sa.Column(sa.String(36), primary_key=True)


class NeutronNsxFirewallSectionMapping(model_base.BASEV2,
                                       models.TimestampMixin):
    """Backend mappings for Neutron Security-group associated fw sections."""

    __tablename__ = 'neutron_nsx_firewall_section_mappings'
    neutron_id = sa.Column(sa.String(36),
                           sa.ForeignKey('securitygroups.id',
                                         ondelete='CASCADE'),
                           primary_key=True,
                           nullable=False)
    nsx_id = sa.Column(sa.String(36), nullable=False)


class NeutronNsxRuleMapping(model_base.BASEV2, models.TimestampMixin):
    """Backend mappings for firewall rules.

    This class maps a neutron security group rule with NSX firewall rule.
    """

    __tablename__ = 'neutron_nsx_rule_mappings'

    neutron_id = sa.Column(sa.String(36),
                           sa.ForeignKey('securitygrouprules.id',
                                         ondelete="CASCADE"),
                           primary_key=True,
                           nullable=False)
    nsx_id = sa.Column(sa.String(36), nullable=False)


class NeutronNsxPortMapping(model_base.BASEV2, models.TimestampMixin):
    """Represents the mapping between neutron and nsx port uuids."""

    __tablename__ = 'neutron_nsx_port_mappings'
    neutron_id = sa.Column(sa.String(36),
                           sa.ForeignKey('ports.id', ondelete="CASCADE"),
                           primary_key=True)
    nsx_switch_id = sa.Column(sa.String(36))
    nsx_port_id = sa.Column(sa.String(36), nullable=False)

    def __init__(self, neutron_id, nsx_switch_id, nsx_port_id):
        self.neutron_id = neutron_id
        self.nsx_switch_id = nsx_switch_id
        self.nsx_port_id = nsx_port_id


class NeutronNsxRouterMapping(model_base.BASEV2, models.TimestampMixin):
    """Maps neutron router identifiers to NSX identifiers."""
    __tablename__ = 'neutron_nsx_router_mappings'
    neutron_id = sa.Column(sa.String(36),
                           sa.ForeignKey('routers.id', ondelete='CASCADE'),
                           primary_key=True)
    nsx_id = sa.Column(sa.String(36))


class NeutronNsxServiceBinding(model_base.BASEV2, models.TimestampMixin):
    """Represents a binding of a Neutron network with enabled NSX services."""
    __tablename__ = 'neutron_nsx_service_bindings'
    network_id = sa.Column(sa.String(36),
                           sa.ForeignKey('networks.id', ondelete='CASCADE'),
                           nullable=False, primary_key=True)
    port_id = sa.Column(sa.String(36), nullable=True)
    nsx_service_type = sa.Column(
        sa.Enum(nsx_constants.SERVICE_DHCP,
                name='neutron_nsx_service_bindings_service_type'),
        nullable=False, primary_key=True)
    nsx_service_id = sa.Column(sa.String(36), nullable=False)


class NeutronNsxDhcpBinding(model_base.BASEV2, models.TimestampMixin):
    """Represents a binding of a Neutron port with DHCP address binding."""
    __tablename__ = 'neutron_nsx_dhcp_bindings'
    port_id = sa.Column(sa.String(36),
                        sa.ForeignKey('ports.id', ondelete="CASCADE"),
                        nullable=False, primary_key=True)
    subnet_id = sa.Column(sa.String(36), nullable=False)
    ip_address = sa.Column(sa.String(64), nullable=False)
    nsx_service_id = sa.Column(sa.String(36), nullable=False)
    nsx_binding_id = sa.Column(sa.String(36), nullable=False, primary_key=True)


class MultiProviderNetworks(model_base.BASEV2, models.TimestampMixin):
    """Networks provisioned through multiprovider extension."""

    __tablename__ = 'multi_provider_networks'
    network_id = sa.Column(sa.String(36),
                           sa.ForeignKey('networks.id', ondelete="CASCADE"),
                           primary_key=True)

    def __init__(self, network_id):
        self.network_id = network_id


class NetworkConnection(model_base.BASEV2, model_base.HasProject,
                        models.TimestampMixin):
    """Defines a connection between a network gateway and a network."""
    # We use port_id as the primary key as one can connect a gateway
    # to a network in multiple ways (and we cannot use the same port form
    # more than a single gateway)
    network_gateway_id = sa.Column(sa.String(36),
                                   sa.ForeignKey('networkgateways.id',
                                                 ondelete='CASCADE'))
    network_id = sa.Column(sa.String(36),
                           sa.ForeignKey('networks.id', ondelete='CASCADE'))
    segmentation_type = sa.Column(
        sa.Enum('flat', 'vlan',
                name='networkconnections_segmentation_type'))
    segmentation_id = sa.Column(sa.Integer)
    __table_args__ = (sa.UniqueConstraint(network_gateway_id,
                                          segmentation_type,
                                          segmentation_id),
                      model_base.BASEV2.__table_args__)
    # Also, storing port id comes back useful when disconnecting a network
    # from a gateway
    port_id = sa.Column(sa.String(36),
                        sa.ForeignKey('ports.id', ondelete='CASCADE'),
                        primary_key=True)


class NetworkGatewayDeviceReference(model_base.BASEV2, models.TimestampMixin):
    id = sa.Column(sa.String(36), primary_key=True)
    network_gateway_id = sa.Column(sa.String(36),
                                   sa.ForeignKey('networkgateways.id',
                                                 ondelete='CASCADE'),
                                   primary_key=True)
    interface_name = sa.Column(sa.String(64), primary_key=True)


class NetworkGatewayDevice(model_base.BASEV2, model_base.HasId,
                           model_base.HasProject, models.TimestampMixin):
    nsx_id = sa.Column(sa.String(36))
    # Optional name for the gateway device
    name = sa.Column(sa.String(255))
    # Transport connector type. Not using enum as range of
    # connector types might vary with backend version
    connector_type = sa.Column(sa.String(10))
    # Transport connector IP Address
    connector_ip = sa.Column(sa.String(64))
    # operational status
    status = sa.Column(sa.String(16))


class NetworkGateway(model_base.BASEV2, model_base.HasId,
                     model_base.HasProject, models.TimestampMixin):
    """Defines the data model for a network gateway."""
    name = sa.Column(sa.String(255))
    default = sa.Column(sa.Boolean())
    devices = orm.relationship(NetworkGatewayDeviceReference,
                               backref='networkgateways',
                               cascade='all,delete')
    network_connections = orm.relationship(NetworkConnection, lazy='joined')


class MacLearningState(model_base.BASEV2, models.TimestampMixin):

    port_id = sa.Column(sa.String(36),
                        sa.ForeignKey('ports.id', ondelete="CASCADE"),
                        primary_key=True)
    mac_learning_enabled = sa.Column(sa.Boolean(), nullable=False)

    # Add a relationship to the Port model using the backref attribute.
    # This will instruct SQLAlchemy to eagerly load this association.
    port = orm.relationship(
        models_v2.Port,
        backref=orm.backref("mac_learning_state", lazy='joined',
                            uselist=False, cascade='delete'))


class LsnPort(models_v2.model_base.BASEV2, models.TimestampMixin):

    __tablename__ = 'lsn_port'

    lsn_port_id = sa.Column(sa.String(36), primary_key=True)

    lsn_id = sa.Column(sa.String(36),
                       sa.ForeignKey('lsn.lsn_id', ondelete="CASCADE"),
                       nullable=False)
    sub_id = sa.Column(sa.String(36), nullable=False, unique=True)
    mac_addr = sa.Column(sa.String(32), nullable=False, unique=True)

    def __init__(self, lsn_port_id, subnet_id, mac_address, lsn_id):
        self.lsn_port_id = lsn_port_id
        self.lsn_id = lsn_id
        self.sub_id = subnet_id
        self.mac_addr = mac_address


class Lsn(models_v2.model_base.BASEV2, models.TimestampMixin):
    __tablename__ = 'lsn'

    lsn_id = sa.Column(sa.String(36), primary_key=True)
    net_id = sa.Column(sa.String(36), nullable=False)

    def __init__(self, net_id, lsn_id):
        self.net_id = net_id
        self.lsn_id = lsn_id


class QoSQueue(model_base.BASEV2, model_base.HasId, model_base.HasProject,
               models.TimestampMixin):
    name = sa.Column(sa.String(255))
    default = sa.Column(sa.Boolean, default=False, server_default=sql.false())
    min = sa.Column(sa.Integer, nullable=False)
    max = sa.Column(sa.Integer, nullable=True)
    qos_marking = sa.Column(sa.Enum('untrusted', 'trusted',
                                    name='qosqueues_qos_marking'))
    dscp = sa.Column(sa.Integer)


class PortQueueMapping(model_base.BASEV2, models.TimestampMixin):
    port_id = sa.Column(sa.String(36),
                        sa.ForeignKey("ports.id", ondelete="CASCADE"),
                        primary_key=True)

    queue_id = sa.Column(sa.String(36), sa.ForeignKey("qosqueues.id"),
                         primary_key=True)

    # Add a relationship to the Port model adding a backref which will
    # allow SQLAlchemy for eagerly load the queue binding
    port = orm.relationship(
        models_v2.Port,
        backref=orm.backref("qos_queue", uselist=False,
                            cascade='delete', lazy='joined'))


class NetworkQueueMapping(model_base.BASEV2, models.TimestampMixin):
    network_id = sa.Column(sa.String(36),
                           sa.ForeignKey("networks.id", ondelete="CASCADE"),
                           primary_key=True)

    queue_id = sa.Column(sa.String(36), sa.ForeignKey("qosqueues.id",
                                                      ondelete="CASCADE"))

    # Add a relationship to the Network model adding a backref which will
    # allow SQLAlcremy for eagerly load the queue binding
    network = orm.relationship(
        models_v2.Network,
        backref=orm.backref("qos_queue", uselist=False,
                            cascade='delete', lazy='joined'))


class NsxL2GWConnectionMapping(model_base.BASEV2, models.TimestampMixin):
    """Define a mapping between L2 gateway connection and bridge endpoint."""
    __tablename__ = 'nsx_l2gw_connection_mappings'
    connection_id = sa.Column(sa.String(36),
                              nullable=False,
                              primary_key=True)
    port_id = sa.Column(sa.String(36),
                        sa.ForeignKey("ports.id", ondelete="CASCADE"),
                        nullable=False)
    bridge_endpoint_id = sa.Column(sa.String(36), nullable=False)


class QosPolicySwitchProfile(model_base.BASEV2, models.TimestampMixin):
    # Maps neutron qos policy identifiers to NSX-V3 switch profile identifiers
    __tablename__ = 'neutron_nsx_qos_policy_mappings'
    qos_policy_id = sa.Column(sa.String(36),
                              sa.ForeignKey('qos_policies.id',
                              ondelete='CASCADE'),
                              primary_key=True)
    switch_profile_id = sa.Column(sa.String(36), nullable=False)


class NsxPortMirrorSessionMapping(model_base.BASEV2):
    """Define a mapping between Tap Flow and PortMirrorSession object."""
    __tablename__ = 'nsx_port_mirror_session_mappings'
    tap_flow_id = sa.Column(sa.String(36),
                            nullable=False,
                            primary_key=True)
    port_mirror_session_id = sa.Column(sa.String(36), nullable=False)


class NsxSubnetIpam(model_base.BASEV2, models.TimestampMixin):
    """Map Subnets with their backend pool id."""
    __tablename__ = 'nsx_subnet_ipam'
    # the Subnet id is not a foreign key because the subnet is deleted
    # before the pool does
    subnet_id = sa.Column(sa.String(36), primary_key=True)
    nsx_pool_id = sa.Column(sa.String(36), primary_key=True)


class NsxCertificateRepository(model_base.BASEV2, models.TimestampMixin):
    """Stores certificate and private key per logical purpose.

    For now, will have zero or one rows with nsxv3 client certificate
    """
    __tablename__ = 'nsx_certificates'
    purpose = sa.Column(sa.String(32),
                        nullable=False,
                        primary_key=True)
    certificate = sa.Column(sa.String(9216), nullable=False)
    private_key = sa.Column(sa.String(5120), nullable=False)
