"Fossies" - the Fresh Open Source Software Archive

Member "ec2-api-12.0.0/ec2api/api/volume.py" (14 Apr 2021, 9345 Bytes) of package /linux/misc/openstack/ec2-api-12.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "volume.py" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 12.0.0_vs_13.0.0.

    1 # Copyright 2014
    2 # The Cloudscaling Group, Inc.
    3 #
    4 # Licensed under the Apache License, Version 2.0 (the "License");
    5 # you may not use this file except in compliance with the License.
    6 # You may obtain a copy of the License at
    7 # http://www.apache.org/licenses/LICENSE-2.0
    8 #
    9 # Unless required by applicable law or agreed to in writing, software
   10 # distributed under the License is distributed on an "AS IS" BASIS,
   11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   12 # See the License for the specific language governing permissions and
   13 # limitations under the License.
   14 
   15 from cinderclient import exceptions as cinder_exception
   16 from novaclient import exceptions as nova_exception
   17 from oslo_log import log as logging
   18 
   19 from ec2api.api import common
   20 from ec2api.api import ec2utils
   21 from ec2api import clients
   22 from ec2api import context as ec2_context
   23 from ec2api.db import api as db_api
   24 from ec2api import exception
   25 from ec2api.i18n import _
   26 
   27 
   28 LOG = logging.getLogger(__name__)
   29 
   30 
   31 """Volume related API implementation
   32 """
   33 
   34 
   35 Validator = common.Validator
   36 
   37 
   38 def create_volume(context, availability_zone=None, size=None,
   39                   snapshot_id=None, volume_type=None, iops=None,
   40                   encrypted=None, kms_key_id=None):
   41     if snapshot_id is not None:
   42         snapshot = ec2utils.get_db_item(context, snapshot_id)
   43         os_snapshot_id = snapshot['os_id']
   44     else:
   45         os_snapshot_id = None
   46 
   47     cinder = clients.cinder(context)
   48     with common.OnCrashCleaner() as cleaner:
   49         os_volume = cinder.volumes.create(
   50                 size, snapshot_id=os_snapshot_id, volume_type=volume_type,
   51                 availability_zone=availability_zone)
   52         cleaner.addCleanup(os_volume.delete)
   53 
   54         volume = db_api.add_item(context, 'vol', {'os_id': os_volume.id})
   55         cleaner.addCleanup(db_api.delete_item, context, volume['id'])
   56         os_volume.update(display_name=volume['id'])
   57 
   58     return _format_volume(context, volume, os_volume, snapshot_id=snapshot_id)
   59 
   60 
   61 def attach_volume(context, volume_id, instance_id, device):
   62     volume = ec2utils.get_db_item(context, volume_id)
   63     instance = ec2utils.get_db_item(context, instance_id)
   64 
   65     nova = clients.nova(context)
   66     try:
   67         nova.volumes.create_server_volume(instance['os_id'], volume['os_id'],
   68                                           device)
   69     except (nova_exception.Conflict, nova_exception.BadRequest):
   70         # TODO(andrey-mp): raise correct errors for different cases
   71         LOG.exception('Attach has failed.')
   72         raise exception.UnsupportedOperation()
   73     cinder = clients.cinder(context)
   74     os_volume = cinder.volumes.get(volume['os_id'])
   75     attachment = _format_attachment(context, volume, os_volume,
   76                                     instance_id=instance_id)
   77     # NOTE(andrey-mp): nova sets deleteOnTermination=False for attached volume
   78     attachment['deleteOnTermination'] = False
   79     return attachment
   80 
   81 
   82 def detach_volume(context, volume_id, instance_id=None, device=None,
   83                   force=None):
   84     volume = ec2utils.get_db_item(context, volume_id)
   85 
   86     cinder = clients.cinder(context)
   87     os_volume = cinder.volumes.get(volume['os_id'])
   88     os_instance_id = next(iter(os_volume.attachments), {}).get('server_id')
   89     if not os_instance_id:
   90         # TODO(ft): Change the message with the real AWS message
   91         reason = _('Volume %(vol_id)s is not attached to anything')
   92         raise exception.IncorrectState(reason=reason % {'vol_id': volume_id})
   93 
   94     nova = clients.nova(context)
   95     nova.volumes.delete_server_volume(os_instance_id, os_volume.id)
   96     os_volume.get()
   97     instance_id = next((i['id'] for i in db_api.get_items(context, 'i')
   98                         if i['os_id'] == os_instance_id), None)
   99     return _format_attachment(context, volume, os_volume,
  100                               instance_id=instance_id)
  101 
  102 
  103 def delete_volume(context, volume_id):
  104     volume = ec2utils.get_db_item(context, volume_id)
  105     cinder = clients.cinder(context)
  106     try:
  107         cinder.volumes.delete(volume['os_id'])
  108     except cinder_exception.BadRequest:
  109         # TODO(andrey-mp): raise correct errors for different cases
  110         raise exception.UnsupportedOperation()
  111     except cinder_exception.NotFound:
  112         pass
  113     # NOTE(andrey-mp) Don't delete item from DB until it disappears from Cloud
  114     # It will be deleted by describer in the future
  115     return True
  116 
  117 
  118 class VolumeDescriber(common.TaggableItemsDescriber):
  119 
  120     KIND = 'vol'
  121     SORT_KEY = 'volumeId'
  122     FILTER_MAP = {
  123         'availability-zone': 'availabilityZone',
  124         'create-time': 'createTime',
  125         'encrypted': 'encrypted',
  126         'size': 'size',
  127         'snapshot-id': 'snapshotId',
  128         'status': 'status',
  129         'volume-id': 'volumeId',
  130         'volume-type': 'volumeType',
  131         'attachment.delete-on-termination':
  132             ['attachmentSet', 'deleteOnTermination'],
  133         'attachment.device': ['attachmentSet', 'device'],
  134         'attachment.instance-id': ['attachmentSet', 'instanceId'],
  135         'attachment.status': ['attachmentSet', 'status']}
  136 
  137     def format(self, volume, os_volume):
  138         return _format_volume(self.context, volume, os_volume,
  139                               self.instances, self.os_instances,
  140                               self.snapshots)
  141 
  142     def get_db_items(self):
  143         self.instances = {i['os_id']: i
  144                           for i in db_api.get_items(self.context, 'i')}
  145         self.snapshots = {s['os_id']: s
  146                           for s in db_api.get_items(self.context, 'snap')}
  147         return super(VolumeDescriber, self).get_db_items()
  148 
  149     def get_os_items(self):
  150         nova = clients.nova(ec2_context.get_os_admin_context())
  151         os_instances = nova.servers.list(
  152             search_opts={'all_tenants': True,
  153                          'project_id': self.context.project_id})
  154         self.os_instances = {i.id: i for i in os_instances}
  155         return clients.cinder(self.context).volumes.list()
  156 
  157     def get_name(self, os_item):
  158         return ''
  159 
  160 
  161 def describe_volumes(context, volume_id=None, filter=None,
  162                      max_results=None, next_token=None):
  163     if volume_id and max_results:
  164         msg = _('The parameter volumeSet cannot be used with the parameter '
  165                 'maxResults')
  166         raise exception.InvalidParameterCombination(msg)
  167 
  168     volume_describer = VolumeDescriber()
  169     formatted_volumes = volume_describer.describe(
  170         context, ids=volume_id, filter=filter,
  171         max_results=max_results, next_token=next_token)
  172     result = {'volumeSet': formatted_volumes}
  173     if volume_describer.next_token:
  174         result['nextToken'] = volume_describer.next_token
  175     return result
  176 
  177 
  178 def _format_volume(context, volume, os_volume, instances={}, os_instances={},
  179                    snapshots={}, snapshot_id=None):
  180     valid_ec2_api_volume_status_map = {
  181         'reserved': 'in-use',
  182         'attaching': 'in-use',
  183         'detaching': 'in-use'}
  184 
  185     ec2_volume = {
  186             'volumeId': volume['id'],
  187             'status': valid_ec2_api_volume_status_map.get(os_volume.status,
  188                                                           os_volume.status),
  189             'size': os_volume.size,
  190             'availabilityZone': os_volume.availability_zone,
  191             'createTime': os_volume.created_at,
  192             'volumeType': os_volume.volume_type,
  193             'encrypted': os_volume.encrypted,
  194     }
  195     if ec2_volume['status'] == 'in-use':
  196         ec2_volume['attachmentSet'] = (
  197                 [_format_attachment(context, volume, os_volume, instances,
  198                                     os_instances)])
  199     else:
  200         ec2_volume['attachmentSet'] = {}
  201     if snapshot_id is None and os_volume.snapshot_id:
  202         snapshot = ec2utils.get_db_item_by_os_id(
  203                 context, 'snap', os_volume.snapshot_id, snapshots)
  204         snapshot_id = snapshot['id']
  205     ec2_volume['snapshotId'] = snapshot_id
  206 
  207     return ec2_volume
  208 
  209 
  210 def _format_attachment(context, volume, os_volume, instances={},
  211                        os_instances={}, instance_id=None):
  212     os_attachment = next(iter(os_volume.attachments), {})
  213     os_instance_id = os_attachment.get('server_id')
  214     if not instance_id and os_instance_id:
  215         instance = ec2utils.get_db_item_by_os_id(
  216                 context, 'i', os_instance_id, instances)
  217         instance_id = instance['id']
  218     status = os_volume.status
  219     if status == 'reserved':
  220         status = 'attaching'
  221     ec2_attachment = {
  222             'device': os_attachment.get('device'),
  223             'instanceId': instance_id,
  224             'status': (status
  225                        if status in ('attaching', 'detaching') else
  226                        'attached' if os_attachment else 'detached'),
  227             'volumeId': volume['id']}
  228     if os_instance_id in os_instances:
  229         os_instance = os_instances[os_instance_id]
  230         volumes_attached = getattr(os_instance,
  231                                    'os-extended-volumes:volumes_attached', [])
  232         volume_attached = next((va for va in volumes_attached
  233                                 if va['id'] == volume['os_id']), None)
  234         if volume_attached and 'delete_on_termination' in volume_attached:
  235             ec2_attachment['deleteOnTermination'] = (
  236                 volume_attached['delete_on_termination'])
  237     return ec2_attachment