# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2013 IBM Corp.
#
#    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 socket
import time

from oslo.config import cfg

from nova.compute import flavors
from nova.compute import utils as compute_utils
from nova.image import glance
from nova.openstack.common.gettextutils import _
from nova.openstack.common import log as logging
from nova.virt import driver
from nova.virt.powervm import exception
from nova.virt.powervm import operator

LOG = logging.getLogger(__name__)

powervm_opts = [
    cfg.StrOpt('powervm_mgr_type',
               default='ivm',
               help='PowerVM manager type (ivm, hmc)'),
    cfg.StrOpt('powervm_mgr',
               help='PowerVM manager host or ip'),
    cfg.StrOpt('powervm_mgr_user',
               help='PowerVM manager user name'),
    cfg.StrOpt('powervm_mgr_passwd',
               help='PowerVM manager user password',
               secret=True),
    cfg.StrOpt('powervm_img_remote_path',
               default='/home/padmin',
               help='PowerVM image remote path where images will be moved.'
               ' Make sure this path can fit your biggest image in glance'),
    cfg.StrOpt('powervm_img_local_path',
               default='/tmp',
               help='Local directory to download glance images to.'
               ' Make sure this path can fit your biggest image in glance')
    ]

CONF = cfg.CONF
CONF.register_opts(powervm_opts)


class PowerVMDriver(driver.ComputeDriver):

    """PowerVM Implementation of Compute Driver."""

    def __init__(self, virtapi):
        super(PowerVMDriver, self).__init__(virtapi)
        self._powervm = operator.PowerVMOperator()

    def init_host(self, host):
        """Initialize anything that is necessary for the driver to function,
        including catching up with currently running VM's on the given host.
        """
        pass

    def get_info(self, instance):
        """Get the current status of an instance."""
        return self._powervm.get_info(instance['name'])

    def get_num_instances(self):
        return len(self.list_instances())

    def instance_exists(self, instance_name):
        return self._powervm.instance_exists(instance_name)

    def list_instances(self):
        return self._powervm.list_instances()

    def get_host_stats(self, refresh=False):
        """Return currently known host stats."""
        return self._powervm.get_host_stats(refresh=refresh)

    def get_host_uptime(self, host):
        """Returns the result of calling "uptime" on the target host."""
        return self._powervm.get_host_uptime(host)

    def plug_vifs(self, instance, network_info):
        """Plug VIFs into networks."""
        msg = _("VIF plugging is not supported by the PowerVM driver.")
        raise NotImplementedError(msg)

    def unplug_vifs(self, instance, network_info):
        """Unplug VIFs from networks."""
        msg = _("VIF unplugging is not supported by the PowerVM driver.")
        raise NotImplementedError(msg)

    def macs_for_instance(self, instance):
        return self._powervm.macs_for_instance(instance)

    def spawn(self, context, instance, image_meta, injected_files,
              admin_password, network_info=None, block_device_info=None):
        """Create a new instance/VM/domain on powerVM."""
        self._powervm.spawn(context, instance, image_meta['id'], network_info)

    def destroy(self, instance, network_info, block_device_info=None,
                destroy_disks=True, context=None):
        """Destroy (shutdown and delete) the specified instance."""
        self._powervm.destroy(instance['name'], destroy_disks)

    def reboot(self, context, instance, network_info, reboot_type,
               block_device_info=None, bad_volumes_callback=None):
        """Reboot the specified instance.

        :param instance: Instance object as returned by DB layer.
        :param network_info:
           :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
        :param reboot_type: Either a HARD or SOFT reboot
        :param block_device_info: Info pertaining to attached volumes
        :param bad_volumes_callback: Function to handle any bad volumes
            encountered
        """
        if reboot_type == 'SOFT':
            LOG.debug(_('Soft reboot is not supported for PowerVM.'),
                      instance=instance)
            return

        self.power_off(instance)
        self.power_on(context, instance, network_info, block_device_info)

    def get_host_ip_addr(self):
        """Retrieves the IP address of the hypervisor host."""
        LOG.debug(_("In get_host_ip_addr"))
        # TODO(mrodden): use operator get_hostname instead
        hostname = CONF.powervm_mgr
        LOG.debug(_("Attempting to resolve %s") % hostname)
        ip_addr = socket.gethostbyname(hostname)
        LOG.debug(_("%(hostname)s was successfully resolved to %(ip_addr)s") %
                  {'hostname': hostname, 'ip_addr': ip_addr})
        return ip_addr

    def snapshot(self, context, instance, image_id, update_task_state):
        """Snapshots the specified instance.

        :param context: security context
        :param instance: Instance object as returned by DB layer.
        :param image_id: Reference to a pre-created image that will
                         hold the snapshot.
        :param update_task_state: Function reference that allows for updates
                                  to the instance task state.
        """
        snapshot_start = time.time()

        # get current image info
        glance_service, old_image_id = glance.get_remote_image_service(
                context, instance['image_ref'])

        image_meta = compute_utils.get_image_metadata(
            context, glance_service, old_image_id, instance)

        # build updated snapshot metadata
        snapshot_meta = glance_service.show(context, image_id)
        new_snapshot_meta = {'is_public': False,
                             'name': snapshot_meta['name'],
                             'status': 'active',
                             'properties': {'image_location': 'snapshot',
                                            'image_state': 'available',
                                            'owner_id': instance['project_id']
                                           },
                             'disk_format': image_meta['disk_format'],
                             'container_format': image_meta['container_format']
                            }

        # disk capture and glance upload
        self._powervm.capture_image(context, instance, image_id,
                                    new_snapshot_meta, update_task_state)

        snapshot_time = time.time() - snapshot_start
        inst_name = instance['name']
        LOG.info(_("%(inst_name)s captured in %(snapshot_time)s seconds"),
                 {'inst_name': inst_name, 'snapshot_time': snapshot_time})

    def pause(self, instance):
        """Pause the specified instance."""
        msg = _("pause is not supported for PowerVM")
        raise NotImplementedError(msg)

    def unpause(self, instance):
        """Unpause paused VM instance."""
        msg = _("unpause is not supported for PowerVM")
        raise NotImplementedError(msg)

    def suspend(self, instance):
        """suspend the specified instance."""
        raise NotImplementedError(_("Suspend is not supported by the"
                                    "PowerVM driver."))

    def resume(self, context, instance, network_info, block_device_info=None):
        """resume the specified instance."""
        raise NotImplementedError(_("Resume is not supported by the"
                                    "PowerVM driver."))

    def power_off(self, instance):
        """Power off the specified instance."""
        self._powervm.power_off(instance['name'])

    def power_on(self, context, instance, network_info,
                 block_device_info=None):
        """Power on the specified instance."""
        self._powervm.power_on(instance['name'])

    def get_available_resource(self, nodename):
        """Retrieve resource info."""
        return self._powervm.get_available_resource()

    def host_power_action(self, host, action):
        """Reboots, shuts down or powers up the host."""
        raise NotImplementedError(_("Host power action is not supported by the"
                                    "PowerVM driver."))

    def migrate_disk_and_power_off(self, context, instance, dest,
                                   instance_type, network_info,
                                   block_device_info=None):
        """Transfers the disk of a running instance in multiple phases, turning
           off the instance before the end.

        :returns: disk_info dictionary that is passed as the
                  disk_info parameter to finish_migration
                  on the destination nova-compute host
        """
        src_host = self.get_host_ip_addr()
        pvm_op = self._powervm._operator
        lpar_obj = pvm_op.get_lpar(instance['name'])
        vhost = pvm_op.get_vhost_by_instance_id(lpar_obj['lpar_id'])
        diskname = pvm_op.get_disk_name_by_vhost(vhost)

        self._powervm.power_off(instance['name'], timeout=120)
        # NOTE(ldbragst) Here we need to check if the resize or migrate is
        # happening on the same host. If yes, then we need to assign a temp
        # mac address to the source LPAR so we don't have a conflict when
        # another LPAR is booted with the same mac address as the
        # original LPAR
        if src_host == dest:
            macs = self.macs_for_instance(instance)
            temp_mac = macs.pop()
            self._powervm._operator.set_lpar_mac_base_value(instance['name'],
                                                            temp_mac)

        disk_info = self._powervm.migrate_disk(
                diskname, src_host, dest, CONF.powervm_img_remote_path,
                instance['name'])
        disk_info['old_lv_size'] = pvm_op.get_logical_vol_size(diskname)
        new_name = self._get_resize_name(instance['name'])
        pvm_op.rename_lpar(instance['name'], new_name)
        return disk_info

    def _get_resize_name(self, instance_name):
        """Rename the instance to be migrated to avoid naming conflicts

        :param instance_name: name of instance to be migrated
        :returns: the new instance name
        """
        name_tag = 'rsz_'

        # if the current name would overflow with new tag
        if ((len(instance_name) + len(name_tag)) > 31):
            # remove enough chars for the tag to fit
            num_chars = len(name_tag)
            old_name = instance_name[num_chars:]
        else:
            old_name = instance_name

        return ''.join([name_tag, old_name])

    def finish_migration(self, context, migration, instance, disk_info,
                         network_info, image_meta, resize_instance,
                         block_device_info=None, power_on=True):
        """Completes a resize, turning on the migrated instance

        :param network_info:
           :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
        :param image_meta: image object returned by nova.image.glance that
                           defines the image from which this instance
                           was created
        """
        lpar_obj = self._powervm._create_lpar_instance(instance, network_info)

        instance_type = flavors.extract_flavor(instance)
        new_lv_size = instance_type['root_gb']
        old_lv_size = disk_info['old_lv_size']
        if 'root_disk_file' in disk_info:
            disk_size = max(int(new_lv_size), int(old_lv_size))
            disk_size_bytes = disk_size * 1024 * 1024 * 1024
            self._powervm.deploy_from_migrated_file(
                    lpar_obj, disk_info['root_disk_file'], disk_size_bytes,
                    power_on)
        else:
            # this shouldn't get hit unless someone forgot to handle
            # a certain migration type
            raise exception.PowerVMUnrecognizedRootDevice(disk_info=disk_info)

    def confirm_migration(self, migration, instance, network_info):
        """Confirms a resize, destroying the source VM."""

        new_name = self._get_resize_name(instance['name'])
        self._powervm.destroy(new_name)

    def finish_revert_migration(self, context, instance, network_info,
                                block_device_info=None, power_on=True):
        """Finish reverting a resize."""

        new_name = self._get_resize_name(instance['name'])

        # NOTE(ldbragst) In the case of a resize_revert on the same host
        # we reassign the original mac address, replacing the temp mac
        # on the old instance that will be started
        # NOTE(guochbo) We can't judge if a resize_revert on the same host
        # due to the instance on destination host has been destoryed.
        # Original mac address is always kept in network_info, we can
        # reassign the original mac address here without negative effects
        # even the old instance kept the original mac address
        if self.instance_exists(new_name):
            original_mac = network_info[0]['address']
            self._powervm._operator.set_lpar_mac_base_value(new_name,
                                                            original_mac)
        # Make sure we don't have a failed same-host migration still
        # hanging around
            if self.instance_exists(instance['name']):
                self._powervm.destroy(instance['name'])
            # undo instance rename and start
            self._powervm._operator.rename_lpar(new_name, instance['name'])

        if power_on:
            self._powervm.power_on(instance['name'])

    def add_to_aggregate(self, context, aggregate, host, **kwargs):
        """Add a compute host to an aggregate."""
        pass

    def remove_from_aggregate(self, context, aggregate, host, **kwargs):
        """Remove a compute host from an aggregate."""
        pass

    def undo_aggregate_operation(self, context, op, aggregate,
                                  host, set_error=True):
        """Undo for Resource Pools."""
        pass
