Source code for SoftLayer.managers.storage

"""
    SoftLayer.storage
    ~~~~~~~~~~~~~~~
    Network Storage Manager

    :license: MIT, see LICENSE for more details.
"""
from SoftLayer import exceptions
from SoftLayer.managers import storage_utils
from SoftLayer import utils


# pylint: disable=too-many-public-methods


class StorageManager(utils.IdentifierMixin, object):
    """"Base class for File and Block storage managers

    Any shared code between File and Block should ideally go here.

    :param SoftLayer.API.BaseClient client: the client instance
    """

    def __init__(self, client):
        self.configuration = {}
        self.client = client
        self.resolvers = [self._get_ids_from_username]

    def get_volume_count_limits(self):
        """Returns a list of block volume count limit.

        :return: Returns a list of block volume count limit.
        """
        return self.client.call('Network_Storage', 'getVolumeCountLimits')

    def get_volume_details(self, volume_id, **kwargs):
        """Returns details about the specified volume.

        :param volume_id: ID of volume.
        :param kwargs:
        :return: Returns details about the specified volume.
        """

        if 'mask' not in kwargs:
            items = [
                'id',
                'username',
                'password',
                'capacityGb',
                'snapshotCapacityGb',
                'parentVolume.snapshotSizeBytes',
                'storageType.keyName',
                'serviceResource.datacenter[name]',
                'serviceResourceBackendIpAddress',
                'storageTierLevel',
                'provisionedIops',
                'lunId',
                'originalVolumeName',
                'originalSnapshotName',
                'originalVolumeSize',
                'activeTransactionCount',
                'activeTransactions.transactionStatus[friendlyName]',
                'replicationPartnerCount',
                'replicationStatus',
                'replicationPartners[id,username,'
                'serviceResourceBackendIpAddress,'
                'serviceResource[datacenter[name]],'
                'replicationSchedule[type[keyname]]]',
                'notes',
            ]
            kwargs['mask'] = ','.join(items)
        return self.client.call('Network_Storage', 'getObject', id=volume_id, **kwargs)

    def get_volume_access_list(self, volume_id, **kwargs):
        """Returns a list of authorized hosts for a specified volume.

        :param volume_id: ID of volume.
        :param kwargs:
        :return: Returns a list of authorized hosts for a specified volume.
        """
        if 'mask' not in kwargs:
            items = [
                'id',
                'allowedVirtualGuests[allowedHost[credential, sourceSubnet]]',
                'allowedHardware[allowedHost[credential]]',
                'allowedSubnets[allowedHost[credential]]',
                'allowedIpAddresses[allowedHost[credential]]',
            ]
            kwargs['mask'] = ','.join(items)
        return self.client.call('Network_Storage', 'getObject', id=volume_id, **kwargs)

    def get_volume_snapshot_list(self, volume_id, **kwargs):
        """Returns a list of snapshots for the specified volume.

        :param volume_id: ID of volume.
        :param kwargs:
        :return: Returns a list of snapshots for the specified volume.
        """
        if 'mask' not in kwargs:
            items = [
                'id',
                'notes',
                'snapshotSizeBytes',
                'storageType[keyName]',
                'snapshotCreationTimestamp',
                'intervalSchedule',
                'hourlySchedule',
                'dailySchedule',
                'weeklySchedule'
            ]

            kwargs['mask'] = ','.join(items)

        return self.client.call('Network_Storage', 'getSnapshots', id=volume_id, **kwargs)

    def authorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest_ids=None,
                                 ip_address_ids=None, subnet_ids=None):
        """Authorizes hosts to Storage Volumes

        :param volume_id: The File volume to authorize hosts to
        :param hardware_ids: A List of SoftLayer_Hardware ids
        :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids
        :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids
        :param subnet_ids: A List of SoftLayer_Network_Subnet ids. Only use with File volumes.
        :return: Returns an array of SoftLayer_Network_Storage_Allowed_Host objects
                which now have access to the given volume
        """
        host_templates = storage_utils.populate_host_templates(hardware_ids, virtual_guest_ids,
                                                               ip_address_ids, subnet_ids)

        return self.client.call('Network_Storage', 'allowAccessFromHostList', host_templates, id=volume_id)

    def deauthorize_host_to_volume(self, volume_id, hardware_ids=None, virtual_guest_ids=None,
                                   ip_address_ids=None, subnet_ids=None):
        """Revokes authorization of hosts to File Storage Volumes

        :param volume_id: The File volume to deauthorize hosts to
        :param hardware_ids: A List of SoftLayer_Hardware ids
        :param virtual_guest_ids: A List of SoftLayer_Virtual_Guest ids
        :param ip_address_ids: A List of SoftLayer_Network_Subnet_IpAddress ids
        :param subnet_ids: A List of SoftLayer_Network_Subnet ids. Only use with File volumes
        :return: Returns an array of SoftLayer_Network_Storage_Allowed_Host objects
                which have access to the given File volume
        """
        host_templates = storage_utils.populate_host_templates(hardware_ids, virtual_guest_ids,
                                                               ip_address_ids, subnet_ids)

        return self.client.call('Network_Storage', 'removeAccessFromHostList', host_templates, id=volume_id)

    def get_replication_partners(self, volume_id):
        """Acquires list of replicant volumes pertaining to the given volume.

        :param volume_id: The ID of the primary volume to be replicated
        :return: Returns an array of SoftLayer_Location objects
        """
        return self.client.call('Network_Storage', 'getReplicationPartners', id=volume_id)

    def get_replication_locations(self, volume_id):
        """Acquires list of the datacenters to which a volume can be replicated.

        :param volume_id: The ID of the primary volume to be replicated
        :return: Returns an array of SoftLayer_Network_Storage objects
        """
        return self.client.call('Network_Storage', 'getValidReplicationTargetDatacenterLocations', id=volume_id)

    def order_replicant_volume(self, volume_id, snapshot_schedule, location, tier=None, os_type=None):
        """Places an order for a replicant volume.

        :param volume_id: The ID of the primary volume to be replicated
        :param snapshot_schedule: The primary volume's snapshot
                                  schedule to use for replication
        :param location: The location for the ordered replicant volume
        :param tier: The tier (IOPS per GB) of the primary volume
        :param os_type: The OS type of the primary volume
        :return: Returns a SoftLayer_Container_Product_Order_Receipt
        """

        block_mask = 'billingItem[activeChildren,hourlyFlag],' \
                     'storageTierLevel,osType,staasVersion,' \
                     'hasEncryptionAtRest,snapshotCapacityGb,schedules,' \
                     'intervalSchedule,hourlySchedule,dailySchedule,' \
                     'weeklySchedule,storageType[keyName],provisionedIops'
        block_volume = self.get_volume_details(volume_id, mask=block_mask)

        storage_class = storage_utils.block_or_file(block_volume['storageType']['keyName'])

        order = storage_utils.prepare_replicant_order_object(
            self, snapshot_schedule, location, tier, block_volume, storage_class
        )

        if storage_class == 'block':
            if os_type is None:
                if isinstance(utils.lookup(block_volume, 'osType', 'keyName'), str):
                    os_type = block_volume['osType']['keyName']
                else:
                    raise exceptions.SoftLayerError(
                        "Cannot find primary volume's os-type "
                        "automatically; must specify manually")
            order['osFormatType'] = {'keyName': os_type}

        return self.client.call('Product_Order', 'placeOrder', order)

    def order_duplicate_volume(self, origin_volume_id, origin_snapshot_id=None, duplicate_size=None,
                               duplicate_iops=None, duplicate_tier_level=None, duplicate_snapshot_size=None,
                               hourly_billing_flag=False, dependent_duplicate=False):
        """Places an order for a duplicate volume.

        :param origin_volume_id: The ID of the origin volume to be duplicated
        :param origin_snapshot_id: Origin snapshot ID to use for duplication
        :param duplicate_size: Size/capacity for the duplicate volume
        :param duplicate_iops: The IOPS per GB for the duplicate volume
        :param duplicate_tier_level: Tier level for the duplicate volume
        :param duplicate_snapshot_size: Snapshot space size for the duplicate
        :param hourly_billing_flag: Billing type, monthly (False) or hourly (True), default to monthly.
        :return: Returns a SoftLayer_Container_Product_Order_Receipt
        """

        block_mask = 'id,billingItem[location,hourlyFlag],snapshotCapacityGb,' \
                     'storageType[keyName],capacityGb,originalVolumeSize,' \
                     'provisionedIops,storageTierLevel,osType[keyName],' \
                     'staasVersion,hasEncryptionAtRest'
        origin_volume = self.get_volume_details(origin_volume_id, mask=block_mask)
        storage_class = storage_utils.block_or_file(origin_volume['storageType']['keyName'])

        order = storage_utils.prepare_duplicate_order_object(
            self, origin_volume, duplicate_iops, duplicate_tier_level,
            duplicate_size, duplicate_snapshot_size, storage_class, hourly_billing_flag
        )

        if storage_class == 'block':
            if isinstance(utils.lookup(origin_volume, 'osType', 'keyName'), str):
                os_type = origin_volume['osType']['keyName']
            else:
                raise exceptions.SoftLayerError("Cannot find origin volume's os-type")

            order['osFormatType'] = {'keyName': os_type}

        if origin_snapshot_id is not None:
            order['duplicateOriginSnapshotId'] = origin_snapshot_id
        if dependent_duplicate:
            # if isDependentDuplicateFlag is set to ANYTHING, it is considered dependent.
            order['isDependentDuplicateFlag'] = 1

        return self.client.call('Product_Order', 'placeOrder', order)

    def order_modified_volume(self, volume_id, new_size=None, new_iops=None, new_tier_level=None):
        """Places an order for modifying an existing block volume.

        :param volume_id: The ID of the volume to be modified
        :param new_size: The new size/capacity for the volume
        :param new_iops: The new IOPS for the volume
        :param new_tier_level: The new tier level for the volume
        :return: Returns a SoftLayer_Container_Product_Order_Receipt
        """

        mask_items = [
            'id',
            'billingItem',
            'storageType[keyName]',
            'capacityGb',
            'provisionedIops',
            'storageTierLevel',
            'staasVersion',
            'hasEncryptionAtRest',
        ]
        block_mask = ','.join(mask_items)
        volume = self.get_volume_details(volume_id, mask=block_mask)

        order = storage_utils.prepare_modify_order_object(
            self, volume, new_iops, new_tier_level, new_size
        )

        return self.client.call('Product_Order', 'placeOrder', order)

    def volume_set_note(self, volume_id, note):
        """Set the notes for an existing block volume.

        :param volume_id: The ID of the volume to be modified
        :param note: the note
        :return: Returns true if success
        """
        template = {'notes': note}
        return self.client.call('SoftLayer_Network_Storage', 'editObject', template, id=volume_id)

    def delete_snapshot(self, snapshot_id):
        """Deletes the specified snapshot object.

        :param snapshot_id: The ID of the snapshot object to delete.
        """
        return self.client.call('Network_Storage', 'deleteObject', id=snapshot_id)

    def create_snapshot(self, volume_id, notes='', **kwargs):
        """Creates a snapshot on the given block volume.

        :param integer volume_id: The id of the volume
        :param string notes: The notes or "name" to assign the snapshot
        :return: Returns the id of the new snapshot
        """
        return self.client.call('Network_Storage', 'createSnapshot', notes, id=volume_id, **kwargs)

    def order_snapshot_space(self, volume_id, capacity, tier, upgrade, **kwargs):
        """Orders snapshot space for the given block volume.

        :param integer volume_id: The id of the volume
        :param integer capacity: The capacity to order, in GB
        :param float tier: The tier level of the block volume, in IOPS per GB
        :param boolean upgrade: Flag to indicate if this order is an upgrade
        :return: Returns a SoftLayer_Container_Product_Order_Receipt
        """
        object_mask = 'id,billingItem[location,hourlyFlag],' \
                      'storageType[keyName],storageTierLevel,provisionedIops,' \
                      'staasVersion,hasEncryptionAtRest'
        volume = self.get_volume_details(volume_id, mask=object_mask, **kwargs)

        order = storage_utils.prepare_snapshot_order_object(self, volume, capacity, tier, upgrade)

        return self.client.call('Product_Order', 'placeOrder', order)

    def cancel_snapshot_space(self, volume_id, reason='No longer needed', immediate=False):
        """Cancels snapshot space for a given volume.

        :param integer volume_id: The volume ID
        :param string reason: The reason for cancellation
        :param boolean immediate_flag: Cancel immediately or on anniversary date
        """

        object_mask = 'mask[id,billingItem[activeChildren,hourlyFlag]]'
        volume = self.get_volume_details(volume_id, mask=object_mask)

        if 'activeChildren' not in volume['billingItem']:
            raise exceptions.SoftLayerError('No snapshot space found to cancel')

        children_array = volume['billingItem']['activeChildren']
        billing_item_id = None

        for child in children_array:
            if child['categoryCode'] == 'storage_snapshot_space':
                billing_item_id = child['id']
                break

        if not billing_item_id:
            raise exceptions.SoftLayerError('No snapshot space found to cancel')

        if utils.lookup(volume, 'billingItem', 'hourlyFlag'):
            immediate = True

        return self.client.call('SoftLayer_Billing_Item', 'cancelItem', immediate, True, reason, id=billing_item_id)

    def enable_snapshots(self, volume_id, schedule_type, retention_count, minute, hour, day_of_week, **kwargs):
        """Enables snapshots for a specific block volume at a given schedule

        :param integer volume_id: The id of the volume
        :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY'
        :param integer retention_count: Number of snapshots to be kept
        :param integer minute: Minute when to take snapshot
        :param integer hour: Hour when to take snapshot
        :param string day_of_week: Day when to take snapshot
        :return: Returns whether successfully scheduled or not
        """
        return self.client.call('Network_Storage', 'enableSnapshots', schedule_type, retention_count,
                                minute, hour, day_of_week, id=volume_id, **kwargs)

    def disable_snapshots(self, volume_id, schedule_type):
        """Disables snapshots for a specific block volume at a given schedule

        :param integer volume_id: The id of the volume
        :param string schedule_type: 'HOURLY'|'DAILY'|'WEEKLY'
        :return: Returns whether successfully disabled or not
        """
        return self.client.call('Network_Storage', 'disableSnapshots', schedule_type, id=volume_id)

    def list_volume_schedules(self, volume_id):
        """Lists schedules for a given volume

        :param integer volume_id: The id of the volume
        :return: Returns list of schedules assigned to a given volume
        """
        object_mask = 'schedules[type,properties[type]]'
        volume_detail = self.client.call('Network_Storage', 'getObject', id=volume_id, mask=object_mask)

        return utils.lookup(volume_detail, 'schedules')

    def restore_from_snapshot(self, volume_id, snapshot_id):
        """Restores a specific volume from a snapshot

        :param integer volume_id: The id of the volume
        :param integer snapshot_id: The id of the restore point
        :return: Returns whether succesfully restored or not
        """
        return self.client.call('Network_Storage', 'restoreFromSnapshot', snapshot_id, id=volume_id)

    def failover_to_replicant(self, volume_id, replicant_id):
        """Failover to a volume replicant.

        :param integer volume_id: The id of the volume
        :param integer replicant_id: ID of replicant to failover to
        :return: Returns whether failover was successful or not
        """
        return self.client.call('Network_Storage', 'failoverToReplicant', replicant_id, id=volume_id)

    def failback_from_replicant(self, volume_id):
        """Failback from a volume replicant.

        :param integer volume_id: The id of the volume
        :return: Returns whether failback was successful or not
        """

        return self.client.call('Network_Storage', 'failbackFromReplicant', id=volume_id)

    def cancel_volume(self, volume_id, reason='No longer needed', immediate=False):
        """Cancels the given storage volume.

        :param integer volume_id: The volume ID
        :param string reason: The reason for cancellation
        :param boolean immediate_flag: Cancel immediately or on anniversary date
        """
        object_mask = 'mask[id,billingItem[id,hourlyFlag]]'
        volume = self.get_volume_details(volume_id, mask=object_mask)

        if 'billingItem' not in volume:
            raise exceptions.SoftLayerError("Storage Volume was already cancelled")

        billing_item_id = volume['billingItem']['id']

        if utils.lookup(volume, 'billingItem', 'hourlyFlag'):
            immediate = True

        return self.client.call('SoftLayer_Billing_Item', 'cancelItem', immediate, True, reason, id=billing_item_id)

    def refresh_dupe(self, volume_id, snapshot_id):
        """"Refresh a duplicate volume with a snapshot from its parent.

        :param integer volume_id: The id of the volume
        :param integer snapshot_id: The id of the snapshot
        """
        return self.client.call('Network_Storage', 'refreshDuplicate', snapshot_id, id=volume_id)

    def convert_dep_dupe(self, volume_id):
        """Convert a dependent duplicate volume to an independent volume.

        :param integer volume_id: The id of the volume.
        """
        return self.client.call('Network_Storage', 'convertCloneDependentToIndependent', id=volume_id)