def on_post(self, req, resp, tenant_id): """Create volume (SL Portable storage).""" client = req.env['sl_client'] try: body = json.loads(req.stream.read().decode()) # required field in the create volume payload namestr = body['volume'].get("display_name") volreq = body['volume'] # portable storage order cannot have empty name name = (config.CONF['volume']['volume_name_prefix'] + (namestr if namestr else "")) # size is required option for volume create. Throw type exception # if it is invalid size = int(volreq['size']) # availability_zone is optional, don't throw exception if # it is not available availability_zone = ( body['volume'].get('availability_zone') or config.CONF['volume']['default_availability_zone']) volume_type = body['volume'].get('volume_type') except Exception: return error_handling.bad_request(resp, 'Malformed request body') try: volinfo = self._create_volume(tenant_id, client, resp, size, name=name, zone=availability_zone, volume_type=volume_type) resp.status = HTTP.ACCEPTED if volinfo: resp.body = {'volume': format_volume(tenant_id, volinfo, client)} resp.body['volume'].update({'status': 'creating'}) else: # Cannot generate a valid response without knowning # the volume id when order takes too long to complete. # This should be a rare case, but it could break openstack # since the volume create caller always expect a volume id # uppon successful return. The approach here is to fail # the volume create operation and leak one portable storage # volume. User can always cancel from SL portal. return error_handling.volume_fault( resp, "Portable storage order delayed") except SoftLayer.SoftLayerAPIError as e: return error_handling.error(resp, "SoftLayerAPIError", e.faultString, code=HTTP.INTERNAL_SERVER_ERROR) except Exception as e: return error_handling.volume_fault(resp, str(e))
def on_get(self, req, resp, tenant_id, volume_id='detail'): try: # if the volume id is 'detail' then we will treat it as # a get list with detail if volume_id == 'detail': get_volume_list_detail(req, resp, tenant_id) else: get_volume(req, resp, tenant_id, volume_id) except Exception as e: error_handling.volume_fault(resp, str(e))
def _list_volumes(self, tenant_id, client, req, resp): """Retrieve all the SoftLayer portable storage devices Generate the Cinder volume list. The swap device(SoftLayer Virtual_Disk_Image with typeID 246) will not be listed. The VSI's boot disk is also SoftLayer portable storage device. The VSI boot disk will always be shown as attached and it will be removed during VSI destory due to its ephemeral nature. :param tenant_id: SoftLayer tenant id :param client: SoftLayer Client :param req: Http Request body :param resp: Http Response body :param return: Http status """ # Get SoftLayer getVirtualDiskImages() function try: _getVirtualDiskImages = getattr(client['Account'], 'getVirtualDiskImages') # filter out the swap disk from the retrived portable storage # devices vols = [x for x in _getVirtualDiskImages(mask=get_virt_disk_img_mask()) if x['typeId'] != VIRTUAL_DISK_IMAGE_TYPE['SWAP'] and not x['localDiskFlag']] resp.body = {"volumes": [format_volume(tenant_id, vol, client) for vol in vols]} resp.status = HTTP.OK except Exception as e: return error_handling.volume_fault(resp, str(e))
def _show_volume(self, tenant_id, volume_id, client, req, resp): """Show the details of a particular portable storage device. :param tenant_id: SoftLayer tenant id :param volume_id: id of the portable storage device :param client: SoftLayer Client :param req: Http Request body :param resp: Http Response body :param return: Http status """ vol = client['Virtual_Disk_Image'] volinfo = None try: volinfo = vol.getObject(id=volume_id, mask=get_virt_disk_img_mask()) except Exception as e: return error_handling.volume_fault(resp, e.faultString, code=HTTP.NOT_FOUND) resp.status = HTTP.OK resp.body = {'volume': format_volume(tenant_id, volinfo, client, showDetails=True)}
def on_get(self, req, resp, tenant_id, instance_id, volume_id): '''Shows details for the specified volume attachment.''' try: instance_id = int(instance_id) except Exception: return error_handling.not_found(resp, "Invalid instance ID specified.") if volume_id and len(volume_id) > OPENSTACK_VOLUME_UUID_LEN: return error_handling.bad_request(resp, message="Malformed request body") # since detail has the same info as the input request params, we can # just return the values back in the response using the request params. # But instead we will do sanity check to ensure the volume_id belongs # to the instance. vg_client = req.sl_client['Virtual_Guest'] try: blkDevices = vg_client.getBlockDevices(mask='id, diskImage.type', id=instance_id) vols = [ x for x in blkDevices if x['diskImage']['type']['keyName'] != 'SWAP' ] for vol in vols: json_response = None vol_disk_id = vol['diskImage']['id'] if str(vol_disk_id) == volume_id: json_response = { "volumeAttachment": { "device": "", "id": vol_disk_id, "serverId": instance_id, "volumeId": vol_disk_id } } break if json_response: resp.body = json_response else: return error_handling.volume_fault(resp, 'Invalid volume id.', code=HTTP.BAD_REQUEST) except Exception as e: return error_handling.volume_fault(resp, e.faultString)
def on_delete(self, req, resp, tenant_id, instance_id, volume_id): """Detach the requested volume from the specified instance.""" try: instance_id = int(instance_id) except Exception: return error_handling.not_found(resp, "Invalid instance ID specified.") if volume_id and len(volume_id) > OPENSTACK_VOLUME_UUID_LEN: return error_handling.bad_request(resp, message="Malformed request body") vdi_client = req.env['sl_client']['Virtual_Disk_Image'] # first let's check if the volume is already attached try: volinfo = vdi_client.getObject(id=volume_id, mask='blockDevices') blkDevices = volinfo['blockDevices'] if len(blkDevices) > 0: guestId_list = [blkDevice['guestId'] for blkDevice in blkDevices] for guest_id in guestId_list: if guest_id == instance_id: try: # detach the volume here vg_client = req.env['sl_client']['Virtual_Guest'] vg_client.detachDiskImage(volume_id, id=instance_id) break except Exception as e: error_handling.volume_fault(resp, e.faultString) else: return error_handling.volume_fault( resp, 'The requested disk image is attached to another ' 'guest and cannot be detached.', code=HTTP.BAD_REQUEST) except Exception as e: return error_handling.volume_fault(resp, e.faultString, code=500) resp.status = HTTP.ACCEPTED
def on_delete(self, req, resp, tenant_id, instance_id, volume_id): """Detach the requested volume from the specified instance.""" try: instance_id = int(instance_id) except Exception: return error_handling.not_found(resp, "Invalid instance ID specified.") if volume_id and len(volume_id) > OPENSTACK_VOLUME_UUID_LEN: return error_handling.bad_request(resp, message="Malformed request body") vdi_client = req.sl_client['Virtual_Disk_Image'] # first let's check if the volume is already attached try: volinfo = vdi_client.getObject(id=volume_id, mask='blockDevices') blkDevices = volinfo['blockDevices'] if len(blkDevices) > 0: guestId_list = [ blkDevice['guestId'] for blkDevice in blkDevices ] for guest_id in guestId_list: if guest_id == instance_id: try: # detach the volume here vg_client = req.sl_client['Virtual_Guest'] vg_client.detachDiskImage(volume_id, id=instance_id) break except Exception as e: error_handling.volume_fault(resp, e.faultString) else: return error_handling.volume_fault( resp, 'The requested disk image is attached to another ' 'guest and cannot be detached.', code=HTTP.BAD_REQUEST) except Exception as e: return error_handling.volume_fault(resp, e.faultString, code=500) resp.status = HTTP.ACCEPTED
def on_get(self, req, resp, tenant_id, instance_id): '''Lists volume attachments for the instance.''' vg_client = req.env['sl_client']['Virtual_Guest'] try: instance_id = int(instance_id) except Exception: return error_handling.not_found(resp, "Invalid instance ID specified.") try: blkDevices = vg_client.getBlockDevices(mask='id, diskImage.type', id=instance_id) vols = [format_volume_attachment(vol['diskImage']['id'], instance_id, '') for vol in blkDevices if vol['diskImage']['type']['keyName'] != 'SWAP'] resp.body = {"volumeAttachments": vols} except Exception as e: error_handling.volume_fault(resp, e.faultString)
def on_get(self, req, resp, tenant_id, instance_id): '''Lists volume attachments for the instance.''' vg_client = req.sl_client['Virtual_Guest'] try: instance_id = int(instance_id) except Exception: return error_handling.not_found(resp, "Invalid instance ID specified.") try: blkDevices = vg_client.getBlockDevices(mask='id, diskImage.type', id=instance_id) vols = [ format_volume_attachment(vol['diskImage']['id'], instance_id, '') for vol in blkDevices if vol['diskImage']['type']['keyName'] != 'SWAP' ] resp.body = {"volumeAttachments": vols} except Exception as e: error_handling.volume_fault(resp, e.faultString)
def on_get(self, req, resp, tenant_id, instance_id, volume_id): '''Shows details for the specified volume attachment.''' try: instance_id = int(instance_id) except Exception: return error_handling.not_found(resp, "Invalid instance ID specified.") if volume_id and len(volume_id) > OPENSTACK_VOLUME_UUID_LEN: return error_handling.bad_request(resp, message="Malformed request body") # since detail has the same info as the input request params, we can # just return the values back in the response using the request params. # But instead we will do sanity check to ensure the volume_id belongs # to the instance. vg_client = req.env['sl_client']['Virtual_Guest'] try: blkDevices = vg_client.getBlockDevices(mask='id, diskImage.type', id=instance_id) vols = [x for x in blkDevices if x['diskImage']['type']['keyName'] != 'SWAP'] for vol in vols: json_response = None vol_disk_id = vol['diskImage']['id'] if str(vol_disk_id) == volume_id: json_response = {"volumeAttachment": {"device": "", "id": vol_disk_id, "serverId": instance_id, "volumeId": vol_disk_id}} break if json_response: resp.body = json_response else: error_handling.volume_fault(resp, 'Invalid volume id.', code=HTTP.BAD_REQUEST) except Exception as e: error_handling.volume_fault(resp, e.faultString)
def _delete_volume(self, tenant_id, volume_id, client, req, resp): virtual_disk = client['Virtual_Disk_Image'] try: item = virtual_disk.getObject(id=volume_id, mask='billingItem') except SoftLayer.SoftLayerAPIError as e: return error_handling.volume_fault(resp, e.faultString, code=HTTP.NOT_FOUND) billingItemId = item['billingItem']['id'] billing = client['Billing_Item'] # Reason is from this document: # https://sldn.softlayer.com/reference/services/ # SoftLayer_Billing_Item/cancelItem reason = "No longer needed" billing.cancelItem(True, True, reason, id=billingItemId) resp.status = HTTP.ACCEPTED
def on_post(self, req, resp, tenant_id, instance_id): '''Attaches a specified volume to a specified server.''' body = json.loads(req.stream.read().decode()) if any([len(body) == 0, 'volumeAttachment' not in body, 'volumeId' not in body['volumeAttachment']]): return error_handling.bad_request(resp, message="Malformed request body") vg_client = req.env['sl_client']['Virtual_Guest'] try: instance_id = int(instance_id) except Exception: return error_handling.not_found(resp, "Invalid instance ID specified.") volume_id = body['volumeAttachment']['volumeId'] if volume_id and len(volume_id) > OPENSTACK_VOLUME_UUID_LEN: return error_handling.bad_request(resp, message="Malformed request body") vdi_client = req.env['sl_client']['Virtual_Disk_Image'] volinfo = None # first let's check if the volume is already attached try: volinfo = vdi_client.getObject(id=volume_id, mask='blockDevices') blkDevices = volinfo['blockDevices'] if (len(blkDevices) > 0): guestId_list = [blkDevice['guestId'] for blkDevice in blkDevices] for guest_id in guestId_list: if (guest_id == instance_id): return error_handling.volume_fault( resp, 'The requested disk image is already attached to ' 'this guest.', code=HTTP.BAD_REQUEST) else: return error_handling.volume_fault( resp, 'The requested disk image is already attached to ' 'another guest.', code=HTTP.BAD_REQUEST) except Exception as e: return error_handling.volume_fault(resp, e.faultString, code=HTTP.NOT_FOUND) try: # providing different size doesn't seem to have any impact on the # outcome hence using 10 as default size. disk_check = vg_client.checkHostDiskAvailability(10, id=instance_id) except Exception: disk_check = True try: if disk_check: sl_transaction = vg_client.attachDiskImage(volume_id, id=instance_id) resp.body = {"volumeAttachment": {"device": "", "id": sl_transaction['id'], "serverId": instance_id, "volumeId": volume_id}} resp.status = HTTP.ACCEPTED else: return error_handling.volume_fault( resp, 'Action causes migration to a new host. Migration is not ' 'allowed.', code=HTTP.BAD_REQUEST) except Exception as e: error_handling.volume_fault(resp, e.faultString)
def on_post(self, req, resp, tenant_id, instance_id): '''Attaches a specified volume to a specified server.''' vs_mgr = SoftLayer.VSManager(req.sl_client) try: instance_id = int(instance_id) except Exception: # If the instance ID is not valid an exception will be sent return error_handling.not_found(resp, "Invalid instance ID specified.") # Get the instance to verify it exists; also using info from # instance to fill out the response object try: instance = vs_mgr.get_instance(instance_id) except Exception as e: # If the instance is not found an exception will be sent return error_handling.volume_fault( resp, 'The requested instance was not found', code=HTTP.NOT_FOUND) if not instance: return error_handling.volume_fault( resp, 'The requested instance was not found', code=HTTP.NOT_FOUND) # Get the volume ID from the request body body = json.loads(req.stream.read().decode()) CREATE_VOLUME_ATTACHMENT_VALIDATOR.validate(body) volume_id = body['volumeAttachment']['volumeId'] # first let's check if the volume is already attached block_mgr = SoftLayer.BlockStorageManager(req.sl_client) blk_devices = [] try: # get the volume info to see if there are any attachments # it also verifies that the volume exists items = {'id', 'allowedVirtualGuests[allowedHost[credential]]'} al_mask = ','.join(items) access_list = block_mgr.get_block_volume_access_list(volume_id, mask=al_mask) blk_devices = access_list['allowedVirtualGuests'] except Exception as e: return error_handling.volume_fault( resp, 'The requested volume was not found', code=HTTP.NOT_FOUND) if len(blk_devices) > 0: return error_handling.volume_fault( resp, 'The requested volume is already attached to ' 'a guest.', code=HTTP.BAD_REQUEST) try: # attach the volume; authorizing the host to the volume block_mgr.authorize_host_to_volume(volume_id, virtual_guest_ids=[instance_id]) volume_att = { 'device': "", 'serverId': instance_id, 'volumeId': volume_id } resp.body = {"volumeAttachment": volume_att} resp.status = HTTP.OK except Exception as e: return error_handling.volume_fault(resp, message=str(e), code=HTTP.BAD_REQUEST)
def on_post(self, req, resp, tenant_id): """Create volume (SL Portable storage) :param req: Falcon request object :param resp: Falcon request object :param tenant_id: Softlayer tenant_id :param return: Falcon response object with openstack response body """ client = req.env['sl_client'] try: v_type_zone = None rounding = False body = json.loads(req.stream.read().decode()) if body['volume']['volume_type'] is not None: if not self.volume_types['volume_types']: resp.status = HTTP.INTERNAL_SERVER_ERROR return error_handling.volume_fault(resp, "Server has no" " types to select") foundType = False for type in self.volume_types['volume_types']: if type['name'] == body['volume']['volume_type']: foundType = True v_type_zone = ( type['extra_specs']['capabilities:volume_backend_name'] # noqa ) rounding = ( type['extra_specs']['drivers:exact_capacity'] ) if not foundType: resp.status = 400 raise Exception('Specify a volume with a valid name') # required field in the create volume payload namestr = body['volume'].get("display_name") volreq = body['volume'] # portable storage order cannot have empty name name = (config.CONF['volume']['volume_name_prefix'] + (namestr if namestr else "")) # size is required option for volume create. Throw type exception # if it is invalid size = int(volreq['size']) # availability_zone is optional, don't throw exception if # it is not available availability_zone = (body['volume'].get('availability_zone') or v_type_zone or config.CONF['volume']['default_availability_zone']) # noqa volume_type = body['volume'].get('volume_type') except Exception as e: return error_handling.bad_request(resp, str(e)) try: volinfo = self._create_volume(tenant_id, client, resp, size, name=name, zone=availability_zone, volume_type=volume_type, exact_capacity=rounding) resp.status = HTTP.ACCEPTED if volinfo: resp.body = {'volume': format_volume(tenant_id, volinfo, client)} resp.body['volume'].update({'status': 'creating'}) else: # Cannot generate a valid response without knowning # the volume id when order takes too long to complete. # This should be a rare case, but it could break openstack # since the volume create caller always expect a volume id # uppon successful return. The approach here is to fail # the volume create operation and leak one portable storage # volume. User can always cancel from SL portal. return error_handling.volume_fault(resp, "Portable storage" " order delayed") except SoftLayer.SoftLayerAPIError as e: return error_handling.error(resp, "SoftLayerAPIError", e.faultString, code=e.faultCode) except Exception as e: return error_handling.volume_fault(resp, str(e))
def on_post(self, req, resp, tenant_id, instance_id): '''Attaches a specified volume to a specified server.''' body = json.loads(req.stream.read().decode()) if (len(body) == 0 or 'volumeAttachment' not in body or 'volumeId' not in body['volumeAttachment']): return error_handling.bad_request(resp, message="Malformed " "request body") vg_client = req.env['sl_client']['Virtual_Guest'] try: instance_id = int(instance_id) except Exception: return error_handling.not_found(resp, "Invalid instance ID specified.") volume_id = body['volumeAttachment']['volumeId'] if volume_id and len(volume_id) > OPENSTACK_VOLUME_UUID_LEN: return error_handling.bad_request(resp, message="Malformed " "request body") vdi_client = req.env['sl_client']['Virtual_Disk_Image'] volinfo = None # first let's check if the volume is already attached try: volinfo = vdi_client.getObject(id=volume_id, mask='blockDevices') blkDevices = volinfo['blockDevices'] if (len(blkDevices) > 0): guestId_list = [ blkDevice['guestId'] for blkDevice in blkDevices ] for guest_id in guestId_list: if (guest_id == instance_id): return error_handling.volume_fault( resp, 'The requested disk image is already attached to ' 'this guest.', code=HTTP.BAD_REQUEST) else: return error_handling.volume_fault( resp, 'The requested disk image is already attached to ' 'another guest.', code=HTTP.BAD_REQUEST) except Exception as e: return error_handling.volume_fault(resp, e.faultString, code=HTTP.NOT_FOUND) try: # providing different size doesn't seem to have any impact on the # outcome hence using 10 as default size. disk_check = vg_client.checkHostDiskAvailability(10, id=instance_id) except Exception: disk_check = True try: if disk_check: sl_transaction = vg_client.attachDiskImage(volume_id, id=instance_id) resp.body = { "volumeAttachment": { "device": "", "id": sl_transaction['id'], "serverId": instance_id, "volumeId": volume_id } } resp.status = HTTP.ACCEPTED else: return error_handling.volume_fault( resp, 'Action causes migration to a new host. Migration is not ' 'allowed.', code=HTTP.BAD_REQUEST) except Exception as e: error_handling.volume_fault(resp, e.faultString)