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

import os
import time

import log as logging

LOG = logging.getLogger(__name__)

class KBVolAttachException(Exception):
    pass


class BaseCompute(object):
    """
    The Base class for nova compute resources
    1. Creates virtual machines with specific configs
    """

    def __init__(self, vm_name, network):
        self.novaclient = network.router.user.nova_client
        self.network = network
        self.res_logger = network.res_logger
        self.vm_name = vm_name
        self.instance = None
        self.host = None
        self.fip = None
        self.fip_ip = None
        self.subnet_ip = None
        self.fixed_ip = None
        self.ssh_ip = None
        # Shared interface ip for tested and testing cloud
        self.shared_interface_ip = None
        self.vol = None


    # Create a server instance with associated
    # security group, keypair with a provided public key
    def create_server(self, image_name, flavor_type, keyname,
                      nic, sec_group, avail_zone=None, user_data=None,
                      config_drive=None, retry_count=100):
        """
        Create a VM instance given following parameters
        1. VM Name
        2. Image Name
        3. Flavor name
        4. key pair name
        5. Security group instance
        6. Optional parameters: availability zone, user data, config drive
        """

        # Get the image id and flavor id from their logical names
        image = self.find_image(image_name)
        flavor_type = self.find_flavor(flavor_type)

        # Also attach the created security group for the test
        instance = self.novaclient.servers.create(name=self.vm_name,
                                                  image=image,
                                                  flavor=flavor_type,
                                                  key_name=keyname,
                                                  nics=nic,
                                                  availability_zone=avail_zone,
                                                  userdata=user_data,
                                                  config_drive=config_drive,
                                                  security_groups=[sec_group.id])
        self.res_logger.log('instances', self.vm_name, instance.id)

        if not instance:
            return None
        # Verify that the instance gets into the ACTIVE state
        for _ in range(retry_count):
            instance = self.novaclient.servers.get(instance.id)
            if instance.status == 'ACTIVE':
                self.instance = instance
                if 'OS-EXT-SRV-ATTR:hypervisor_hostname' in vars(instance):
                    self.host = vars(instance)['OS-EXT-SRV-ATTR:hypervisor_hostname']
                else:
                    self.host = "Unknown"
                return instance
            if instance.status == 'ERROR':
                LOG.error('Instance creation error:' + instance.fault['message'])
                return None
            time.sleep(2)

    def attach_vol(self):
        if self.vol.status != 'available':
            raise KBVolAttachException('Volume must be in available status before attaching.')
        for _ in range(10):
            try:
                self.novaclient.volumes.create_server_volume(self.instance.id, self.vol.id)
                break
            except Exception:
                time.sleep(1)

    def get_server_list(self):
        servers_list = self.novaclient.servers.list()
        return servers_list

    def delete_server(self):
        # First delete the instance
        if self.instance:
            self.novaclient.servers.delete(self.instance)
            self.instance = None

    def detach_vol(self):
        if self.instance and self.vol:
            attached_vols = self.novaclient.volumes.get_server_volumes(self.instance.id)
            if len(attached_vols):
                self.novaclient.volumes.delete_server_volume(self.instance.id, self.vol.id)

    def find_image(self, image_name):
        """
        Given a image name return the image id
        """
        try:
            image = self.novaclient.images.find(name=image_name)
            return image
        except Exception:
            return None

    def find_flavor(self, flavor_type):
        """
        Given a named flavor return the flavor
        """
        flavor = self.novaclient.flavors.find(name=flavor_type)
        return flavor


class SecGroup(object):


    def __init__(self, novaclient):
        self.secgroup = None
        self.secgroup_name = None
        self.novaclient = novaclient


    def create_secgroup_with_rules(self, group_name):
        group = self.novaclient.security_groups.create(name=group_name,
                                                       description="Test sec group")
        # Allow ping traffic
        self.novaclient.security_group_rules.create(group.id,
                                                    ip_protocol="icmp",
                                                    from_port=-1,
                                                    to_port=-1)
        # Allow SSH traffic
        self.novaclient.security_group_rules.create(group.id,
                                                    ip_protocol="tcp",
                                                    from_port=22,
                                                    to_port=22)
        # Allow HTTP traffic
        self.novaclient.security_group_rules.create(group.id,
                                                    ip_protocol="tcp",
                                                    from_port=80,
                                                    to_port=80)
        # Allow Redis traffic
        self.novaclient.security_group_rules.create(group.id,
                                                    ip_protocol="tcp",
                                                    from_port=6379,
                                                    to_port=6379)
        # Allow Nuttcp traffic
        self.novaclient.security_group_rules.create(group.id,
                                                    ip_protocol="tcp",
                                                    from_port=5001,
                                                    to_port=5002)
        self.novaclient.security_group_rules.create(group.id,
                                                    ip_protocol="udp",
                                                    from_port=5001,
                                                    to_port=5001)
        self.secgroup = group
        self.secgroup_name = group_name


    def delete_secgroup(self):
        """
        Delete the security group
        Sometimes this maybe in use if instance is just deleted
        Add a retry mechanism
        """
        if not self.secgroup:
            return True

        for _ in range(10):
            try:
                self.novaclient.security_groups.delete(self.secgroup)
                return True
            except Exception:
                time.sleep(2)

        LOG.error('Failed while deleting security group %s.' % self.secgroup.id)
        return False

class KeyPair(object):


    def __init__(self, novaclient):
        self.keypair = None
        self.keypair_name = None
        self.novaclient = novaclient


    def add_public_key(self, name, public_key_file=None):
        """
        Add the KloudBuster public key to openstack
        """
        public_key = None
        try:
            with open(os.path.expanduser(public_key_file)) as pkf:
                public_key = pkf.read()
        except IOError as exc:
            LOG.error("Cannot open public key file %s: %s" % (public_key_file,
                                                              exc))
        LOG.info("Adding public key %s" % name)
        keypair = self.novaclient.keypairs.create(name, public_key)
        self.keypair = keypair
        self.keypair_name = name


    def remove_public_key(self):
        """
        Remove the keypair created by KloudBuster
        """
        if self.keypair:
            self.novaclient.keypairs.delete(self.keypair)


class Flavor(object):

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

    def get(self, name):
        flavor = None
        try:
            flavor = vars(self.novaclient.flavors.find(name=name))
        except Exception:
            pass

        return flavor

    def list(self):
        return self.novaclient.flavors.list()

    def create_flavor(self, name, ram, vcpus, disk, ephemeral, override=False):
        # Creating flavors
        if override:
            self.delete_flavor(name)
        return self.novaclient.flavors.create(name=name, ram=ram, vcpus=vcpus,
                                              disk=disk, ephemeral=ephemeral)

    def delete_flavor(self, name):
        try:
            flavor = self.novaclient.flavors.find(name=name)
            flavor.delete()
        except Exception:
            pass

class NovaQuota(object):

    def __init__(self, novaclient, tenant_id):
        self.novaclient = novaclient
        self.tenant_id = tenant_id

    def get(self):
        return vars(self.novaclient.quotas.get(self.tenant_id))

    def update_quota(self, **kwargs):
        self.novaclient.quotas.update(self.tenant_id, **kwargs)

class CinderQuota(object):

    def __init__(self, cinderclient, tenant_id):
        self.cinderclient = cinderclient
        self.tenant_id = tenant_id

    def get(self):
        return vars(self.cinderclient.quotas.get(self.tenant_id))

    def update_quota(self, **kwargs):
        self.cinderclient.quotas.update(self.tenant_id, **kwargs)
