# Copyright 2015 OpenStack LLC.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

from neutron_lib import constants as const
from oslo_utils import uuidutils

from neutron.objects import ipam as ipam_objs

# Database operations for Neutron's DB-backed IPAM driver


class IpamSubnetManager(object):

    @classmethod
    def load_by_neutron_subnet_id(cls, context, neutron_subnet_id):

        objs = ipam_objs.IpamSubnet.get_objects(
            context, neutron_subnet_id=neutron_subnet_id)
        return objs.pop() if objs else None

    def __init__(self, ipam_subnet_id, neutron_subnet_id):
        self._ipam_subnet_id = ipam_subnet_id
        self._neutron_subnet_id = neutron_subnet_id

    @property
    def neutron_id(self):
        return self._neutron_subnet_id

    def create(self, context):
        """Create database models for an IPAM subnet.

        This method creates a subnet resource for the IPAM driver and
        associates it with its neutron identifier, if specified.

        :param context: neutron api request context
        :returns: the identifier of created IPAM subnet
        """
        if not self._ipam_subnet_id:
            self._ipam_subnet_id = uuidutils.generate_uuid()
        ipam_objs.IpamSubnet(
            context, id=self._ipam_subnet_id,
            neutron_subnet_id=self._neutron_subnet_id).create()
        return self._ipam_subnet_id

    @classmethod
    def delete(cls, context, neutron_subnet_id):
        """Delete IPAM subnet.

        IPAM subnet no longer has foreign key to neutron subnet,
        so need to perform delete manually

        :param context: neutron api request context
        :param neutron_subnet_id: neutron subnet id associated with ipam subnet
        """
        return ipam_objs.IpamSubnet.delete_objects(
            context, neutron_subnet_id=neutron_subnet_id)

    def create_pool(self, context, pool_start, pool_end):
        """Create an allocation pool for the subnet.

        This method does not perform any validation on parameters; it simply
        persist data on the database.

        :param pool_start: string expressing the start of the pool
        :param pool_end: string expressing the end of the pool
        :return: the newly created pool object.
        """
        ip_pool_obj = ipam_objs.IpamAllocationPool(
            context, ipam_subnet_id=self._ipam_subnet_id, first_ip=pool_start,
            last_ip=pool_end)
        ip_pool_obj.create()
        return ip_pool_obj

    def delete_allocation_pools(self, context):
        """Remove all allocation pools for the current subnet.

        :param context: neutron api request context
        """
        ipam_objs.IpamAllocationPool.delete_objects(
            context, ipam_subnet_id=self._ipam_subnet_id)

    def list_pools(self, context):
        """Return pools for the current subnet."""
        return ipam_objs.IpamAllocationPool.get_objects(
            context, ipam_subnet_id=self._ipam_subnet_id)

    def check_unique_allocation(self, context, ip_address):
        """Validate that the IP address on the subnet is not in use."""
        return not ipam_objs.IpamAllocation.objects_exist(
            context, ipam_subnet_id=self._ipam_subnet_id,
            status=const.IPAM_ALLOCATION_STATUS_ALLOCATED,
            ip_address=ip_address)

    def list_allocations(self, context,
                         status=const.IPAM_ALLOCATION_STATUS_ALLOCATED):
        """Return current allocations for the subnet.

        :param context: neutron api request context
        :param status: IP allocation status
        :returns: a list of IpamAllocation OVO objects
        """
        return ipam_objs.IpamAllocation.get_objects(
            context, ipam_subnet_id=self._ipam_subnet_id, status=status)

    def create_allocation(self, context, ip_address,
                          status=const.IPAM_ALLOCATION_STATUS_ALLOCATED):
        """Create an IP allocation entry.

        :param context: neutron api request context
        :param ip_address: the IP address to allocate
        :param status: IP allocation status
        """
        ipam_objs.IpamAllocation(
            context, ip_address=ip_address, status=status,
            ipam_subnet_id=self._ipam_subnet_id).create()

    def delete_allocation(self, context, ip_address):
        """Remove an IP allocation for this subnet.

        :param context: neutron api request context
        :param ip_address: IP address for which the allocation entry should
            be removed.
        :returns: number of deleted allocation entries.
        """
        return ipam_objs.IpamAllocation.delete_objects(
            context,
            ipam_subnet_id=self._ipam_subnet_id,
            ip_address=ip_address)
