1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright 2010 United States Government as represented by the
4
# Administrator of the National Aeronautics and Space Administration.
7
# Licensed under the Apache License, Version 2.0 (the "License"); you may
8
# not use this file except in compliance with the License. You may obtain
9
# a copy of the License at
11
# http://www.apache.org/licenses/LICENSE-2.0
13
# Unless required by applicable law or agreed to in writing, software
14
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16
# License for the specific language governing permissions and limitations
20
Cloud Controller: Implementation of EC2 REST API calls, which are
21
dispatched to other nodes via AMQP RPC. State is via distributed
30
from nova.api.ec2 import ec2utils
31
from nova.api.ec2 import inst_state
32
from nova.api import validator
33
from nova import block_device
34
from nova import compute
35
from nova.compute import instance_types
36
from nova.compute import vm_states
37
from nova import crypto
39
from nova import exception
40
from nova import flags
41
from nova.image import s3
42
from nova import log as logging
43
from nova import network
44
from nova.rpc import common as rpc_common
45
from nova import quota
46
from nova import utils
47
from nova import volume
51
flags.DECLARE('dhcp_domain', 'nova.network.manager')
53
LOG = logging.getLogger(__name__)
56
def validate_ec2_id(val):
57
if not validator.validate_str()(val):
58
raise exception.InvalidInstanceIDMalformed(val)
60
ec2utils.ec2_id_to_id(val)
61
except exception.InvalidEc2Id:
62
raise exception.InvalidInstanceIDMalformed(val)
65
def _gen_key(context, user_id, key_name):
68
This is a module level method because it is slow and we need to defer
69
it into a process pool."""
70
# NOTE(vish): generating key pair is slow so check for legal
71
# creation before creating key_pair
73
db.key_pair_get(context, user_id, key_name)
74
raise exception.KeyPairExists(key_name=key_name)
75
except exception.NotFound:
77
private_key, public_key, fingerprint = crypto.generate_key_pair()
79
key['user_id'] = user_id
80
key['name'] = key_name
81
key['public_key'] = public_key
82
key['fingerprint'] = fingerprint
83
db.key_pair_create(context, key)
84
return {'private_key': private_key, 'fingerprint': fingerprint}
87
# EC2 API can return the following values as documented in the EC2 API
88
# http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/
89
# ApiReference-ItemType-InstanceStateType.html
90
# pending 0 | running 16 | shutting-down 32 | terminated 48 | stopping 64 |
92
_STATE_DESCRIPTION_MAP = {
93
None: inst_state.PENDING,
94
vm_states.ACTIVE: inst_state.RUNNING,
95
vm_states.BUILDING: inst_state.PENDING,
96
vm_states.REBUILDING: inst_state.PENDING,
97
vm_states.DELETED: inst_state.TERMINATED,
98
vm_states.SOFT_DELETE: inst_state.TERMINATED,
99
vm_states.STOPPED: inst_state.STOPPED,
100
vm_states.SHUTOFF: inst_state.SHUTOFF,
101
vm_states.MIGRATING: inst_state.MIGRATE,
102
vm_states.RESIZING: inst_state.RESIZE,
103
vm_states.PAUSED: inst_state.PAUSE,
104
vm_states.SUSPENDED: inst_state.SUSPEND,
105
vm_states.RESCUED: inst_state.RESCUE,
109
def _state_description(vm_state, shutdown_terminate):
110
"""Map the vm state to the server status string"""
111
if (vm_state == vm_states.SHUTOFF and
112
not shutdown_terminate):
113
name = inst_state.STOPPED
115
name = _STATE_DESCRIPTION_MAP.get(vm_state, vm_state)
117
return {'code': inst_state.name_to_code(name),
121
def _parse_block_device_mapping(bdm):
122
"""Parse BlockDeviceMappingItemType into flat hash
123
BlockDevicedMapping.<N>.DeviceName
124
BlockDevicedMapping.<N>.Ebs.SnapshotId
125
BlockDevicedMapping.<N>.Ebs.VolumeSize
126
BlockDevicedMapping.<N>.Ebs.DeleteOnTermination
127
BlockDevicedMapping.<N>.Ebs.NoDevice
128
BlockDevicedMapping.<N>.VirtualName
129
=> remove .Ebs and allow volume id in SnapshotId
131
ebs = bdm.pop('ebs', None)
133
ec2_id = ebs.pop('snapshot_id', None)
135
id = ec2utils.ec2_id_to_id(ec2_id)
136
if ec2_id.startswith('snap-'):
137
bdm['snapshot_id'] = id
138
elif ec2_id.startswith('vol-'):
139
bdm['volume_id'] = id
140
ebs.setdefault('delete_on_termination', True)
145
def _properties_get_mappings(properties):
146
return block_device.mappings_prepend_dev(properties.get('mappings', []))
149
def _format_block_device_mapping(bdm):
150
"""Contruct BlockDeviceMappingItemType
151
{'device_name': '...', 'snapshot_id': , ...}
152
=> BlockDeviceMappingItemType
154
keys = (('deviceName', 'device_name'),
155
('virtualName', 'virtual_name'))
160
if bdm.get('no_device'):
161
item['noDevice'] = True
162
if ('snapshot_id' in bdm) or ('volume_id' in bdm):
163
ebs_keys = (('snapshotId', 'snapshot_id'),
164
('snapshotId', 'volume_id'), # snapshotId is abused
165
('volumeSize', 'volume_size'),
166
('deleteOnTermination', 'delete_on_termination'))
168
for name, k in ebs_keys:
170
if k == 'snapshot_id':
171
ebs[name] = ec2utils.id_to_ec2_snap_id(bdm[k])
172
elif k == 'volume_id':
173
ebs[name] = ec2utils.id_to_ec2_vol_id(bdm[k])
176
assert 'snapshotId' in ebs
181
def _format_mappings(properties, result):
182
"""Format multiple BlockDeviceMappingItemType"""
183
mappings = [{'virtualName': m['virtual'], 'deviceName': m['device']}
184
for m in _properties_get_mappings(properties)
185
if block_device.is_swap_or_ephemeral(m['virtual'])]
187
block_device_mapping = [_format_block_device_mapping(bdm) for bdm in
188
properties.get('block_device_mapping', [])]
190
# NOTE(yamahata): overwrite mappings with block_device_mapping
191
for bdm in block_device_mapping:
192
for i in range(len(mappings)):
193
if bdm['deviceName'] == mappings[i]['deviceName']:
198
# NOTE(yamahata): trim ebs.no_device == true. Is this necessary?
199
mappings = [bdm for bdm in mappings if not (bdm.get('noDevice', False))]
202
result['blockDeviceMapping'] = mappings
205
class CloudController(object):
206
""" CloudController provides the critical dispatch between
207
inbound API calls through the endpoint and messages
208
sent to the other nodes.
211
self.image_service = s3.S3ImageService()
212
self.network_api = network.API()
213
self.volume_api = volume.API()
214
self.compute_api = compute.API(network_api=self.network_api,
215
volume_api=self.volume_api)
216
self.sgh = utils.import_object(FLAGS.security_group_handler)
219
return 'CloudController'
221
def _get_image_state(self, image):
222
# NOTE(vish): fallback status if image_state isn't set
223
state = image.get('status')
224
if state == 'active':
226
return image['properties'].get('image_state', state)
228
def describe_availability_zones(self, context, **kwargs):
229
if ('zone_name' in kwargs and
230
'verbose' in kwargs['zone_name'] and
232
return self._describe_availability_zones_verbose(context,
235
return self._describe_availability_zones(context, **kwargs)
237
def _describe_availability_zones(self, context, **kwargs):
238
ctxt = context.elevated()
239
enabled_services = db.service_get_all(ctxt, False)
240
disabled_services = db.service_get_all(ctxt, True)
242
for zone in [service.availability_zone for service
243
in enabled_services]:
244
if not zone in available_zones:
245
available_zones.append(zone)
246
not_available_zones = []
247
for zone in [service.availability_zone for service in disabled_services
248
if not service['availability_zone'] in available_zones]:
249
if not zone in not_available_zones:
250
not_available_zones.append(zone)
252
for zone in available_zones:
253
result.append({'zoneName': zone,
254
'zoneState': "available"})
255
for zone in not_available_zones:
256
result.append({'zoneName': zone,
257
'zoneState': "not available"})
258
return {'availabilityZoneInfo': result}
260
def _describe_availability_zones_verbose(self, context, **kwargs):
261
rv = {'availabilityZoneInfo': [{'zoneName': 'nova',
262
'zoneState': 'available'}]}
264
services = db.service_get_all(context, False)
266
for host in [service['host'] for service in services]:
267
if not host in hosts:
270
rv['availabilityZoneInfo'].append({'zoneName': '|- %s' % host,
272
hsvcs = [service for service in services
273
if service['host'] == host]
275
alive = utils.service_is_up(svc)
276
art = (alive and ":-)") or "XXX"
280
rv['availabilityZoneInfo'].append({
281
'zoneName': '| |- %s' % svc['binary'],
282
'zoneState': '%s %s %s' % (active, art,
286
def describe_regions(self, context, region_name=None, **kwargs):
287
if FLAGS.region_list:
289
for region in FLAGS.region_list:
290
name, _sep, host = region.partition('=')
291
endpoint = '%s://%s:%s%s' % (FLAGS.ec2_scheme,
295
regions.append({'regionName': name,
296
'regionEndpoint': endpoint})
298
regions = [{'regionName': 'nova',
299
'regionEndpoint': '%s://%s:%s%s' % (FLAGS.ec2_scheme,
303
return {'regionInfo': regions}
305
def describe_snapshots(self,
313
for ec2_id in snapshot_id:
314
internal_id = ec2utils.ec2_id_to_id(ec2_id)
315
snapshot = self.volume_api.get_snapshot(
317
snapshot_id=internal_id)
318
snapshots.append(snapshot)
320
snapshots = self.volume_api.get_all_snapshots(context)
321
snapshots = [self._format_snapshot(context, s) for s in snapshots]
322
return {'snapshotSet': snapshots}
324
def _format_snapshot(self, context, snapshot):
326
s['snapshotId'] = ec2utils.id_to_ec2_snap_id(snapshot['id'])
327
s['volumeId'] = ec2utils.id_to_ec2_vol_id(snapshot['volume_id'])
328
s['status'] = snapshot['status']
329
s['startTime'] = snapshot['created_at']
330
s['progress'] = snapshot['progress']
331
s['ownerId'] = snapshot['project_id']
332
s['volumeSize'] = snapshot['volume_size']
333
s['description'] = snapshot['display_description']
336
def create_snapshot(self, context, volume_id, **kwargs):
337
validate_ec2_id(volume_id)
338
LOG.audit(_("Create snapshot of volume %s"), volume_id,
340
volume_id = ec2utils.ec2_id_to_id(volume_id)
341
volume = self.volume_api.get(context, volume_id)
342
snapshot = self.volume_api.create_snapshot(
346
kwargs.get('description'))
347
return self._format_snapshot(context, snapshot)
349
def delete_snapshot(self, context, snapshot_id, **kwargs):
350
snapshot_id = ec2utils.ec2_id_to_id(snapshot_id)
351
snapshot = self.volume_api.get_snapshot(context, snapshot_id)
352
self.volume_api.delete_snapshot(context, snapshot)
355
def describe_key_pairs(self, context, key_name=None, **kwargs):
356
key_pairs = db.key_pair_get_all_by_user(context, context.user_id)
357
if not key_name is None:
358
key_pairs = [x for x in key_pairs if x['name'] in key_name]
361
for key_pair in key_pairs:
362
# filter out the vpn keys
363
suffix = FLAGS.vpn_key_suffix
364
if context.is_admin or not key_pair['name'].endswith(suffix):
366
'keyName': key_pair['name'],
367
'keyFingerprint': key_pair['fingerprint'],
370
return {'keySet': result}
372
def create_key_pair(self, context, key_name, **kwargs):
373
if not re.match('^[a-zA-Z0-9_\- ]+$', str(key_name)):
374
err = _("Value (%s) for KeyName is invalid."
375
" Content limited to Alphanumeric character, "
376
"spaces, dashes, and underscore.") % key_name
377
raise exception.EC2APIError(err)
379
if len(str(key_name)) > 255:
380
err = _("Value (%s) for Keyname is invalid."
381
" Length exceeds maximum of 255.") % key_name
382
raise exception.EC2APIError(err)
384
LOG.audit(_("Create key pair %s"), key_name, context=context)
385
data = _gen_key(context, context.user_id, key_name)
386
return {'keyName': key_name,
387
'keyFingerprint': data['fingerprint'],
388
'keyMaterial': data['private_key']}
389
# TODO(vish): when context is no longer an object, pass it here
391
def import_key_pair(self, context, key_name, public_key_material,
393
LOG.audit(_("Import key %s"), key_name, context=context)
395
db.key_pair_get(context, context.user_id, key_name)
396
raise exception.KeyPairExists(key_name=key_name)
397
except exception.NotFound:
399
public_key = base64.b64decode(public_key_material)
400
fingerprint = crypto.generate_fingerprint(public_key)
402
key['user_id'] = context.user_id
403
key['name'] = key_name
404
key['public_key'] = public_key
405
key['fingerprint'] = fingerprint
406
db.key_pair_create(context, key)
407
return {'keyName': key_name,
408
'keyFingerprint': fingerprint}
410
def delete_key_pair(self, context, key_name, **kwargs):
411
LOG.audit(_("Delete key pair %s"), key_name, context=context)
413
db.key_pair_destroy(context, context.user_id, key_name)
414
except exception.NotFound:
415
# aws returns true even if the key doesn't exist
419
def describe_security_groups(self, context, group_name=None, group_id=None,
421
self.compute_api.ensure_default_security_group(context)
422
if group_name or group_id:
425
for name in group_name:
426
group = db.security_group_get_by_name(context,
432
group = db.security_group_get(context, gid)
434
elif context.is_admin:
435
groups = db.security_group_get_all(context)
437
groups = db.security_group_get_by_project(context,
439
groups = [self._format_security_group(context, g) for g in groups]
441
return {'securityGroupInfo':
443
key=lambda k: (k['ownerId'], k['groupName'])))}
445
def _format_security_group(self, context, group):
447
g['groupDescription'] = group.description
448
g['groupName'] = group.name
449
g['ownerId'] = group.project_id
450
g['ipPermissions'] = []
451
for rule in group.rules:
456
source_group = db.security_group_get(context, rule.group_id)
457
r['groups'] += [{'groupName': source_group.name,
458
'userId': source_group.project_id}]
460
r['ipProtocol'] = rule.protocol
461
r['fromPort'] = rule.from_port
462
r['toPort'] = rule.to_port
463
g['ipPermissions'] += [dict(r)]
465
for protocol, min_port, max_port in (('icmp', -1, -1),
468
r['ipProtocol'] = protocol
469
r['fromPort'] = min_port
470
r['toPort'] = max_port
471
g['ipPermissions'] += [dict(r)]
473
r['ipProtocol'] = rule.protocol
474
r['fromPort'] = rule.from_port
475
r['toPort'] = rule.to_port
476
r['ipRanges'] += [{'cidrIp': rule.cidr}]
477
g['ipPermissions'] += [r]
480
def _rule_args_to_dict(self, context, kwargs):
482
if not 'groups' in kwargs and not 'ip_ranges' in kwargs:
483
rule = self._rule_dict_last_step(context, **kwargs)
487
if 'ip_ranges' in kwargs:
488
rules = self._cidr_args_split(kwargs)
494
groups_values = self._groups_args_split(rule)
495
for groups_value in groups_values:
496
final = self._rule_dict_last_step(context, **groups_value)
497
finalset.append(final)
499
final = self._rule_dict_last_step(context, **rule)
500
finalset.append(final)
503
def _cidr_args_split(self, kwargs):
505
cidrs = kwargs['ip_ranges']
506
for key, cidr in cidrs.iteritems():
507
mykwargs = kwargs.copy()
508
del mykwargs['ip_ranges']
509
mykwargs['cidr_ip'] = cidr['cidr_ip']
510
cidr_args_split.append(mykwargs)
511
return cidr_args_split
513
def _groups_args_split(self, kwargs):
514
groups_args_split = []
515
groups = kwargs['groups']
516
for key, group in groups.iteritems():
517
mykwargs = kwargs.copy()
518
del mykwargs['groups']
519
if 'group_name' in group:
520
mykwargs['source_security_group_name'] = group['group_name']
521
if 'user_id' in group:
522
mykwargs['source_security_group_owner_id'] = group['user_id']
523
if 'group_id' in group:
524
mykwargs['source_security_group_id'] = group['group_id']
525
groups_args_split.append(mykwargs)
526
return groups_args_split
528
def _rule_dict_last_step(self, context, to_port=None, from_port=None,
529
ip_protocol=None, cidr_ip=None, user_id=None,
530
source_security_group_name=None,
531
source_security_group_owner_id=None):
535
if source_security_group_name:
536
source_project_id = self._get_source_project_id(context,
537
source_security_group_owner_id)
539
source_security_group = db.security_group_get_by_name(
542
source_security_group_name)
543
notfound = exception.SecurityGroupNotFound
544
if not source_security_group:
545
raise notfound(security_group_id=source_security_group_name)
546
values['group_id'] = source_security_group['id']
548
# If this fails, it throws an exception. This is what we want.
549
cidr_ip = urllib.unquote(cidr_ip).decode()
551
if not utils.is_valid_cidr(cidr_ip):
552
# Raise exception for non-valid address
553
raise exception.EC2APIError(_("Invalid CIDR"))
555
values['cidr'] = cidr_ip
557
values['cidr'] = '0.0.0.0/0'
559
if source_security_group_name:
560
# Open everything if an explicit port range or type/code are not
561
# specified, but only if a source group was specified.
562
ip_proto_upper = ip_protocol.upper() if ip_protocol else ''
563
if (ip_proto_upper == 'ICMP' and
564
from_port is None and to_port is None):
567
elif (ip_proto_upper in ['TCP', 'UDP'] and from_port is None
568
and to_port is None):
572
if ip_protocol and from_port is not None and to_port is not None:
574
ip_protocol = str(ip_protocol)
576
# Verify integer conversions
577
from_port = int(from_port)
578
to_port = int(to_port)
580
if ip_protocol.upper() == 'ICMP':
581
raise exception.InvalidInput(reason="Type and"
582
" Code must be integers for ICMP protocol type")
584
raise exception.InvalidInput(reason="To and From ports "
587
if ip_protocol.upper() not in ['TCP', 'UDP', 'ICMP']:
588
raise exception.InvalidIpProtocol(protocol=ip_protocol)
590
# Verify that from_port must always be less than
591
# or equal to to_port
592
if (ip_protocol.upper() in ['TCP', 'UDP'] and
593
(from_port > to_port)):
594
raise exception.InvalidPortRange(from_port=from_port,
595
to_port=to_port, msg="Former value cannot"
596
" be greater than the later")
598
# Verify valid TCP, UDP port ranges
599
if (ip_protocol.upper() in ['TCP', 'UDP'] and
600
(from_port < 1 or to_port > 65535)):
601
raise exception.InvalidPortRange(from_port=from_port,
602
to_port=to_port, msg="Valid TCP ports should"
603
" be between 1-65535")
605
# Verify ICMP type and code
606
if (ip_protocol.upper() == "ICMP" and
607
(from_port < -1 or from_port > 255 or
608
to_port < -1 or to_port > 255)):
609
raise exception.InvalidPortRange(from_port=from_port,
610
to_port=to_port, msg="For ICMP, the"
611
" type:code must be valid")
613
values['protocol'] = ip_protocol
614
values['from_port'] = from_port
615
values['to_port'] = to_port
617
# If cidr based filtering, protocol and ports are mandatory
623
def _security_group_rule_exists(self, security_group, values):
624
"""Indicates whether the specified rule values are already
625
defined in the given security group.
627
for rule in security_group.rules:
629
keys = ('group_id', 'cidr', 'from_port', 'to_port', 'protocol')
631
if rule.get(key) != values.get(key):
638
def revoke_security_group_ingress(self, context, group_name=None,
639
group_id=None, **kwargs):
640
if not group_name and not group_id:
641
err = _("Not enough parameters, need group_name or group_id")
642
raise exception.EC2APIError(err)
643
self.compute_api.ensure_default_security_group(context)
644
notfound = exception.SecurityGroupNotFound
646
security_group = db.security_group_get_by_name(context,
649
if not security_group:
650
raise notfound(security_group_id=group_name)
652
security_group = db.security_group_get(context, group_id)
653
if not security_group:
654
raise notfound(security_group_id=group_id)
656
msg = _("Revoke security group ingress %s")
657
LOG.audit(msg, security_group['name'], context=context)
660
prevalues = kwargs['ip_permissions']
662
prevalues.append(kwargs)
665
for values in prevalues:
666
rulesvalues = self._rule_args_to_dict(context, values)
668
err = _("%s Not enough parameters to build a valid rule")
669
raise exception.EC2APIError(err % rulesvalues)
671
for values_for_rule in rulesvalues:
672
values_for_rule['parent_group_id'] = security_group.id
673
rule_id = self._security_group_rule_exists(security_group,
676
db.security_group_rule_destroy(context, rule_id)
677
rule_ids.append(rule_id)
679
# NOTE(vish): we removed a rule, so refresh
680
self.compute_api.trigger_security_group_rules_refresh(
682
security_group_id=security_group['id'])
683
self.sgh.trigger_security_group_rule_destroy_refresh(
686
raise exception.EC2APIError(_("No rule for the specified parameters."))
688
# TODO(soren): This has only been tested with Boto as the client.
689
# Unfortunately, it seems Boto is using an old API
690
# for these operations, so support for newer API versions
692
def authorize_security_group_ingress(self, context, group_name=None,
693
group_id=None, **kwargs):
694
if not group_name and not group_id:
695
err = _("Not enough parameters, need group_name or group_id")
696
raise exception.EC2APIError(err)
697
self.compute_api.ensure_default_security_group(context)
698
notfound = exception.SecurityGroupNotFound
700
security_group = db.security_group_get_by_name(context,
703
if not security_group:
704
raise notfound(security_group_id=group_name)
706
security_group = db.security_group_get(context, group_id)
707
if not security_group:
708
raise notfound(security_group_id=group_id)
710
msg = _("Authorize security group ingress %s")
711
LOG.audit(msg, security_group['name'], context=context)
714
prevalues = kwargs['ip_permissions']
716
prevalues.append(kwargs)
718
for values in prevalues:
719
rulesvalues = self._rule_args_to_dict(context, values)
721
err = _("%s Not enough parameters to build a valid rule")
722
raise exception.EC2APIError(err % rulesvalues)
723
for values_for_rule in rulesvalues:
724
values_for_rule['parent_group_id'] = security_group.id
725
if self._security_group_rule_exists(security_group,
727
err = _('%s - This rule already exists in group')
728
raise exception.EC2APIError(err % values_for_rule)
729
postvalues.append(values_for_rule)
731
allowed = quota.allowed_security_group_rules(context,
732
security_group['id'],
735
msg = _("Quota exceeded, too many security group rules.")
736
raise exception.EC2APIError(msg)
739
for values_for_rule in postvalues:
740
security_group_rule = db.security_group_rule_create(
743
rule_ids.append(security_group_rule['id'])
746
self.compute_api.trigger_security_group_rules_refresh(
748
security_group_id=security_group['id'])
749
self.sgh.trigger_security_group_rule_create_refresh(
753
raise exception.EC2APIError(_("No rule for the specified parameters."))
755
def _get_source_project_id(self, context, source_security_group_owner_id):
756
if source_security_group_owner_id:
757
# Parse user:project for source group.
758
source_parts = source_security_group_owner_id.split(':')
760
# If no project name specified, assume it's same as user name.
761
# Since we're looking up by project name, the user name is not
762
# used here. It's only read for EC2 API compatibility.
763
if len(source_parts) == 2:
764
source_project_id = source_parts[1]
766
source_project_id = source_parts[0]
768
source_project_id = context.project_id
770
return source_project_id
772
def create_security_group(self, context, group_name, group_description):
773
if not re.match('^[a-zA-Z0-9_\- ]+$', str(group_name)):
774
# Some validation to ensure that values match API spec.
775
# - Alphanumeric characters, spaces, dashes, and underscores.
776
# TODO(Daviey): LP: #813685 extend beyond group_name checking, and
777
# probably create a param validator that can be used elsewhere.
778
err = _("Value (%s) for parameter GroupName is invalid."
779
" Content limited to Alphanumeric characters, "
780
"spaces, dashes, and underscores.") % group_name
781
# err not that of master ec2 implementation, as they fail to raise.
782
raise exception.InvalidParameterValue(err=err)
784
if len(str(group_name)) > 255:
785
err = _("Value (%s) for parameter GroupName is invalid."
786
" Length exceeds maximum of 255.") % group_name
787
raise exception.InvalidParameterValue(err=err)
789
LOG.audit(_("Create Security Group %s"), group_name, context=context)
790
self.compute_api.ensure_default_security_group(context)
791
if db.security_group_exists(context, context.project_id, group_name):
792
msg = _('group %s already exists')
793
raise exception.EC2APIError(msg % group_name)
795
if quota.allowed_security_groups(context, 1) < 1:
796
msg = _("Quota exceeded, too many security groups.")
797
raise exception.EC2APIError(msg)
799
group = {'user_id': context.user_id,
800
'project_id': context.project_id,
802
'description': group_description}
803
group_ref = db.security_group_create(context, group)
805
self.sgh.trigger_security_group_create_refresh(context, group)
807
return {'securityGroupSet': [self._format_security_group(context,
810
def delete_security_group(self, context, group_name=None, group_id=None,
812
if not group_name and not group_id:
813
err = _("Not enough parameters, need group_name or group_id")
814
raise exception.EC2APIError(err)
815
notfound = exception.SecurityGroupNotFound
817
security_group = db.security_group_get_by_name(context,
820
if not security_group:
821
raise notfound(security_group_id=group_name)
823
security_group = db.security_group_get(context, group_id)
824
if not security_group:
825
raise notfound(security_group_id=group_id)
826
if db.security_group_in_use(context, security_group.id):
827
raise exception.InvalidGroup(reason="In Use")
828
LOG.audit(_("Delete security group %s"), group_name, context=context)
829
db.security_group_destroy(context, security_group.id)
831
self.sgh.trigger_security_group_destroy_refresh(context,
835
def get_console_output(self, context, instance_id, **kwargs):
836
LOG.audit(_("Get console output for instance %s"), instance_id,
838
# instance_id may be passed in as a list of instances
839
if isinstance(instance_id, list):
840
ec2_id = instance_id[0]
843
validate_ec2_id(ec2_id)
844
instance_id = ec2utils.ec2_id_to_id(ec2_id)
845
instance = self.compute_api.get(context, instance_id)
846
output = self.compute_api.get_console_output(context, instance)
848
return {"InstanceId": ec2_id,
850
"output": base64.b64encode(output)}
852
def describe_volumes(self, context, volume_id=None, **kwargs):
855
for ec2_id in volume_id:
856
validate_ec2_id(ec2_id)
857
internal_id = ec2utils.ec2_id_to_id(ec2_id)
858
volume = self.volume_api.get(context, internal_id)
859
volumes.append(volume)
861
volumes = self.volume_api.get_all(context)
862
volumes = [self._format_volume(context, v) for v in volumes]
863
return {'volumeSet': volumes}
865
def _format_volume(self, context, volume):
866
instance_ec2_id = None
868
if volume.get('instance', None):
869
instance_id = volume['instance']['id']
870
instance_ec2_id = ec2utils.id_to_ec2_id(instance_id)
871
instance_data = '%s[%s]' % (instance_ec2_id,
872
volume['instance']['host'])
874
v['volumeId'] = ec2utils.id_to_ec2_vol_id(volume['id'])
875
v['status'] = volume['status']
876
v['size'] = volume['size']
877
v['availabilityZone'] = volume['availability_zone']
878
v['createTime'] = volume['created_at']
880
v['status'] = '%s (%s, %s, %s, %s)' % (
882
volume['project_id'],
885
volume['mountpoint'])
886
if volume['attach_status'] == 'attached':
887
v['attachmentSet'] = [{'attachTime': volume['attach_time'],
888
'deleteOnTermination': False,
889
'device': volume['mountpoint'],
890
'instanceId': instance_ec2_id,
891
'status': 'attached',
892
'volumeId': v['volumeId']}]
894
v['attachmentSet'] = [{}]
895
if volume.get('snapshot_id') is not None:
896
v['snapshotId'] = ec2utils.id_to_ec2_snap_id(volume['snapshot_id'])
898
v['snapshotId'] = None
902
def create_volume(self, context, **kwargs):
903
size = kwargs.get('size')
904
if kwargs.get('snapshot_id') is not None:
905
snapshot_id = ec2utils.ec2_id_to_id(kwargs['snapshot_id'])
906
snapshot = self.volume_api.get_snapshot(context, snapshot_id)
907
LOG.audit(_("Create volume from snapshot %s"), snapshot_id,
911
LOG.audit(_("Create volume of %s GB"), size, context=context)
913
availability_zone = kwargs.get('availability_zone', None)
915
volume = self.volume_api.create(context,
920
availability_zone=availability_zone)
921
# TODO(vish): Instance should be None at db layer instead of
922
# trying to lazy load, but for now we turn it into
923
# a dict to avoid an error.
924
return self._format_volume(context, dict(volume))
926
def delete_volume(self, context, volume_id, **kwargs):
927
validate_ec2_id(volume_id)
928
volume_id = ec2utils.ec2_id_to_id(volume_id)
931
volume = self.volume_api.get(context, volume_id)
932
self.volume_api.delete(context, volume)
933
except exception.InvalidVolume:
934
raise exception.EC2APIError(_('Delete Failed'))
938
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
939
validate_ec2_id(instance_id)
940
validate_ec2_id(volume_id)
941
volume_id = ec2utils.ec2_id_to_id(volume_id)
942
instance_id = ec2utils.ec2_id_to_id(instance_id)
943
instance = self.compute_api.get(context, instance_id)
944
msg = _("Attach volume %(volume_id)s to instance %(instance_id)s"
945
" at %(device)s") % locals()
946
LOG.audit(msg, context=context)
949
self.compute_api.attach_volume(context, instance,
951
except exception.InvalidVolume:
952
raise exception.EC2APIError(_('Attach Failed.'))
954
volume = self.volume_api.get(context, volume_id)
955
return {'attachTime': volume['attach_time'],
956
'device': volume['mountpoint'],
957
'instanceId': ec2utils.id_to_ec2_id(instance_id),
958
'requestId': context.request_id,
959
'status': volume['attach_status'],
960
'volumeId': ec2utils.id_to_ec2_vol_id(volume_id)}
962
def detach_volume(self, context, volume_id, **kwargs):
963
validate_ec2_id(volume_id)
964
volume_id = ec2utils.ec2_id_to_id(volume_id)
965
LOG.audit(_("Detach volume %s"), volume_id, context=context)
966
volume = self.volume_api.get(context, volume_id)
969
instance = self.compute_api.detach_volume(context,
971
except exception.InvalidVolume:
972
raise exception.EC2APIError(_('Detach Volume Failed.'))
974
return {'attachTime': volume['attach_time'],
975
'device': volume['mountpoint'],
976
'instanceId': ec2utils.id_to_ec2_id(instance['id']),
977
'requestId': context.request_id,
978
'status': volume['attach_status'],
979
'volumeId': ec2utils.id_to_ec2_vol_id(volume_id)}
981
def _format_kernel_id(self, context, instance_ref, result, key):
982
kernel_uuid = instance_ref['kernel_id']
983
if kernel_uuid is None or kernel_uuid == '':
985
result[key] = ec2utils.glance_id_to_ec2_id(context, kernel_uuid, 'aki')
987
def _format_ramdisk_id(self, context, instance_ref, result, key):
988
ramdisk_uuid = instance_ref['ramdisk_id']
989
if ramdisk_uuid is None or ramdisk_uuid == '':
991
result[key] = ec2utils.glance_id_to_ec2_id(context, ramdisk_uuid,
994
def describe_instance_attribute(self, context, instance_id, attribute,
996
def _unsupported_attribute(instance, result):
997
raise exception.EC2APIError(_('attribute not supported: %s') %
1000
def _format_attr_block_device_mapping(instance, result):
1002
self._format_instance_root_device_name(instance, tmp)
1003
self._format_instance_bdm(context, instance_id,
1004
tmp['rootDeviceName'], result)
1006
def _format_attr_disable_api_termination(instance, result):
1007
result['disableApiTermination'] = instance['disable_terminate']
1009
def _format_attr_group_set(instance, result):
1010
CloudController._format_group_set(instance, result)
1012
def _format_attr_instance_initiated_shutdown_behavior(instance,
1014
if instance['shutdown_terminate']:
1015
result['instanceInitiatedShutdownBehavior'] = 'terminate'
1017
result['instanceInitiatedShutdownBehavior'] = 'stop'
1019
def _format_attr_instance_type(instance, result):
1020
self._format_instance_type(instance, result)
1022
def _format_attr_kernel(instance, result):
1023
self._format_kernel_id(context, instance, result, 'kernel')
1025
def _format_attr_ramdisk(instance, result):
1026
self._format_ramdisk_id(context, instance, result, 'ramdisk')
1028
def _format_attr_root_device_name(instance, result):
1029
self._format_instance_root_device_name(instance, result)
1031
def _format_attr_source_dest_check(instance, result):
1032
_unsupported_attribute(instance, result)
1034
def _format_attr_user_data(instance, result):
1035
result['userData'] = base64.b64decode(instance['user_data'])
1037
attribute_formatter = {
1038
'blockDeviceMapping': _format_attr_block_device_mapping,
1039
'disableApiTermination': _format_attr_disable_api_termination,
1040
'groupSet': _format_attr_group_set,
1041
'instanceInitiatedShutdownBehavior':
1042
_format_attr_instance_initiated_shutdown_behavior,
1043
'instanceType': _format_attr_instance_type,
1044
'kernel': _format_attr_kernel,
1045
'ramdisk': _format_attr_ramdisk,
1046
'rootDeviceName': _format_attr_root_device_name,
1047
'sourceDestCheck': _format_attr_source_dest_check,
1048
'userData': _format_attr_user_data,
1051
fn = attribute_formatter.get(attribute)
1053
raise exception.EC2APIError(
1054
_('attribute not supported: %s') % attribute)
1056
ec2_instance_id = instance_id
1057
validate_ec2_id(instance_id)
1058
instance_id = ec2utils.ec2_id_to_id(ec2_instance_id)
1059
instance = self.compute_api.get(context, instance_id)
1060
result = {'instance_id': ec2_instance_id}
1061
fn(instance, result)
1064
def describe_instances(self, context, **kwargs):
1065
# Optional DescribeInstances argument
1066
instance_id = kwargs.get('instance_id', None)
1067
return self._format_describe_instances(context,
1068
instance_id=instance_id)
1070
def describe_instances_v6(self, context, **kwargs):
1071
# Optional DescribeInstancesV6 argument
1072
instance_id = kwargs.get('instance_id', None)
1073
return self._format_describe_instances(context,
1074
instance_id=instance_id, use_v6=True)
1076
def _format_describe_instances(self, context, **kwargs):
1077
return {'reservationSet': self._format_instances(context, **kwargs)}
1079
def _format_run_instances(self, context, reservation_id):
1080
i = self._format_instances(context, reservation_id=reservation_id)
1084
def _format_terminate_instances(self, context, instance_id,
1087
for (ec2_id, previous_state) in zip(instance_id, previous_states):
1089
i['instanceId'] = ec2_id
1090
i['previousState'] = _state_description(previous_state['vm_state'],
1091
previous_state['shutdown_terminate'])
1093
internal_id = ec2utils.ec2_id_to_id(ec2_id)
1094
instance = self.compute_api.get(context, internal_id)
1095
i['shutdownState'] = _state_description(instance['vm_state'],
1096
instance['shutdown_terminate'])
1097
except exception.NotFound:
1098
i['shutdownState'] = _state_description(vm_states.DELETED,
1100
instances_set.append(i)
1101
return {'instancesSet': instances_set}
1103
def _format_instance_bdm(self, context, instance_id, root_device_name,
1105
"""Format InstanceBlockDeviceMappingResponseItemType"""
1106
root_device_type = 'instance-store'
1108
for bdm in db.block_device_mapping_get_all_by_instance(context,
1110
volume_id = bdm['volume_id']
1111
if (volume_id is None or bdm['no_device']):
1114
if (bdm['device_name'] == root_device_name and
1115
(bdm['snapshot_id'] or bdm['volume_id'])):
1116
assert not bdm['virtual_name']
1117
root_device_type = 'ebs'
1119
vol = self.volume_api.get(context, volume_id)
1120
LOG.debug(_("vol = %s\n"), vol)
1121
# TODO(yamahata): volume attach time
1122
ebs = {'volumeId': volume_id,
1123
'deleteOnTermination': bdm['delete_on_termination'],
1124
'attachTime': vol['attach_time'] or '-',
1125
'status': vol['status'], }
1126
res = {'deviceName': bdm['device_name'],
1131
result['blockDeviceMapping'] = mapping
1132
result['rootDeviceType'] = root_device_type
1135
def _format_instance_root_device_name(instance, result):
1136
result['rootDeviceName'] = (instance.get('root_device_name') or
1137
block_device.DEFAULT_ROOT_DEV_NAME)
1140
def _format_instance_type(instance, result):
1141
if instance['instance_type']:
1142
result['instanceType'] = instance['instance_type'].get('name')
1144
result['instanceType'] = None
1147
def _format_group_set(instance, result):
1148
security_group_names = []
1149
if instance.get('security_groups'):
1150
for security_group in instance['security_groups']:
1151
security_group_names.append(security_group['name'])
1152
result['groupSet'] = utils.convert_to_list_dict(
1153
security_group_names, 'groupId')
1155
def _format_instances(self, context, instance_id=None, use_v6=False,
1157
# TODO(termie): this method is poorly named as its name does not imply
1158
# that it will be making a variety of database calls
1159
# rather than simply formatting a bunch of instances that
1162
# NOTE(vish): instance_id is an optional list of ids to filter by
1165
for ec2_id in instance_id:
1166
internal_id = ec2utils.ec2_id_to_id(ec2_id)
1168
instance = self.compute_api.get(context, internal_id)
1169
except exception.NotFound:
1171
instances.append(instance)
1174
# always filter out deleted instances
1175
search_opts['deleted'] = False
1176
instances = self.compute_api.get_all(context,
1177
search_opts=search_opts,
1179
except exception.NotFound:
1181
for instance in instances:
1182
if not context.is_admin:
1183
if instance['image_ref'] == str(FLAGS.vpn_image_id):
1186
instance_id = instance['id']
1187
ec2_id = ec2utils.id_to_ec2_id(instance_id)
1188
i['instanceId'] = ec2_id
1189
image_uuid = instance['image_ref']
1190
i['imageId'] = ec2utils.glance_id_to_ec2_id(context, image_uuid)
1191
self._format_kernel_id(context, instance, i, 'kernelId')
1192
self._format_ramdisk_id(context, instance, i, 'ramdiskId')
1193
i['instanceState'] = _state_description(
1194
instance['vm_state'], instance['shutdown_terminate'])
1198
ip_info = ec2utils.get_ip_info_for_instance(context, instance)
1199
if ip_info['fixed_ips']:
1200
fixed_ip = ip_info['fixed_ips'][0]
1201
if ip_info['floating_ips']:
1202
floating_ip = ip_info['floating_ips'][0]
1203
if ip_info['fixed_ip6s']:
1204
i['dnsNameV6'] = ip_info['fixed_ip6s'][0]
1205
if FLAGS.ec2_private_dns_show_ip:
1206
i['privateDnsName'] = fixed_ip
1208
i['privateDnsName'] = instance['hostname']
1209
i['privateIpAddress'] = fixed_ip
1210
i['publicDnsName'] = floating_ip
1211
i['ipAddress'] = floating_ip or fixed_ip
1212
i['dnsName'] = i['publicDnsName'] or i['privateDnsName']
1213
i['keyName'] = instance['key_name']
1215
if context.is_admin:
1216
i['keyName'] = '%s (%s, %s)' % (i['keyName'],
1217
instance['project_id'],
1219
i['productCodesSet'] = utils.convert_to_list_dict([],
1221
self._format_instance_type(instance, i)
1222
i['launchTime'] = instance['created_at']
1223
i['amiLaunchIndex'] = instance['launch_index']
1224
self._format_instance_root_device_name(instance, i)
1225
self._format_instance_bdm(context, instance_id,
1226
i['rootDeviceName'], i)
1227
host = instance['host']
1228
services = db.service_get_all_by_host(context.elevated(), host)
1229
zone = ec2utils.get_availability_zone_by_host(services, host)
1230
i['placement'] = {'availabilityZone': zone}
1231
if instance['reservation_id'] not in reservations:
1233
r['reservationId'] = instance['reservation_id']
1234
r['ownerId'] = instance['project_id']
1235
self._format_group_set(instance, r)
1236
r['instancesSet'] = []
1237
reservations[instance['reservation_id']] = r
1238
reservations[instance['reservation_id']]['instancesSet'].append(i)
1240
return list(reservations.values())
1242
def describe_addresses(self, context, **kwargs):
1243
return self.format_addresses(context)
1245
def format_addresses(self, context):
1247
floaters = self.network_api.get_floating_ips_by_project(context)
1248
for floating_ip_ref in floaters:
1249
if floating_ip_ref['project_id'] is None:
1251
address = floating_ip_ref['address']
1253
if floating_ip_ref['fixed_ip_id']:
1254
fixed_id = floating_ip_ref['fixed_ip_id']
1255
fixed = self.network_api.get_fixed_ip(context, fixed_id)
1256
if fixed['instance_id'] is not None:
1257
ec2_id = ec2utils.id_to_ec2_id(fixed['instance_id'])
1258
address_rv = {'public_ip': address,
1259
'instance_id': ec2_id}
1260
if context.is_admin:
1261
details = "%s (%s)" % (address_rv['instance_id'],
1262
floating_ip_ref['project_id'])
1263
address_rv['instance_id'] = details
1264
addresses.append(address_rv)
1265
return {'addressesSet': addresses}
1267
def allocate_address(self, context, **kwargs):
1268
LOG.audit(_("Allocate address"), context=context)
1270
public_ip = self.network_api.allocate_floating_ip(context)
1271
return {'publicIp': public_ip}
1272
except rpc_common.RemoteError as ex:
1273
# NOTE(tr3buchet) - why does this block exist?
1274
if ex.exc_type == 'NoMoreFloatingIps':
1275
raise exception.NoMoreFloatingIps()
1279
def release_address(self, context, public_ip, **kwargs):
1280
LOG.audit(_("Release address %s"), public_ip, context=context)
1281
self.network_api.release_floating_ip(context, address=public_ip)
1282
return {'return': "true"}
1284
def associate_address(self, context, instance_id, public_ip, **kwargs):
1285
LOG.audit(_("Associate address %(public_ip)s to"
1286
" instance %(instance_id)s") % locals(), context=context)
1287
instance_id = ec2utils.ec2_id_to_id(instance_id)
1288
instance = self.compute_api.get(context, instance_id)
1289
self.compute_api.associate_floating_ip(context,
1292
return {'return': "true"}
1294
def disassociate_address(self, context, public_ip, **kwargs):
1295
LOG.audit(_("Disassociate address %s"), public_ip, context=context)
1296
self.network_api.disassociate_floating_ip(context, address=public_ip)
1297
return {'return': "true"}
1299
def run_instances(self, context, **kwargs):
1300
max_count = int(kwargs.get('max_count', 1))
1301
if kwargs.get('kernel_id'):
1302
kernel = self._get_image(context, kwargs['kernel_id'])
1303
kwargs['kernel_id'] = ec2utils.id_to_glance_id(context,
1305
if kwargs.get('ramdisk_id'):
1306
ramdisk = self._get_image(context, kwargs['ramdisk_id'])
1307
kwargs['ramdisk_id'] = ec2utils.id_to_glance_id(context,
1309
for bdm in kwargs.get('block_device_mapping', []):
1310
_parse_block_device_mapping(bdm)
1312
image = self._get_image(context, kwargs['image_id'])
1313
image_uuid = ec2utils.id_to_glance_id(context, image['id'])
1316
image_state = self._get_image_state(image)
1318
raise exception.ImageNotFound(image_id=kwargs['image_id'])
1320
if image_state != 'available':
1321
raise exception.EC2APIError(_('Image must be available'))
1323
(instances, resv_id) = self.compute_api.create(context,
1324
instance_type=instance_types.get_instance_type_by_name(
1325
kwargs.get('instance_type', None)),
1326
image_href=image_uuid,
1327
min_count=int(kwargs.get('min_count', max_count)),
1328
max_count=max_count,
1329
kernel_id=kwargs.get('kernel_id'),
1330
ramdisk_id=kwargs.get('ramdisk_id'),
1331
key_name=kwargs.get('key_name'),
1332
user_data=kwargs.get('user_data'),
1333
security_group=kwargs.get('security_group'),
1334
availability_zone=kwargs.get('placement', {}).get(
1335
'availability_zone'),
1336
block_device_mapping=kwargs.get('block_device_mapping', {}))
1337
return self._format_run_instances(context, resv_id)
1339
def terminate_instances(self, context, instance_id, **kwargs):
1340
"""Terminate each instance in instance_id, which is a list of ec2 ids.
1341
instance_id is a kwarg so its name cannot be modified."""
1342
LOG.debug(_("Going to start terminating instances"))
1343
previous_states = []
1344
for ec2_id in instance_id:
1345
validate_ec2_id(ec2_id)
1346
_instance_id = ec2utils.ec2_id_to_id(ec2_id)
1347
instance = self.compute_api.get(context, _instance_id)
1348
previous_states.append(instance)
1349
self.compute_api.delete(context, instance)
1350
return self._format_terminate_instances(context,
1354
def reboot_instances(self, context, instance_id, **kwargs):
1355
"""instance_id is a list of instance ids"""
1356
LOG.audit(_("Reboot instance %r"), instance_id, context=context)
1357
for ec2_id in instance_id:
1358
validate_ec2_id(ec2_id)
1359
_instance_id = ec2utils.ec2_id_to_id(ec2_id)
1360
instance = self.compute_api.get(context, _instance_id)
1361
self.compute_api.reboot(context, instance, 'HARD')
1364
def stop_instances(self, context, instance_id, **kwargs):
1365
"""Stop each instances in instance_id.
1366
Here instance_id is a list of instance ids"""
1367
LOG.debug(_("Going to stop instances"))
1368
for ec2_id in instance_id:
1369
validate_ec2_id(ec2_id)
1370
_instance_id = ec2utils.ec2_id_to_id(ec2_id)
1371
instance = self.compute_api.get(context, _instance_id)
1372
self.compute_api.stop(context, instance)
1375
def start_instances(self, context, instance_id, **kwargs):
1376
"""Start each instances in instance_id.
1377
Here instance_id is a list of instance ids"""
1378
LOG.debug(_("Going to start instances"))
1379
for ec2_id in instance_id:
1380
validate_ec2_id(ec2_id)
1381
_instance_id = ec2utils.ec2_id_to_id(ec2_id)
1382
instance = self.compute_api.get(context, _instance_id)
1383
self.compute_api.start(context, instance)
1386
def _get_image(self, context, ec2_id):
1388
internal_id = ec2utils.ec2_id_to_id(ec2_id)
1389
image = self.image_service.show(context, internal_id)
1390
except (exception.InvalidEc2Id, exception.ImageNotFound):
1392
return self.image_service.show_by_name(context, ec2_id)
1393
except exception.NotFound:
1394
raise exception.ImageNotFound(image_id=ec2_id)
1395
image_type = ec2_id.split('-')[0]
1396
if ec2utils.image_type(image.get('container_format')) != image_type:
1397
raise exception.ImageNotFound(image_id=ec2_id)
1400
def _format_image(self, image):
1401
"""Convert from format defined by GlanceImageService to S3 format."""
1403
image_type = ec2utils.image_type(image.get('container_format'))
1404
ec2_id = ec2utils.image_ec2_id(image.get('id'), image_type)
1405
name = image.get('name')
1406
i['imageId'] = ec2_id
1407
kernel_id = image['properties'].get('kernel_id')
1409
i['kernelId'] = ec2utils.image_ec2_id(kernel_id, 'aki')
1410
ramdisk_id = image['properties'].get('ramdisk_id')
1412
i['ramdiskId'] = ec2utils.image_ec2_id(ramdisk_id, 'ari')
1414
if FLAGS.auth_strategy == 'deprecated':
1415
i['imageOwnerId'] = image['properties'].get('project_id')
1417
i['imageOwnerId'] = image.get('owner')
1419
img_loc = image['properties'].get('image_location')
1421
i['imageLocation'] = img_loc
1423
i['imageLocation'] = "%s (%s)" % (img_loc, name)
1426
if not name and img_loc:
1427
# This should only occur for images registered with ec2 api
1428
# prior to that api populating the glance name
1431
i['imageState'] = self._get_image_state(image)
1432
i['description'] = image.get('description')
1433
display_mapping = {'aki': 'kernel',
1436
i['imageType'] = display_mapping.get(image_type)
1437
i['isPublic'] = not not image.get('is_public')
1438
i['architecture'] = image['properties'].get('architecture')
1440
properties = image['properties']
1441
root_device_name = block_device.properties_root_device_name(properties)
1442
root_device_type = 'instance-store'
1443
for bdm in properties.get('block_device_mapping', []):
1444
if (bdm.get('device_name') == root_device_name and
1445
('snapshot_id' in bdm or 'volume_id' in bdm) and
1446
not bdm.get('no_device')):
1447
root_device_type = 'ebs'
1448
i['rootDeviceName'] = (root_device_name or
1449
block_device.DEFAULT_ROOT_DEV_NAME)
1450
i['rootDeviceType'] = root_device_type
1452
_format_mappings(properties, i)
1456
def describe_images(self, context, image_id=None, **kwargs):
1457
# NOTE: image_id is a list!
1460
for ec2_id in image_id:
1462
image = self._get_image(context, ec2_id)
1463
except exception.NotFound:
1464
raise exception.ImageNotFound(image_id=ec2_id)
1465
images.append(image)
1467
images = self.image_service.detail(context)
1468
images = [self._format_image(i) for i in images]
1469
return {'imagesSet': images}
1471
def deregister_image(self, context, image_id, **kwargs):
1472
LOG.audit(_("De-registering image %s"), image_id, context=context)
1473
image = self._get_image(context, image_id)
1474
internal_id = image['id']
1475
self.image_service.delete(context, internal_id)
1476
return {'imageId': image_id}
1478
def _register_image(self, context, metadata):
1479
image = self.image_service.create(context, metadata)
1480
image_type = ec2utils.image_type(image.get('container_format'))
1481
image_id = ec2utils.image_ec2_id(image['id'], image_type)
1484
def register_image(self, context, image_location=None, **kwargs):
1485
if image_location is None and kwargs.get('name'):
1486
image_location = kwargs['name']
1487
if image_location is None:
1488
raise exception.EC2APIError(_('imageLocation is required'))
1490
metadata = {'properties': {'image_location': image_location}}
1492
if kwargs.get('name'):
1493
metadata['name'] = kwargs['name']
1495
metadata['name'] = image_location
1497
if 'root_device_name' in kwargs:
1498
metadata['properties']['root_device_name'] = kwargs.get(
1501
mappings = [_parse_block_device_mapping(bdm) for bdm in
1502
kwargs.get('block_device_mapping', [])]
1504
metadata['properties']['block_device_mapping'] = mappings
1506
image_id = self._register_image(context, metadata)
1507
msg = _("Registered image %(image_location)s with"
1508
" id %(image_id)s") % locals()
1509
LOG.audit(msg, context=context)
1510
return {'imageId': image_id}
1512
def describe_image_attribute(self, context, image_id, attribute, **kwargs):
1513
def _block_device_mapping_attribute(image, result):
1514
_format_mappings(image['properties'], result)
1516
def _launch_permission_attribute(image, result):
1517
result['launchPermission'] = []
1518
if image['is_public']:
1519
result['launchPermission'].append({'group': 'all'})
1521
def _root_device_name_attribute(image, result):
1522
_prop_root_dev_name = block_device.properties_root_device_name
1523
result['rootDeviceName'] = _prop_root_dev_name(image['properties'])
1524
if result['rootDeviceName'] is None:
1525
result['rootDeviceName'] = block_device.DEFAULT_ROOT_DEV_NAME
1527
supported_attributes = {
1528
'blockDeviceMapping': _block_device_mapping_attribute,
1529
'launchPermission': _launch_permission_attribute,
1530
'rootDeviceName': _root_device_name_attribute,
1533
fn = supported_attributes.get(attribute)
1535
raise exception.EC2APIError(_('attribute not supported: %s')
1538
image = self._get_image(context, image_id)
1539
except exception.NotFound:
1540
raise exception.ImageNotFound(image_id=image_id)
1542
result = {'imageId': image_id}
1546
def modify_image_attribute(self, context, image_id, attribute,
1547
operation_type, **kwargs):
1548
# TODO(devcamcar): Support users and groups other than 'all'.
1549
if attribute != 'launchPermission':
1550
raise exception.EC2APIError(_('attribute not supported: %s')
1552
if not 'user_group' in kwargs:
1553
raise exception.EC2APIError(_('user or group not specified'))
1554
if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all':
1555
raise exception.EC2APIError(_('only group "all" is supported'))
1556
if not operation_type in ['add', 'remove']:
1557
msg = _('operation_type must be add or remove')
1558
raise exception.EC2APIError(msg)
1559
LOG.audit(_("Updating image %s publicity"), image_id, context=context)
1562
image = self._get_image(context, image_id)
1563
except exception.NotFound:
1564
raise exception.ImageNotFound(image_id=image_id)
1565
internal_id = image['id']
1568
image['is_public'] = (operation_type == 'add')
1570
return self.image_service.update(context, internal_id, image)
1571
except exception.ImageNotAuthorized:
1572
msg = _('Not allowed to modify attributes for image %s')
1573
raise exception.EC2APIError(msg % image_id)
1575
def update_image(self, context, image_id, **kwargs):
1576
internal_id = ec2utils.ec2_id_to_id(image_id)
1577
result = self.image_service.update(context, internal_id, dict(kwargs))
1580
# TODO(yamahata): race condition
1581
# At the moment there is no way to prevent others from
1582
# manipulating instances/volumes/snapshots.
1583
# As other code doesn't take it into consideration, here we don't
1584
# care of it for now. Ostrich algorithm
1585
def create_image(self, context, instance_id, **kwargs):
1586
# NOTE(yamahata): name/description are ignored by register_image(),
1588
no_reboot = kwargs.get('no_reboot', False)
1589
validate_ec2_id(instance_id)
1590
ec2_instance_id = instance_id
1591
instance_id = ec2utils.ec2_id_to_id(ec2_instance_id)
1592
instance = self.compute_api.get(context, instance_id)
1594
# stop the instance if necessary
1595
restart_instance = False
1597
vm_state = instance['vm_state']
1599
# if the instance is in subtle state, refuse to proceed.
1600
if vm_state not in (vm_states.ACTIVE, vm_states.SHUTOFF,
1602
raise exception.InstanceNotRunning(instance_id=ec2_instance_id)
1604
if vm_state in (vm_states.ACTIVE, vm_states.SHUTOFF):
1605
restart_instance = True
1606
self.compute_api.stop(context, instance)
1608
# wait instance for really stopped
1609
start_time = time.time()
1610
while vm_state != vm_states.STOPPED:
1612
instance = self.compute_api.get(context, instance_id)
1613
vm_state = instance['vm_state']
1614
# NOTE(yamahata): timeout and error. 1 hour for now for safety.
1615
# Is it too short/long?
1616
# Or is there any better way?
1617
timeout = 1 * 60 * 60 * 60
1618
if time.time() > start_time + timeout:
1619
raise exception.EC2APIError(
1620
_('Couldn\'t stop instance with in %d sec') % timeout)
1622
src_image = self._get_image(context, instance['image_ref'])
1623
properties = src_image['properties']
1624
if instance['root_device_name']:
1625
properties['root_device_name'] = instance['root_device_name']
1628
bdms = db.block_device_mapping_get_all_by_instance(context,
1634
for attr in ('device_name', 'snapshot_id', 'volume_id',
1635
'volume_size', 'delete_on_termination', 'no_device',
1637
val = getattr(bdm, attr)
1641
volume_id = m.get('volume_id')
1642
if m.get('snapshot_id') and volume_id:
1643
# create snapshot based on volume_id
1644
volume = self.volume_api.get(context, volume_id)
1645
# NOTE(yamahata): Should we wait for snapshot creation?
1646
# Linux LVM snapshot creation completes in
1647
# short time, it doesn't matter for now.
1648
snapshot = self.volume_api.create_snapshot_force(
1649
context, volume, volume['display_name'],
1650
volume['display_description'])
1651
m['snapshot_id'] = snapshot['id']
1657
for m in _properties_get_mappings(properties):
1658
virtual_name = m['virtual']
1659
if virtual_name in ('ami', 'root'):
1662
assert block_device.is_swap_or_ephemeral(virtual_name)
1663
device_name = m['device']
1664
if device_name in [b['device_name'] for b in mapping
1665
if not b.get('no_device', False)]:
1668
# NOTE(yamahata): swap and ephemeral devices are specified in
1669
# AMI, but disabled for this instance by user.
1670
# So disable those device by no_device.
1671
mapping.append({'device_name': device_name, 'no_device': True})
1674
properties['block_device_mapping'] = mapping
1676
for attr in ('status', 'location', 'id'):
1677
src_image.pop(attr, None)
1679
image_id = self._register_image(context, src_image)
1681
if restart_instance:
1682
self.compute_api.start(context, instance_id=instance_id)
1684
return {'imageId': image_id}