# Copyright 2014 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.

from novaclient import exceptions as nova_exc
from novaclient import utils as n_utils
from novaclient.v1_1 import client
from oslo.config import cfg

from neutron import manager
from neutron.openstack.common import log as logging
from neutron.plugins.cisco.common import cisco_constants as c_constants

LOG = logging.getLogger(__name__)


SERVICE_VM_LIB_OPTS = [
    cfg.StrOpt('templates_path',
               default='/opt/stack/data/neutron/cisco/templates',
               help=_("Path to templates for hosting devices.")),
    cfg.StrOpt('service_vm_config_path',
               default='/opt/stack/data/neutron/cisco/config_drive',
               help=_("Path to config drive files for service VM instances.")),
]

cfg.CONF.register_opts(SERVICE_VM_LIB_OPTS, "general")


class ServiceVMManager(object):

    def __init__(self, user=None, passwd=None, l3_admin_tenant=None,
                 auth_url=''):
        self._nclient = client.Client(user, passwd, l3_admin_tenant, auth_url,
                                      service_type="compute")

    @property
    def _core_plugin(self):
        return manager.NeutronManager.get_plugin()

    def nova_services_up(self):
        """Checks if required Nova services are up and running.

        returns: True if all needed Nova services are up, False otherwise
        """
        required = set(['nova-conductor', 'nova-cert', 'nova-scheduler',
                        'nova-compute', 'nova-consoleauth'])
        try:
            services = self._nclient.services.list()
        # There are several individual Nova client exceptions but they have
        # no other common base than Exception, hence the long list.
        except (nova_exc.UnsupportedVersion, nova_exc.CommandError,
                nova_exc.AuthorizationFailure, nova_exc.NoUniqueMatch,
                nova_exc.AuthSystemNotFound, nova_exc.NoTokenLookupException,
                nova_exc.EndpointNotFound, nova_exc.AmbiguousEndpoints,
                nova_exc.ConnectionRefused, nova_exc.ClientException,
                Exception) as e:
            LOG.error(_('Failure determining running Nova services: %s'), e)
            return False
        return not bool(required.difference(
            [service.binary for service in services
             if service.status == 'enabled' and service.state == 'up']))

    def get_service_vm_status(self, vm_id):
        try:
            status = self._nclient.servers.get(vm_id).status
        # There are several individual Nova client exceptions but they have
        # no other common base than Exception, hence the long list.
        except (nova_exc.UnsupportedVersion, nova_exc.CommandError,
                nova_exc.AuthorizationFailure, nova_exc.NoUniqueMatch,
                nova_exc.AuthSystemNotFound, nova_exc.NoTokenLookupException,
                nova_exc.EndpointNotFound, nova_exc.AmbiguousEndpoints,
                nova_exc.ConnectionRefused, nova_exc.ClientException,
                Exception) as e:
            LOG.error(_('Failed to get status of service VM instance %(id)s, '
                        'due to %(err)s'), {'id': vm_id, 'err': e})
            status = c_constants.SVM_ERROR
        return status

    def dispatch_service_vm(self, context, instance_name, vm_image,
                            vm_flavor, hosting_device_drv, mgmt_port,
                            ports=None):
        nics = [{'port-id': mgmt_port['id']}]
        for port in ports:
            nics.append({'port-id': port['id']})

        try:
            image = n_utils.find_resource(self._nclient.images, vm_image)
            flavor = n_utils.find_resource(self._nclient.flavors, vm_flavor)
        except (nova_exc.CommandError, Exception) as e:
            LOG.error(_('Failure finding needed Nova resource: %s'), e)
            return

        try:
            # Assumption for now is that this does not need to be
            # plugin dependent, only hosting device type dependent.
            files = hosting_device_drv.create_config(context, mgmt_port)
        except IOError:
            return

        try:
            server = self._nclient.servers.create(
                instance_name, image.id, flavor.id, nics=nics, files=files,
                config_drive=(files != {}))
        # There are several individual Nova client exceptions but they have
        # no other common base than Exception, therefore the long list.
        except (nova_exc.UnsupportedVersion, nova_exc.CommandError,
                nova_exc.AuthorizationFailure, nova_exc.NoUniqueMatch,
                nova_exc.AuthSystemNotFound, nova_exc.NoTokenLookupException,
                nova_exc.EndpointNotFound, nova_exc.AmbiguousEndpoints,
                nova_exc.ConnectionRefused, nova_exc.ClientException,
                Exception) as e:
            LOG.error(_('Failed to create service VM instance: %s'), e)
            return
        return {'id': server.id}

    def delete_service_vm(self, context, vm_id):
        try:
            self._nclient.servers.delete(vm_id)
            return True
        # There are several individual Nova client exceptions but they have
        # no other common base than Exception, therefore the long list.
        except (nova_exc.UnsupportedVersion, nova_exc.CommandError,
                nova_exc.AuthorizationFailure, nova_exc.NoUniqueMatch,
                nova_exc.AuthSystemNotFound, nova_exc.NoTokenLookupException,
                nova_exc.EndpointNotFound, nova_exc.AmbiguousEndpoints,
                nova_exc.ConnectionRefused, nova_exc.ClientException,
                Exception) as e:
            LOG.error(_('Failed to delete service VM instance %(id)s, '
                        'due to %(err)s'), {'id': vm_id, 'err': e})
            return False
