#!/usr/bin/python

#
#   Copyright 2017 RIFT.IO 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.
#
import gi
gi.require_version('RwcalYang', '1.0')
from gi.repository import RwcalYang
import neutronclient.common.exceptions as NeutronException


class NetworkUtils(object):
    """
    Utility class for network operations
    """
    def __init__(self, driver):
        """
        Constructor for class
        Arguments:
          driver: object of OpenstackDriver()
        """
        self._driver = driver
        self.log = driver.log

    @property
    def driver(self):
        return self._driver
    
    def _parse_cp(self, cp_info):
        """
        Parse the port_info dictionary returned by neutronclient 
        Arguments:
          cp_info: A dictionary object representing port attributes

        Returns:
          Protobuf GI oject of type RwcalYang.VirtualLinkInfoParams_ConnectionPoints()
        """
        cp = RwcalYang.VirtualLinkInfoParams_ConnectionPoints()
        if 'name' in cp_info and cp_info['name']:
            cp.name = cp_info['name']
            
        if 'id' in cp_info and cp_info['id']:
            cp.connection_point_id = cp_info['id']
            
        if ('fixed_ips' in cp_info) and (len(cp_info['fixed_ips']) >= 1):
            if 'ip_address' in cp_info['fixed_ips'][0]:
                cp.ip_address = cp_info['fixed_ips'][0]['ip_address']
                
        if 'mac_address' in cp_info and cp_info['mac_address']:
            cp.mac_addr = cp_info['mac_address']
            
        if cp_info['status'] == 'ACTIVE':
            cp.state = 'active'
        else:
            cp.state = 'inactive'
            
        if 'network_id' in cp_info and cp_info['network_id']:
            cp.virtual_link_id = cp_info['network_id']
            
        if 'device_id' in cp_info and cp_info['device_id']:
            cp.vdu_id = cp_info['device_id']
        return cp

    def parse_cloud_virtual_link_info(self, vlink_info, port_list, subnet):
        """
        Parse vlink_info dictionary (return by python-client) and put values in GI object for Virtual Link

        Arguments:
        vlink_info : A dictionary object return by neutronclient library listing network attributes
        
        Returns:
        Protobuf GI Object of type RwcalYang.VirtualLinkInfoParams()
        """
        link = RwcalYang.VirtualLinkInfoParams()
        link.name  = vlink_info['name']
        if 'status' in vlink_info and vlink_info['status'] == 'ACTIVE':
            link.state = 'active'
        else:
            link.state = 'inactive'

        link.virtual_link_id = vlink_info['id']
        for port in port_list:
            if ('device_owner' in port) and (port['device_owner'] == 'compute:None'):
                link.connection_points.append(self._parse_cp(port))

        if subnet is not None:
            link.subnet = subnet['cidr']

        if ('provider:network_type' in vlink_info) and (vlink_info['provider:network_type'] != None):
            link.provider_network.overlay_type = vlink_info['provider:network_type'].upper()
        if ('provider:segmentation_id' in vlink_info) and (vlink_info['provider:segmentation_id']):
            link.provider_network.segmentation_id = vlink_info['provider:segmentation_id']
        if ('provider:physical_network' in vlink_info) and (vlink_info['provider:physical_network']):
            link.provider_network.physical_network = vlink_info['provider:physical_network'].upper()

        return link
    
    def setup_vdu_networking(self, vdu_params):
        """
        This function validates the networking/connectivity setup.

        Arguments:
          vdu_params: object of RwcalYang.VDUInitParams()

        Returns:
          A list of port_ids and network_ids for VDU
  
        """
        port_args = list()
        network_ids = list()
        add_mgmt_net = False
        for cp in vdu_params.connection_points:
            if cp.virtual_link_id == self.driver._mgmt_network_id:
                ### Remove mgmt_network_id from net_ids
                add_mgmt_net = True
            port_args.append(self._create_cp_args(cp))

        if not add_mgmt_net:
            network_ids.append(self.driver._mgmt_network_id)
            
        ### Create ports and collect port ids
        port_ids = self.driver.neutron_multi_port_create(port_args)
        return port_ids, network_ids
    
        
    def _create_cp_args(self, cp):
        """
        Creates a request dictionary for port create call
        Arguments:
           cp: Object of RwcalYang.VDUInitParams_ConnectionPoints() 
        Returns:
           dict() of request params
        """
        args = dict()
        args['name'] = cp.name
        args['network_id'] = cp.virtual_link_id
        args['admin_state_up'] = True

        if cp.type_yang == 'VIRTIO' or cp.type_yang == 'E1000':
            args['binding:vnic_type'] = 'normal'
        elif cp.type_yang == 'SR_IOV':
            args['binding:vnic_type'] = 'direct'
        else:
            raise NotImplementedError("Port Type: %s not supported" %(cp.type_yang))

        try:
            if cp.static_ip_address:
                args["fixed_ips"] = [{"ip_address" : cp.static_ip_address}]
        except Exception as e:
            pass

        if 'port_security_enabled' in cp:
            args['port_security_enabled'] = cp.port_security_enabled

        if cp.has_field('security_group'):
            if self.driver._neutron_security_groups:
                gid = self.driver._neutron_security_groups[0]['id']
                args['security_groups'] = [ gid ]
        return args

    def make_virtual_link_args(self, link_params):
        """
        Function to create kwargs required for neutron_network_create API
        
        Arguments:
         link_params: Protobuf GI object RwcalYang.VirtualLinkReqParams()

        Returns:
          A kwargs dictionary for network operation
        """
        kwargs = dict()
        kwargs['name']            = link_params.name
        kwargs['admin_state_up']  = True
        kwargs['external_router'] = False
        kwargs['shared']          = False

        if link_params.has_field('provider_network'):
            if link_params.provider_network.has_field('physical_network'):
                kwargs['physical_network'] = link_params.provider_network.physical_network
            if link_params.provider_network.has_field('overlay_type'):
                kwargs['network_type'] = link_params.provider_network.overlay_type.lower()
            if link_params.provider_network.has_field('segmentation_id'):
                kwargs['segmentation_id'] = link_params.provider_network.segmentation_id

        return kwargs

    def make_subnet_args(self, link_params, network_id):
        """
        Function to create kwargs required for neutron_subnet_create API
        
        Arguments:
         link_params: Protobuf GI object RwcalYang.VirtualLinkReqParams()

        Returns:
          A kwargs dictionary for subnet operation
        """
        kwargs = {'network_id' : network_id,
                  'dhcp_params': {'enable_dhcp': True},
                  'gateway_ip' : None,}

        if link_params.ip_profile_params.has_field('ip_version'):
            kwargs['ip_version'] = 6 if link_params.ip_profile_params.ip_version == 'ipv6' else 4
        else:
            kwargs['ip_version'] = 4

        if link_params.ip_profile_params.has_field('subnet_address'):
            kwargs['cidr'] = link_params.ip_profile_params.subnet_address
        elif link_params.ip_profile_params.has_field('subnet_prefix_pool'):
            name = link_params.ip_profile_params.subnet_prefix_pool
            pools = [ p['id']  for p in self.driver._neutron_subnet_prefix_pool if p['name'] == name ]
            if not pools:
                self.log.error("Could not find subnet pool with name :%s to be used for network: %s",
                               link_params.ip_profile_params.subnet_prefix_pool,
                               link_params.name)
                raise NeutronException.NotFound("SubnetPool with name %s not found"%(link_params.ip_profile_params.subnet_prefix_pool))
            
            kwargs['subnetpool_id'] = pools[0]
            
        elif link_params.has_field('subnet'):
            kwargs['cidr'] = link_params.subnet
        else:
            raise NeutronException.NeutronException("No IP Prefix or Pool name specified")

        if link_params.ip_profile_params.has_field('dhcp_params'):
            if link_params.ip_profile_params.dhcp_params.has_field('enabled'):
                kwargs['dhcp_params']['enable_dhcp'] = link_params.ip_profile_params.dhcp_params.enabled
            if link_params.ip_profile_params.dhcp_params.has_field('start_address'):
                kwargs['dhcp_params']['start_address']  = link_params.ip_profile_params.dhcp_params.start_address
            if link_params.ip_profile_params.dhcp_params.has_field('count'):
                kwargs['dhcp_params']['count']  = link_params.ip_profile_params.dhcp_params.count

        if link_params.ip_profile_params.has_field('dns_server'):
            kwargs['dns_server'] = []
            for server in link_params.ip_profile_params.dns_server:
                kwargs['dns_server'].append(server.address)

        if link_params.ip_profile_params.has_field('gateway_address'):
            kwargs['gateway_ip'] = link_params.ip_profile_params.gateway_address

        return kwargs
