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 utils
46
from nova import volume
50
flags.DECLARE('dhcp_domain', 'nova.network.manager')
52
LOG = logging.getLogger(__name__)
55
def validate_ec2_id(val):
56
if not validator.validate_str()(val):
57
raise exception.InvalidInstanceIDMalformed(val)
59
ec2utils.ec2_id_to_id(val)
60
except exception.InvalidEc2Id:
61
raise exception.InvalidInstanceIDMalformed(val)
64
def _gen_key(context, user_id, key_name):
67
This is a module level method because it is slow and we need to defer
68
it into a process pool."""
69
# NOTE(vish): generating key pair is slow so check for legal
70
# creation before creating key_pair
72
db.key_pair_get(context, user_id, key_name)
73
raise exception.KeyPairExists(key_name=key_name)
74
except exception.NotFound:
76
private_key, public_key, fingerprint = crypto.generate_key_pair()
78
key['user_id'] = user_id
79
key['name'] = key_name
80
key['public_key'] = public_key
81
key['fingerprint'] = fingerprint
82
db.key_pair_create(context, key)
83
return {'private_key': private_key, 'fingerprint': fingerprint}
86
# EC2 API can return the following values as documented in the EC2 API
87
# http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/
88
# ApiReference-ItemType-InstanceStateType.html
89
# pending 0 | running 16 | shutting-down 32 | terminated 48 | stopping 64 |
91
_STATE_DESCRIPTION_MAP = {
92
None: inst_state.PENDING,
93
vm_states.ACTIVE: inst_state.RUNNING,
94
vm_states.BUILDING: inst_state.PENDING,
95
vm_states.REBUILDING: inst_state.PENDING,
96
vm_states.DELETED: inst_state.TERMINATED,
97
vm_states.SOFT_DELETE: inst_state.TERMINATED,
98
vm_states.STOPPED: inst_state.STOPPED,
99
vm_states.SHUTOFF: inst_state.SHUTOFF,
100
vm_states.MIGRATING: inst_state.MIGRATE,
101
vm_states.RESIZING: inst_state.RESIZE,
102
vm_states.PAUSED: inst_state.PAUSE,
103
vm_states.SUSPENDED: inst_state.SUSPEND,
104
vm_states.RESCUED: inst_state.RESCUE,
108
def _state_description(vm_state, shutdown_terminate):
109
"""Map the vm state to the server status string"""
110
if (vm_state == vm_states.SHUTOFF and
111
not shutdown_terminate):
112
name = inst_state.STOPPED
114
name = _STATE_DESCRIPTION_MAP.get(vm_state, vm_state)
116
return {'code': inst_state.name_to_code(name),
120
def _parse_block_device_mapping(bdm):
121
"""Parse BlockDeviceMappingItemType into flat hash
122
BlockDevicedMapping.<N>.DeviceName
123
BlockDevicedMapping.<N>.Ebs.SnapshotId
124
BlockDevicedMapping.<N>.Ebs.VolumeSize
125
BlockDevicedMapping.<N>.Ebs.DeleteOnTermination
126
BlockDevicedMapping.<N>.Ebs.NoDevice
127
BlockDevicedMapping.<N>.VirtualName
128
=> remove .Ebs and allow volume id in SnapshotId
130
ebs = bdm.pop('ebs', None)
132
ec2_id = ebs.pop('snapshot_id', None)
134
id = ec2utils.ec2_id_to_id(ec2_id)
135
if ec2_id.startswith('snap-'):
136
bdm['snapshot_id'] = id
137
elif ec2_id.startswith('vol-'):
138
bdm['volume_id'] = id
139
ebs.setdefault('delete_on_termination', True)
144
def _properties_get_mappings(properties):
145
return block_device.mappings_prepend_dev(properties.get('mappings', []))
148
def _format_block_device_mapping(bdm):
149
"""Contruct BlockDeviceMappingItemType
150
{'device_name': '...', 'snapshot_id': , ...}
151
=> BlockDeviceMappingItemType
153
keys = (('deviceName', 'device_name'),
154
('virtualName', 'virtual_name'))
159
if bdm.get('no_device'):
160
item['noDevice'] = True
161
if ('snapshot_id' in bdm) or ('volume_id' in bdm):
162
ebs_keys = (('snapshotId', 'snapshot_id'),
163
('snapshotId', 'volume_id'), # snapshotId is abused
164
('volumeSize', 'volume_size'),
165
('deleteOnTermination', 'delete_on_termination'))
167
for name, k in ebs_keys:
169
if k == 'snapshot_id':
170
ebs[name] = ec2utils.id_to_ec2_snap_id(bdm[k])
171
elif k == 'volume_id':
172
ebs[name] = ec2utils.id_to_ec2_vol_id(bdm[k])
175
assert 'snapshotId' in ebs
180
def _format_mappings(properties, result):
181
"""Format multiple BlockDeviceMappingItemType"""
182
mappings = [{'virtualName': m['virtual'], 'deviceName': m['device']}
183
for m in _properties_get_mappings(properties)
184
if block_device.is_swap_or_ephemeral(m['virtual'])]
186
block_device_mapping = [_format_block_device_mapping(bdm) for bdm in
187
properties.get('block_device_mapping', [])]
189
# NOTE(yamahata): overwrite mappings with block_device_mapping
190
for bdm in block_device_mapping:
191
for i in range(len(mappings)):
192
if bdm['deviceName'] == mappings[i]['deviceName']:
197
# NOTE(yamahata): trim ebs.no_device == true. Is this necessary?
198
mappings = [bdm for bdm in mappings if not (bdm.get('noDevice', False))]
201
result['blockDeviceMapping'] = mappings
204
class CloudController(object):
205
""" CloudController provides the critical dispatch between
206
inbound API calls through the endpoint and messages
207
sent to the other nodes.
210
self.image_service = s3.S3ImageService()
211
self.network_api = network.API()
212
self.volume_api = volume.API()
213
self.compute_api = compute.API(network_api=self.network_api,
214
volume_api=self.volume_api)
215
self.sgh = utils.import_object(FLAGS.security_group_handler)
218
return 'CloudController'
220
def _get_image_state(self, image):
221
# NOTE(vish): fallback status if image_state isn't set
222
state = image.get('status')
223
if state == 'active':
225
return image['properties'].get('image_state', state)
227
def describe_availability_zones(self, context, **kwargs):
228
if ('zone_name' in kwargs and
229
'verbose' in kwargs['zone_name'] and
231
return self._describe_availability_zones_verbose(context,
234
return self._describe_availability_zones(context, **kwargs)
236
def _describe_availability_zones(self, context, **kwargs):
237
ctxt = context.elevated()
238
enabled_services = db.service_get_all(ctxt, False)
239
disabled_services = db.service_get_all(ctxt, True)
241
for zone in [service.availability_zone for service
242
in enabled_services]:
243
if not zone in available_zones:
244
available_zones.append(zone)
245
not_available_zones = []
246
for zone in [service.availability_zone for service in disabled_services
247
if not service['availability_zone'] in available_zones]:
248
if not zone in not_available_zones:
249
not_available_zones.append(zone)
251
for zone in available_zones:
252
result.append({'zoneName': zone,
253
'zoneState': "available"})
254
for zone in not_available_zones:
255
result.append({'zoneName': zone,
256
'zoneState': "not available"})
257
return {'availabilityZoneInfo': result}
259
def _describe_availability_zones_verbose(self, context, **kwargs):
260
rv = {'availabilityZoneInfo': [{'zoneName': 'nova',
261
'zoneState': 'available'}]}
263
services = db.service_get_all(context, False)
265
for host in [service['host'] for service in services]:
266
if not host in hosts:
269
rv['availabilityZoneInfo'].append({'zoneName': '|- %s' % host,
271
hsvcs = [service for service in services
272
if service['host'] == host]
274
alive = utils.service_is_up(svc)
275
art = (alive and ":-)") or "XXX"
279
rv['availabilityZoneInfo'].append({
280
'zoneName': '| |- %s' % svc['binary'],
281
'zoneState': '%s %s %s' % (active, art,
285
def describe_regions(self, context, region_name=None, **kwargs):
286
if FLAGS.region_list:
288
for region in FLAGS.region_list:
289
name, _sep, host = region.partition('=')
290
endpoint = '%s://%s:%s%s' % (FLAGS.ec2_scheme,
294
regions.append({'regionName': name,
295
'regionEndpoint': endpoint})
297
regions = [{'regionName': 'nova',
298
'regionEndpoint': '%s://%s:%s%s' % (FLAGS.ec2_scheme,
302
return {'regionInfo': regions}
304
def describe_snapshots(self,
312
for ec2_id in snapshot_id:
313
internal_id = ec2utils.ec2_id_to_id(ec2_id)
314
snapshot = self.volume_api.get_snapshot(
316
snapshot_id=internal_id)
317
snapshots.append(snapshot)
319
snapshots = self.volume_api.get_all_snapshots(context)
320
snapshots = [self._format_snapshot(context, s) for s in snapshots]
321
return {'snapshotSet': snapshots}
323
def _format_snapshot(self, context, snapshot):
325
s['snapshotId'] = ec2utils.id_to_ec2_snap_id(snapshot['id'])
326
s['volumeId'] = ec2utils.id_to_ec2_vol_id(snapshot['volume_id'])
327
s['status'] = snapshot['status']
328
s['startTime'] = snapshot['created_at']
329
s['progress'] = snapshot['progress']
330
s['ownerId'] = snapshot['project_id']
331
s['volumeSize'] = snapshot['volume_size']
332
s['description'] = snapshot['display_description']
335
def create_snapshot(self, context, volume_id, **kwargs):
336
validate_ec2_id(volume_id)
337
LOG.audit(_("Create snapshot of volume %s"), volume_id,
339
volume_id = ec2utils.ec2_id_to_id(volume_id)
340
volume = self.volume_api.get(context, volume_id)
341
snapshot = self.volume_api.create_snapshot(
345
kwargs.get('description'))
346
return self._format_snapshot(context, snapshot)
348
def delete_snapshot(self, context, snapshot_id, **kwargs):
349
snapshot_id = ec2utils.ec2_id_to_id(snapshot_id)
350
snapshot = self.volume_api.get_snapshot(context, snapshot_id)
351
self.volume_api.delete_snapshot(context, snapshot)
354
def describe_key_pairs(self, context, key_name=None, **kwargs):
355
key_pairs = db.key_pair_get_all_by_user(context, context.user_id)
356
if not key_name is None:
357
key_pairs = [x for x in key_pairs if x['name'] in key_name]
360
for key_pair in key_pairs:
361
# filter out the vpn keys
362
suffix = FLAGS.vpn_key_suffix
363
if context.is_admin or not key_pair['name'].endswith(suffix):
365
'keyName': key_pair['name'],
366
'keyFingerprint': key_pair['fingerprint'],
369
return {'keySet': result}
371
def create_key_pair(self, context, key_name, **kwargs):
372
if not re.match('^[a-zA-Z0-9_\- ]+$', str(key_name)):
373
err = _("Value (%s) for KeyName is invalid."
374
" Content limited to Alphanumeric character, "
375
"spaces, dashes, and underscore.") % key_name
376
raise exception.EC2APIError(err)
378
if len(str(key_name)) > 255:
379
err = _("Value (%s) for Keyname is invalid."
380
" Length exceeds maximum of 255.") % key_name
381
raise exception.EC2APIError(err)
383
LOG.audit(_("Create key pair %s"), key_name, context=context)
384
data = _gen_key(context, context.user_id, key_name)
385
return {'keyName': key_name,
386
'keyFingerprint': data['fingerprint'],
387
'keyMaterial': data['private_key']}
388
# TODO(vish): when context is no longer an object, pass it here
390
def import_key_pair(self, context, key_name, public_key_material,
392
LOG.audit(_("Import key %s"), key_name, context=context)
394
db.key_pair_get(context, context.user_id, key_name)
395
raise exception.KeyPairExists(key_name=key_name)
396
except exception.NotFound:
398
public_key = base64.b64decode(public_key_material)
399
fingerprint = crypto.generate_fingerprint(public_key)
401
key['user_id'] = context.user_id
402
key['name'] = key_name
403
key['public_key'] = public_key
404
key['fingerprint'] = fingerprint
405
db.key_pair_create(context, key)
406
return {'keyName': key_name,
407
'keyFingerprint': fingerprint}
409
def delete_key_pair(self, context, key_name, **kwargs):
410
LOG.audit(_("Delete key pair %s"), key_name, context=context)
412
db.key_pair_destroy(context, context.user_id, key_name)
413
except exception.NotFound:
414
# aws returns true even if the key doesn't exist
418
def describe_security_groups(self, context, group_name=None, group_id=None,
420
self.compute_api.ensure_default_security_group(context)
421
if group_name or group_id:
424
for name in group_name:
425
group = db.security_group_get_by_name(context,
431
group = db.security_group_get(context, gid)
433
elif context.is_admin:
434
groups = db.security_group_get_all(context)
436
groups = db.security_group_get_by_project(context,
438
groups = [self._format_security_group(context, g) for g in groups]
440
return {'securityGroupInfo':
442
key=lambda k: (k['ownerId'], k['groupName'])))}
444
def _format_security_group(self, context, group):
446
g['groupDescription'] = group.description
447
g['groupName'] = group.name
448
g['ownerId'] = group.project_id
449
g['ipPermissions'] = []
450
for rule in group.rules:
455
source_group = db.security_group_get(context, rule.group_id)
456
r['groups'] += [{'groupName': source_group.name,
457
'userId': source_group.project_id}]
459
r['ipProtocol'] = rule.protocol
460
r['fromPort'] = rule.from_port
461
r['toPort'] = rule.to_port
462
g['ipPermissions'] += [dict(r)]
464
for protocol, min_port, max_port in (('icmp', -1, -1),
467
r['ipProtocol'] = protocol
468
r['fromPort'] = min_port
469
r['toPort'] = max_port
470
g['ipPermissions'] += [dict(r)]
472
r['ipProtocol'] = rule.protocol
473
r['fromPort'] = rule.from_port
474
r['toPort'] = rule.to_port
475
r['ipRanges'] += [{'cidrIp': rule.cidr}]
476
g['ipPermissions'] += [r]
479
def _rule_args_to_dict(self, context, kwargs):
481
if not 'groups' in kwargs and not 'ip_ranges' in kwargs:
482
rule = self._rule_dict_last_step(context, **kwargs)
486
if 'ip_ranges' in kwargs:
487
rules = self._cidr_args_split(kwargs)
493
groups_values = self._groups_args_split(rule)
494
for groups_value in groups_values:
495
final = self._rule_dict_last_step(context, **groups_value)
496
finalset.append(final)
498
final = self._rule_dict_last_step(context, **rule)
499
finalset.append(final)
502
def _cidr_args_split(self, kwargs):
504
cidrs = kwargs['ip_ranges']
505
for key, cidr in cidrs.iteritems():
506
mykwargs = kwargs.copy()
507
del mykwargs['ip_ranges']
508
mykwargs['cidr_ip'] = cidr['cidr_ip']
509
cidr_args_split.append(mykwargs)
510
return cidr_args_split
512
def _groups_args_split(self, kwargs):
513
groups_args_split = []
514
groups = kwargs['groups']
515
for key, group in groups.iteritems():
516
mykwargs = kwargs.copy()
517
del mykwargs['groups']
518
if 'group_name' in group:
519
mykwargs['source_security_group_name'] = group['group_name']
520
if 'user_id' in group:
521
mykwargs['source_security_group_owner_id'] = group['user_id']
522
if 'group_id' in group:
523
mykwargs['source_security_group_id'] = group['group_id']
524
groups_args_split.append(mykwargs)
525
return groups_args_split
527
def _rule_dict_last_step(self, context, to_port=None, from_port=None,
528
ip_protocol=None, cidr_ip=None, user_id=None,
529
source_security_group_name=None,
530
source_security_group_owner_id=None):
534
if source_security_group_name:
535
source_project_id = self._get_source_project_id(context,
536
source_security_group_owner_id)
538
source_security_group = db.security_group_get_by_name(
541
source_security_group_name)
542
notfound = exception.SecurityGroupNotFound
543
if not source_security_group:
544
raise notfound(security_group_id=source_security_group_name)
545
values['group_id'] = source_security_group['id']
547
# If this fails, it throws an exception. This is what we want.
548
cidr_ip = urllib.unquote(cidr_ip).decode()
550
if not utils.is_valid_cidr(cidr_ip):
551
# Raise exception for non-valid address
552
raise exception.EC2APIError(_("Invalid CIDR"))
554
values['cidr'] = cidr_ip
556
values['cidr'] = '0.0.0.0/0'
558
if source_security_group_name:
559
# Open everything if an explicit port range or type/code are not
560
# specified, but only if a source group was specified.
561
ip_proto_upper = ip_protocol.upper() if ip_protocol else ''
562
if (ip_proto_upper == 'ICMP' and
563
from_port is None and to_port is None):
566
elif (ip_proto_upper in ['TCP', 'UDP'] and from_port is None
567
and to_port is None):
571
if ip_protocol and from_port is not None and to_port is not None:
573
ip_protocol = str(ip_protocol)
575
# Verify integer conversions
576
from_port = int(from_port)
577
to_port = int(to_port)
579
if ip_protocol.upper() == 'ICMP':
580
raise exception.InvalidInput(reason="Type and"
581
" Code must be integers for ICMP protocol type")
583
raise exception.InvalidInput(reason="To and From ports "
586
if ip_protocol.upper() not in ['TCP', 'UDP', 'ICMP']:
587
raise exception.InvalidIpProtocol(protocol=ip_protocol)
589
# Verify that from_port must always be less than
590
# or equal to to_port
591
if (ip_protocol.upper() in ['TCP', 'UDP'] and
592
(from_port > to_port)):
593
raise exception.InvalidPortRange(from_port=from_port,
594
to_port=to_port, msg="Former value cannot"
595
" be greater than the later")
597
# Verify valid TCP, UDP port ranges
598
if (ip_protocol.upper() in ['TCP', 'UDP'] and
599
(from_port < 1 or to_port > 65535)):
600
raise exception.InvalidPortRange(from_port=from_port,
601
to_port=to_port, msg="Valid TCP ports should"
602
" be between 1-65535")
604
# Verify ICMP type and code
605
if (ip_protocol.upper() == "ICMP" and
606
(from_port < -1 or from_port > 255 or
607
to_port < -1 or to_port > 255)):
608
raise exception.InvalidPortRange(from_port=from_port,
609
to_port=to_port, msg="For ICMP, the"
610
" type:code must be valid")
612
values['protocol'] = ip_protocol
613
values['from_port'] = from_port
614
values['to_port'] = to_port
616
# If cidr based filtering, protocol and ports are mandatory
622
def _security_group_rule_exists(self, security_group, values):
623
"""Indicates whether the specified rule values are already
624
defined in the given security group.
626
for rule in security_group.rules:
628
keys = ('group_id', 'cidr', 'from_port', 'to_port', 'protocol')
630
if rule.get(key) != values.get(key):
637
def revoke_security_group_ingress(self, context, group_name=None,
638
group_id=None, **kwargs):
639
if not group_name and not group_id:
640
err = _("Not enough parameters, need group_name or group_id")
641
raise exception.EC2APIError(err)
642
self.compute_api.ensure_default_security_group(context)
643
notfound = exception.SecurityGroupNotFound
645
security_group = db.security_group_get_by_name(context,
648
if not security_group:
649
raise notfound(security_group_id=group_name)
651
security_group = db.security_group_get(context, group_id)
652
if not security_group:
653
raise notfound(security_group_id=group_id)
655
msg = _("Revoke security group ingress %s")
656
LOG.audit(msg, security_group['name'], context=context)
659
prevalues = kwargs['ip_permissions']
661
prevalues.append(kwargs)
664
for values in prevalues:
665
rulesvalues = self._rule_args_to_dict(context, values)
667
err = _("%s Not enough parameters to build a valid rule")
668
raise exception.EC2APIError(err % rulesvalues)
670
for values_for_rule in rulesvalues:
671
values_for_rule['parent_group_id'] = security_group.id
672
rule_id = self._security_group_rule_exists(security_group,
675
db.security_group_rule_destroy(context, rule_id)
676
rule_ids.append(rule_id)
678
# NOTE(vish): we removed a rule, so refresh
679
self.compute_api.trigger_security_group_rules_refresh(
681
security_group_id=security_group['id'])
682
self.sgh.trigger_security_group_rule_destroy_refresh(
685
raise exception.EC2APIError(_("No rule for the specified parameters."))
687
# TODO(soren): This has only been tested with Boto as the client.
688
# Unfortunately, it seems Boto is using an old API
689
# for these operations, so support for newer API versions
691
def authorize_security_group_ingress(self, context, group_name=None,
692
group_id=None, **kwargs):
693
if not group_name and not group_id:
694
err = _("Not enough parameters, need group_name or group_id")
695
raise exception.EC2APIError(err)
696
self.compute_api.ensure_default_security_group(context)
697
notfound = exception.SecurityGroupNotFound
699
security_group = db.security_group_get_by_name(context,
702
if not security_group:
703
raise notfound(security_group_id=group_name)
705
security_group = db.security_group_get(context, group_id)
706
if not security_group:
707
raise notfound(security_group_id=group_id)
709
msg = _("Authorize security group ingress %s")
710
LOG.audit(msg, security_group['name'], context=context)
713
prevalues = kwargs['ip_permissions']
715
prevalues.append(kwargs)
717
for values in prevalues:
718
rulesvalues = self._rule_args_to_dict(context, values)
720
err = _("%s Not enough parameters to build a valid rule")
721
raise exception.EC2APIError(err % rulesvalues)
722
for values_for_rule in rulesvalues:
723
values_for_rule['parent_group_id'] = security_group.id
724
if self._security_group_rule_exists(security_group,
726
err = _('%s - This rule already exists in group')
727
raise exception.EC2APIError(err % values_for_rule)
728
postvalues.append(values_for_rule)
731
for values_for_rule in postvalues:
732
security_group_rule = db.security_group_rule_create(
735
rule_ids.append(security_group_rule['id'])
738
self.compute_api.trigger_security_group_rules_refresh(
740
security_group_id=security_group['id'])
741
self.sgh.trigger_security_group_rule_create_refresh(
745
raise exception.EC2APIError(_("No rule for the specified parameters."))
747
def _get_source_project_id(self, context, source_security_group_owner_id):
748
if source_security_group_owner_id:
749
# Parse user:project for source group.
750
source_parts = source_security_group_owner_id.split(':')
752
# If no project name specified, assume it's same as user name.
753
# Since we're looking up by project name, the user name is not
754
# used here. It's only read for EC2 API compatibility.
755
if len(source_parts) == 2:
756
source_project_id = source_parts[1]
758
source_project_id = source_parts[0]
760
source_project_id = context.project_id
762
return source_project_id
764
def create_security_group(self, context, group_name, group_description):
765
if not re.match('^[a-zA-Z0-9_\- ]+$', str(group_name)):
766
# Some validation to ensure that values match API spec.
767
# - Alphanumeric characters, spaces, dashes, and underscores.
768
# TODO(Daviey): LP: #813685 extend beyond group_name checking, and
769
# probably create a param validator that can be used elsewhere.
770
err = _("Value (%s) for parameter GroupName is invalid."
771
" Content limited to Alphanumeric characters, "
772
"spaces, dashes, and underscores.") % group_name
773
# err not that of master ec2 implementation, as they fail to raise.
774
raise exception.InvalidParameterValue(err=err)
776
if len(str(group_name)) > 255:
777
err = _("Value (%s) for parameter GroupName is invalid."
778
" Length exceeds maximum of 255.") % group_name
779
raise exception.InvalidParameterValue(err=err)
781
LOG.audit(_("Create Security Group %s"), group_name, context=context)
782
self.compute_api.ensure_default_security_group(context)
783
if db.security_group_exists(context, context.project_id, group_name):
784
msg = _('group %s already exists')
785
raise exception.EC2APIError(msg % group_name)
787
group = {'user_id': context.user_id,
788
'project_id': context.project_id,
790
'description': group_description}
791
group_ref = db.security_group_create(context, group)
793
self.sgh.trigger_security_group_create_refresh(context, group)
795
return {'securityGroupSet': [self._format_security_group(context,
798
def delete_security_group(self, context, group_name=None, group_id=None,
800
if not group_name and not group_id:
801
err = _("Not enough parameters, need group_name or group_id")
802
raise exception.EC2APIError(err)
803
notfound = exception.SecurityGroupNotFound
805
security_group = db.security_group_get_by_name(context,
808
if not security_group:
809
raise notfound(security_group_id=group_name)
811
security_group = db.security_group_get(context, group_id)
812
if not security_group:
813
raise notfound(security_group_id=group_id)
814
if db.security_group_in_use(context, security_group.id):
815
raise exception.InvalidGroup(reason="In Use")
816
LOG.audit(_("Delete security group %s"), group_name, context=context)
817
db.security_group_destroy(context, security_group.id)
819
self.sgh.trigger_security_group_destroy_refresh(context,
823
def get_console_output(self, context, instance_id, **kwargs):
824
LOG.audit(_("Get console output for instance %s"), instance_id,
826
# instance_id may be passed in as a list of instances
827
if isinstance(instance_id, list):
828
ec2_id = instance_id[0]
831
validate_ec2_id(ec2_id)
832
instance_id = ec2utils.ec2_id_to_id(ec2_id)
833
instance = self.compute_api.get(context, instance_id)
834
output = self.compute_api.get_console_output(context, instance)
836
return {"InstanceId": ec2_id,
838
"output": base64.b64encode(output)}
840
def describe_volumes(self, context, volume_id=None, **kwargs):
843
for ec2_id in volume_id:
844
validate_ec2_id(ec2_id)
845
internal_id = ec2utils.ec2_id_to_id(ec2_id)
846
volume = self.volume_api.get(context, internal_id)
847
volumes.append(volume)
849
volumes = self.volume_api.get_all(context)
850
volumes = [self._format_volume(context, v) for v in volumes]
851
return {'volumeSet': volumes}
853
def _format_volume(self, context, volume):
854
instance_ec2_id = None
856
if volume.get('instance', None):
857
instance_id = volume['instance']['id']
858
instance_ec2_id = ec2utils.id_to_ec2_id(instance_id)
859
instance_data = '%s[%s]' % (instance_ec2_id,
860
volume['instance']['host'])
862
v['volumeId'] = ec2utils.id_to_ec2_vol_id(volume['id'])
863
v['status'] = volume['status']
864
v['size'] = volume['size']
865
v['availabilityZone'] = volume['availability_zone']
866
v['createTime'] = volume['created_at']
868
v['status'] = '%s (%s, %s, %s, %s)' % (
870
volume['project_id'],
873
volume['mountpoint'])
874
if volume['attach_status'] == 'attached':
875
v['attachmentSet'] = [{'attachTime': volume['attach_time'],
876
'deleteOnTermination': False,
877
'device': volume['mountpoint'],
878
'instanceId': instance_ec2_id,
879
'status': 'attached',
880
'volumeId': v['volumeId']}]
882
v['attachmentSet'] = [{}]
883
if volume.get('snapshot_id') is not None:
884
v['snapshotId'] = ec2utils.id_to_ec2_snap_id(volume['snapshot_id'])
886
v['snapshotId'] = None
890
def create_volume(self, context, **kwargs):
891
size = kwargs.get('size')
892
if kwargs.get('snapshot_id') is not None:
893
snapshot_id = ec2utils.ec2_id_to_id(kwargs['snapshot_id'])
894
snapshot = self.volume_api.get_snapshot(context, snapshot_id)
895
LOG.audit(_("Create volume from snapshot %s"), snapshot_id,
899
LOG.audit(_("Create volume of %s GB"), size, context=context)
901
availability_zone = kwargs.get('availability_zone', None)
903
volume = self.volume_api.create(context,
908
availability_zone=availability_zone)
909
# TODO(vish): Instance should be None at db layer instead of
910
# trying to lazy load, but for now we turn it into
911
# a dict to avoid an error.
912
return self._format_volume(context, dict(volume))
914
def delete_volume(self, context, volume_id, **kwargs):
915
validate_ec2_id(volume_id)
916
volume_id = ec2utils.ec2_id_to_id(volume_id)
919
volume = self.volume_api.get(context, volume_id)
920
self.volume_api.delete(context, volume)
921
except exception.InvalidVolume:
922
raise exception.EC2APIError(_('Delete Failed'))
926
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
927
validate_ec2_id(instance_id)
928
validate_ec2_id(volume_id)
929
volume_id = ec2utils.ec2_id_to_id(volume_id)
930
instance_id = ec2utils.ec2_id_to_id(instance_id)
931
instance = self.compute_api.get(context, instance_id)
932
msg = _("Attach volume %(volume_id)s to instance %(instance_id)s"
933
" at %(device)s") % locals()
934
LOG.audit(msg, context=context)
937
self.compute_api.attach_volume(context, instance,
939
except exception.InvalidVolume:
940
raise exception.EC2APIError(_('Attach Failed.'))
942
volume = self.volume_api.get(context, volume_id)
943
return {'attachTime': volume['attach_time'],
944
'device': volume['mountpoint'],
945
'instanceId': ec2utils.id_to_ec2_id(instance_id),
946
'requestId': context.request_id,
947
'status': volume['attach_status'],
948
'volumeId': ec2utils.id_to_ec2_vol_id(volume_id)}
950
def detach_volume(self, context, volume_id, **kwargs):
951
validate_ec2_id(volume_id)
952
volume_id = ec2utils.ec2_id_to_id(volume_id)
953
LOG.audit(_("Detach volume %s"), volume_id, context=context)
954
volume = self.volume_api.get(context, volume_id)
957
instance = self.compute_api.detach_volume(context,
959
except exception.InvalidVolume:
960
raise exception.EC2APIError(_('Detach Volume Failed.'))
962
return {'attachTime': volume['attach_time'],
963
'device': volume['mountpoint'],
964
'instanceId': ec2utils.id_to_ec2_id(instance['id']),
965
'requestId': context.request_id,
966
'status': volume['attach_status'],
967
'volumeId': ec2utils.id_to_ec2_vol_id(volume_id)}
969
def _format_kernel_id(self, context, instance_ref, result, key):
970
kernel_uuid = instance_ref['kernel_id']
971
if kernel_uuid is None or kernel_uuid == '':
973
result[key] = ec2utils.glance_id_to_ec2_id(context, kernel_uuid, 'aki')
975
def _format_ramdisk_id(self, context, instance_ref, result, key):
976
ramdisk_uuid = instance_ref['ramdisk_id']
977
if ramdisk_uuid is None or ramdisk_uuid == '':
979
result[key] = ec2utils.glance_id_to_ec2_id(context, ramdisk_uuid,
982
def describe_instance_attribute(self, context, instance_id, attribute,
984
def _unsupported_attribute(instance, result):
985
raise exception.EC2APIError(_('attribute not supported: %s') %
988
def _format_attr_block_device_mapping(instance, result):
990
self._format_instance_root_device_name(instance, tmp)
991
self._format_instance_bdm(context, instance_id,
992
tmp['rootDeviceName'], result)
994
def _format_attr_disable_api_termination(instance, result):
995
result['disableApiTermination'] = instance['disable_terminate']
997
def _format_attr_group_set(instance, result):
998
CloudController._format_group_set(instance, result)
1000
def _format_attr_instance_initiated_shutdown_behavior(instance,
1002
if instance['shutdown_terminate']:
1003
result['instanceInitiatedShutdownBehavior'] = 'terminate'
1005
result['instanceInitiatedShutdownBehavior'] = 'stop'
1007
def _format_attr_instance_type(instance, result):
1008
self._format_instance_type(instance, result)
1010
def _format_attr_kernel(instance, result):
1011
self._format_kernel_id(context, instance, result, 'kernel')
1013
def _format_attr_ramdisk(instance, result):
1014
self._format_ramdisk_id(context, instance, result, 'ramdisk')
1016
def _format_attr_root_device_name(instance, result):
1017
self._format_instance_root_device_name(instance, result)
1019
def _format_attr_source_dest_check(instance, result):
1020
_unsupported_attribute(instance, result)
1022
def _format_attr_user_data(instance, result):
1023
result['userData'] = base64.b64decode(instance['user_data'])
1025
attribute_formatter = {
1026
'blockDeviceMapping': _format_attr_block_device_mapping,
1027
'disableApiTermination': _format_attr_disable_api_termination,
1028
'groupSet': _format_attr_group_set,
1029
'instanceInitiatedShutdownBehavior':
1030
_format_attr_instance_initiated_shutdown_behavior,
1031
'instanceType': _format_attr_instance_type,
1032
'kernel': _format_attr_kernel,
1033
'ramdisk': _format_attr_ramdisk,
1034
'rootDeviceName': _format_attr_root_device_name,
1035
'sourceDestCheck': _format_attr_source_dest_check,
1036
'userData': _format_attr_user_data,
1039
fn = attribute_formatter.get(attribute)
1041
raise exception.EC2APIError(
1042
_('attribute not supported: %s') % attribute)
1044
ec2_instance_id = instance_id
1045
validate_ec2_id(instance_id)
1046
instance_id = ec2utils.ec2_id_to_id(ec2_instance_id)
1047
instance = self.compute_api.get(context, instance_id)
1048
result = {'instance_id': ec2_instance_id}
1049
fn(instance, result)
1052
def describe_instances(self, context, **kwargs):
1053
# Optional DescribeInstances argument
1054
instance_id = kwargs.get('instance_id', None)
1055
return self._format_describe_instances(context,
1056
instance_id=instance_id)
1058
def describe_instances_v6(self, context, **kwargs):
1059
# Optional DescribeInstancesV6 argument
1060
instance_id = kwargs.get('instance_id', None)
1061
return self._format_describe_instances(context,
1062
instance_id=instance_id, use_v6=True)
1064
def _format_describe_instances(self, context, **kwargs):
1065
return {'reservationSet': self._format_instances(context, **kwargs)}
1067
def _format_run_instances(self, context, reservation_id):
1068
i = self._format_instances(context, reservation_id=reservation_id)
1072
def _format_terminate_instances(self, context, instance_id,
1075
for (ec2_id, previous_state) in zip(instance_id, previous_states):
1077
i['instanceId'] = ec2_id
1078
i['previousState'] = _state_description(previous_state['vm_state'],
1079
previous_state['shutdown_terminate'])
1081
internal_id = ec2utils.ec2_id_to_id(ec2_id)
1082
instance = self.compute_api.get(context, internal_id)
1083
i['shutdownState'] = _state_description(instance['vm_state'],
1084
instance['shutdown_terminate'])
1085
except exception.NotFound:
1086
i['shutdownState'] = _state_description(vm_states.DELETED,
1088
instances_set.append(i)
1089
return {'instancesSet': instances_set}
1091
def _format_instance_bdm(self, context, instance_id, root_device_name,
1093
"""Format InstanceBlockDeviceMappingResponseItemType"""
1094
root_device_type = 'instance-store'
1096
for bdm in db.block_device_mapping_get_all_by_instance(context,
1098
volume_id = bdm['volume_id']
1099
if (volume_id is None or bdm['no_device']):
1102
if (bdm['device_name'] == root_device_name and
1103
(bdm['snapshot_id'] or bdm['volume_id'])):
1104
assert not bdm['virtual_name']
1105
root_device_type = 'ebs'
1107
vol = self.volume_api.get(context, volume_id)
1108
LOG.debug(_("vol = %s\n"), vol)
1109
# TODO(yamahata): volume attach time
1110
ebs = {'volumeId': volume_id,
1111
'deleteOnTermination': bdm['delete_on_termination'],
1112
'attachTime': vol['attach_time'] or '-',
1113
'status': vol['status'], }
1114
res = {'deviceName': bdm['device_name'],
1119
result['blockDeviceMapping'] = mapping
1120
result['rootDeviceType'] = root_device_type
1123
def _format_instance_root_device_name(instance, result):
1124
result['rootDeviceName'] = (instance.get('root_device_name') or
1125
block_device.DEFAULT_ROOT_DEV_NAME)
1128
def _format_instance_type(instance, result):
1129
if instance['instance_type']:
1130
result['instanceType'] = instance['instance_type'].get('name')
1132
result['instanceType'] = None
1135
def _format_group_set(instance, result):
1136
security_group_names = []
1137
if instance.get('security_groups'):
1138
for security_group in instance['security_groups']:
1139
security_group_names.append(security_group['name'])
1140
result['groupSet'] = utils.convert_to_list_dict(
1141
security_group_names, 'groupId')
1143
def _format_instances(self, context, instance_id=None, use_v6=False,
1145
# TODO(termie): this method is poorly named as its name does not imply
1146
# that it will be making a variety of database calls
1147
# rather than simply formatting a bunch of instances that
1150
# NOTE(vish): instance_id is an optional list of ids to filter by
1153
for ec2_id in instance_id:
1154
internal_id = ec2utils.ec2_id_to_id(ec2_id)
1156
instance = self.compute_api.get(context, internal_id)
1157
except exception.NotFound:
1159
instances.append(instance)
1162
# always filter out deleted instances
1163
search_opts['deleted'] = False
1164
instances = self.compute_api.get_all(context,
1165
search_opts=search_opts,
1167
except exception.NotFound:
1169
for instance in instances:
1170
if not context.is_admin:
1171
if instance['image_ref'] == str(FLAGS.vpn_image_id):
1174
instance_id = instance['id']
1175
ec2_id = ec2utils.id_to_ec2_id(instance_id)
1176
i['instanceId'] = ec2_id
1177
image_uuid = instance['image_ref']
1178
i['imageId'] = ec2utils.glance_id_to_ec2_id(context, image_uuid)
1179
self._format_kernel_id(context, instance, i, 'kernelId')
1180
self._format_ramdisk_id(context, instance, i, 'ramdiskId')
1181
i['instanceState'] = _state_description(
1182
instance['vm_state'], instance['shutdown_terminate'])
1186
ip_info = ec2utils.get_ip_info_for_instance(context, instance)
1187
if ip_info['fixed_ips']:
1188
fixed_ip = ip_info['fixed_ips'][0]
1189
if ip_info['floating_ips']:
1190
floating_ip = ip_info['floating_ips'][0]
1191
if ip_info['fixed_ip6s']:
1192
i['dnsNameV6'] = ip_info['fixed_ip6s'][0]
1193
if FLAGS.ec2_private_dns_show_ip:
1194
i['privateDnsName'] = fixed_ip
1196
i['privateDnsName'] = instance['hostname']
1197
i['privateIpAddress'] = fixed_ip
1198
i['publicDnsName'] = floating_ip
1199
i['ipAddress'] = floating_ip or fixed_ip
1200
i['dnsName'] = i['publicDnsName'] or i['privateDnsName']
1201
i['keyName'] = instance['key_name']
1203
if context.is_admin:
1204
i['keyName'] = '%s (%s, %s)' % (i['keyName'],
1205
instance['project_id'],
1207
i['productCodesSet'] = utils.convert_to_list_dict([],
1209
self._format_instance_type(instance, i)
1210
i['launchTime'] = instance['created_at']
1211
i['amiLaunchIndex'] = instance['launch_index']
1212
self._format_instance_root_device_name(instance, i)
1213
self._format_instance_bdm(context, instance_id,
1214
i['rootDeviceName'], i)
1215
host = instance['host']
1216
services = db.service_get_all_by_host(context.elevated(), host)
1217
zone = ec2utils.get_availability_zone_by_host(services, host)
1218
i['placement'] = {'availabilityZone': zone}
1219
if instance['reservation_id'] not in reservations:
1221
r['reservationId'] = instance['reservation_id']
1222
r['ownerId'] = instance['project_id']
1223
self._format_group_set(instance, r)
1224
r['instancesSet'] = []
1225
reservations[instance['reservation_id']] = r
1226
reservations[instance['reservation_id']]['instancesSet'].append(i)
1228
return list(reservations.values())
1230
def describe_addresses(self, context, **kwargs):
1231
return self.format_addresses(context)
1233
def format_addresses(self, context):
1235
floaters = self.network_api.get_floating_ips_by_project(context)
1236
for floating_ip_ref in floaters:
1237
if floating_ip_ref['project_id'] is None:
1239
address = floating_ip_ref['address']
1241
if floating_ip_ref['fixed_ip_id']:
1242
fixed_id = floating_ip_ref['fixed_ip_id']
1243
fixed = self.network_api.get_fixed_ip(context, fixed_id)
1244
if fixed['instance_id'] is not None:
1245
ec2_id = ec2utils.id_to_ec2_id(fixed['instance_id'])
1246
address_rv = {'public_ip': address,
1247
'instance_id': ec2_id}
1248
if context.is_admin:
1249
details = "%s (%s)" % (address_rv['instance_id'],
1250
floating_ip_ref['project_id'])
1251
address_rv['instance_id'] = details
1252
addresses.append(address_rv)
1253
return {'addressesSet': addresses}
1255
def allocate_address(self, context, **kwargs):
1256
LOG.audit(_("Allocate address"), context=context)
1258
public_ip = self.network_api.allocate_floating_ip(context)
1259
return {'publicIp': public_ip}
1260
except rpc_common.RemoteError as ex:
1261
# NOTE(tr3buchet) - why does this block exist?
1262
if ex.exc_type == 'NoMoreFloatingIps':
1263
raise exception.NoMoreFloatingIps()
1267
def release_address(self, context, public_ip, **kwargs):
1268
LOG.audit(_("Release address %s"), public_ip, context=context)
1269
self.network_api.release_floating_ip(context, address=public_ip)
1270
return {'return': "true"}
1272
def associate_address(self, context, instance_id, public_ip, **kwargs):
1273
LOG.audit(_("Associate address %(public_ip)s to"
1274
" instance %(instance_id)s") % locals(), context=context)
1275
instance_id = ec2utils.ec2_id_to_id(instance_id)
1276
instance = self.compute_api.get(context, instance_id)
1277
self.compute_api.associate_floating_ip(context,
1280
return {'return': "true"}
1282
def disassociate_address(self, context, public_ip, **kwargs):
1283
LOG.audit(_("Disassociate address %s"), public_ip, context=context)
1284
self.network_api.disassociate_floating_ip(context, address=public_ip)
1285
return {'return': "true"}
1287
def run_instances(self, context, **kwargs):
1288
max_count = int(kwargs.get('max_count', 1))
1289
if kwargs.get('kernel_id'):
1290
kernel = self._get_image(context, kwargs['kernel_id'])
1291
kwargs['kernel_id'] = ec2utils.id_to_glance_id(context,
1293
if kwargs.get('ramdisk_id'):
1294
ramdisk = self._get_image(context, kwargs['ramdisk_id'])
1295
kwargs['ramdisk_id'] = ec2utils.id_to_glance_id(context,
1297
for bdm in kwargs.get('block_device_mapping', []):
1298
_parse_block_device_mapping(bdm)
1300
image = self._get_image(context, kwargs['image_id'])
1301
image_uuid = ec2utils.id_to_glance_id(context, image['id'])
1304
image_state = self._get_image_state(image)
1306
raise exception.ImageNotFound(image_id=kwargs['image_id'])
1308
if image_state != 'available':
1309
raise exception.EC2APIError(_('Image must be available'))
1311
(instances, resv_id) = self.compute_api.create(context,
1312
instance_type=instance_types.get_instance_type_by_name(
1313
kwargs.get('instance_type', None)),
1314
image_href=image_uuid,
1315
min_count=int(kwargs.get('min_count', max_count)),
1316
max_count=max_count,
1317
kernel_id=kwargs.get('kernel_id'),
1318
ramdisk_id=kwargs.get('ramdisk_id'),
1319
key_name=kwargs.get('key_name'),
1320
user_data=kwargs.get('user_data'),
1321
security_group=kwargs.get('security_group'),
1322
availability_zone=kwargs.get('placement', {}).get(
1323
'availability_zone'),
1324
block_device_mapping=kwargs.get('block_device_mapping', {}))
1325
return self._format_run_instances(context, resv_id)
1327
def terminate_instances(self, context, instance_id, **kwargs):
1328
"""Terminate each instance in instance_id, which is a list of ec2 ids.
1329
instance_id is a kwarg so its name cannot be modified."""
1330
LOG.debug(_("Going to start terminating instances"))
1331
previous_states = []
1332
for ec2_id in instance_id:
1333
validate_ec2_id(ec2_id)
1334
_instance_id = ec2utils.ec2_id_to_id(ec2_id)
1335
instance = self.compute_api.get(context, _instance_id)
1336
previous_states.append(instance)
1337
self.compute_api.delete(context, instance)
1338
return self._format_terminate_instances(context,
1342
def reboot_instances(self, context, instance_id, **kwargs):
1343
"""instance_id is a list of instance ids"""
1344
LOG.audit(_("Reboot instance %r"), instance_id, context=context)
1345
for ec2_id in instance_id:
1346
validate_ec2_id(ec2_id)
1347
_instance_id = ec2utils.ec2_id_to_id(ec2_id)
1348
instance = self.compute_api.get(context, _instance_id)
1349
self.compute_api.reboot(context, instance, 'HARD')
1352
def stop_instances(self, context, instance_id, **kwargs):
1353
"""Stop each instances in instance_id.
1354
Here instance_id is a list of instance ids"""
1355
LOG.debug(_("Going to stop instances"))
1356
for ec2_id in instance_id:
1357
validate_ec2_id(ec2_id)
1358
_instance_id = ec2utils.ec2_id_to_id(ec2_id)
1359
instance = self.compute_api.get(context, _instance_id)
1360
self.compute_api.stop(context, instance)
1363
def start_instances(self, context, instance_id, **kwargs):
1364
"""Start each instances in instance_id.
1365
Here instance_id is a list of instance ids"""
1366
LOG.debug(_("Going to start instances"))
1367
for ec2_id in instance_id:
1368
validate_ec2_id(ec2_id)
1369
_instance_id = ec2utils.ec2_id_to_id(ec2_id)
1370
instance = self.compute_api.get(context, _instance_id)
1371
self.compute_api.start(context, instance)
1374
def _get_image(self, context, ec2_id):
1376
internal_id = ec2utils.ec2_id_to_id(ec2_id)
1377
image = self.image_service.show(context, internal_id)
1378
except (exception.InvalidEc2Id, exception.ImageNotFound):
1380
return self.image_service.show_by_name(context, ec2_id)
1381
except exception.NotFound:
1382
raise exception.ImageNotFound(image_id=ec2_id)
1383
image_type = ec2_id.split('-')[0]
1384
if ec2utils.image_type(image.get('container_format')) != image_type:
1385
raise exception.ImageNotFound(image_id=ec2_id)
1388
def _format_image(self, image):
1389
"""Convert from format defined by GlanceImageService to S3 format."""
1391
image_type = ec2utils.image_type(image.get('container_format'))
1392
ec2_id = ec2utils.image_ec2_id(image.get('id'), image_type)
1393
name = image.get('name')
1394
i['imageId'] = ec2_id
1395
kernel_id = image['properties'].get('kernel_id')
1397
i['kernelId'] = ec2utils.image_ec2_id(kernel_id, 'aki')
1398
ramdisk_id = image['properties'].get('ramdisk_id')
1400
i['ramdiskId'] = ec2utils.image_ec2_id(ramdisk_id, 'ari')
1402
if FLAGS.auth_strategy == 'deprecated':
1403
i['imageOwnerId'] = image['properties'].get('project_id')
1405
i['imageOwnerId'] = image.get('owner')
1407
img_loc = image['properties'].get('image_location')
1409
i['imageLocation'] = img_loc
1411
i['imageLocation'] = "%s (%s)" % (img_loc, name)
1414
if not name and img_loc:
1415
# This should only occur for images registered with ec2 api
1416
# prior to that api populating the glance name
1419
i['imageState'] = self._get_image_state(image)
1420
i['description'] = image.get('description')
1421
display_mapping = {'aki': 'kernel',
1424
i['imageType'] = display_mapping.get(image_type)
1425
i['isPublic'] = not not image.get('is_public')
1426
i['architecture'] = image['properties'].get('architecture')
1428
properties = image['properties']
1429
root_device_name = block_device.properties_root_device_name(properties)
1430
root_device_type = 'instance-store'
1431
for bdm in properties.get('block_device_mapping', []):
1432
if (bdm.get('device_name') == root_device_name and
1433
('snapshot_id' in bdm or 'volume_id' in bdm) and
1434
not bdm.get('no_device')):
1435
root_device_type = 'ebs'
1436
i['rootDeviceName'] = (root_device_name or
1437
block_device.DEFAULT_ROOT_DEV_NAME)
1438
i['rootDeviceType'] = root_device_type
1440
_format_mappings(properties, i)
1444
def describe_images(self, context, image_id=None, **kwargs):
1445
# NOTE: image_id is a list!
1448
for ec2_id in image_id:
1450
image = self._get_image(context, ec2_id)
1451
except exception.NotFound:
1452
raise exception.ImageNotFound(image_id=ec2_id)
1453
images.append(image)
1455
images = self.image_service.detail(context)
1456
images = [self._format_image(i) for i in images]
1457
return {'imagesSet': images}
1459
def deregister_image(self, context, image_id, **kwargs):
1460
LOG.audit(_("De-registering image %s"), image_id, context=context)
1461
image = self._get_image(context, image_id)
1462
internal_id = image['id']
1463
self.image_service.delete(context, internal_id)
1464
return {'imageId': image_id}
1466
def _register_image(self, context, metadata):
1467
image = self.image_service.create(context, metadata)
1468
image_type = ec2utils.image_type(image.get('container_format'))
1469
image_id = ec2utils.image_ec2_id(image['id'], image_type)
1472
def register_image(self, context, image_location=None, **kwargs):
1473
if image_location is None and kwargs.get('name'):
1474
image_location = kwargs['name']
1475
if image_location is None:
1476
raise exception.EC2APIError(_('imageLocation is required'))
1478
metadata = {'properties': {'image_location': image_location}}
1480
if kwargs.get('name'):
1481
metadata['name'] = kwargs['name']
1483
metadata['name'] = image_location
1485
if 'root_device_name' in kwargs:
1486
metadata['properties']['root_device_name'] = kwargs.get(
1489
mappings = [_parse_block_device_mapping(bdm) for bdm in
1490
kwargs.get('block_device_mapping', [])]
1492
metadata['properties']['block_device_mapping'] = mappings
1494
image_id = self._register_image(context, metadata)
1495
msg = _("Registered image %(image_location)s with"
1496
" id %(image_id)s") % locals()
1497
LOG.audit(msg, context=context)
1498
return {'imageId': image_id}
1500
def describe_image_attribute(self, context, image_id, attribute, **kwargs):
1501
def _block_device_mapping_attribute(image, result):
1502
_format_mappings(image['properties'], result)
1504
def _launch_permission_attribute(image, result):
1505
result['launchPermission'] = []
1506
if image['is_public']:
1507
result['launchPermission'].append({'group': 'all'})
1509
def _root_device_name_attribute(image, result):
1510
_prop_root_dev_name = block_device.properties_root_device_name
1511
result['rootDeviceName'] = _prop_root_dev_name(image['properties'])
1512
if result['rootDeviceName'] is None:
1513
result['rootDeviceName'] = block_device.DEFAULT_ROOT_DEV_NAME
1515
supported_attributes = {
1516
'blockDeviceMapping': _block_device_mapping_attribute,
1517
'launchPermission': _launch_permission_attribute,
1518
'rootDeviceName': _root_device_name_attribute,
1521
fn = supported_attributes.get(attribute)
1523
raise exception.EC2APIError(_('attribute not supported: %s')
1526
image = self._get_image(context, image_id)
1527
except exception.NotFound:
1528
raise exception.ImageNotFound(image_id=image_id)
1530
result = {'imageId': image_id}
1534
def modify_image_attribute(self, context, image_id, attribute,
1535
operation_type, **kwargs):
1536
# TODO(devcamcar): Support users and groups other than 'all'.
1537
if attribute != 'launchPermission':
1538
raise exception.EC2APIError(_('attribute not supported: %s')
1540
if not 'user_group' in kwargs:
1541
raise exception.EC2APIError(_('user or group not specified'))
1542
if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all':
1543
raise exception.EC2APIError(_('only group "all" is supported'))
1544
if not operation_type in ['add', 'remove']:
1545
msg = _('operation_type must be add or remove')
1546
raise exception.EC2APIError(msg)
1547
LOG.audit(_("Updating image %s publicity"), image_id, context=context)
1550
image = self._get_image(context, image_id)
1551
except exception.NotFound:
1552
raise exception.ImageNotFound(image_id=image_id)
1553
internal_id = image['id']
1556
image['is_public'] = (operation_type == 'add')
1558
return self.image_service.update(context, internal_id, image)
1559
except exception.ImageNotAuthorized:
1560
msg = _('Not allowed to modify attributes for image %s')
1561
raise exception.EC2APIError(msg % image_id)
1563
def update_image(self, context, image_id, **kwargs):
1564
internal_id = ec2utils.ec2_id_to_id(image_id)
1565
result = self.image_service.update(context, internal_id, dict(kwargs))
1568
# TODO(yamahata): race condition
1569
# At the moment there is no way to prevent others from
1570
# manipulating instances/volumes/snapshots.
1571
# As other code doesn't take it into consideration, here we don't
1572
# care of it for now. Ostrich algorithm
1573
def create_image(self, context, instance_id, **kwargs):
1574
# NOTE(yamahata): name/description are ignored by register_image(),
1576
no_reboot = kwargs.get('no_reboot', False)
1577
validate_ec2_id(instance_id)
1578
ec2_instance_id = instance_id
1579
instance_id = ec2utils.ec2_id_to_id(ec2_instance_id)
1580
instance = self.compute_api.get(context, instance_id)
1582
# stop the instance if necessary
1583
restart_instance = False
1585
vm_state = instance['vm_state']
1587
# if the instance is in subtle state, refuse to proceed.
1588
if vm_state not in (vm_states.ACTIVE, vm_states.SHUTOFF,
1590
raise exception.InstanceNotRunning(instance_id=ec2_instance_id)
1592
if vm_state in (vm_states.ACTIVE, vm_states.SHUTOFF):
1593
restart_instance = True
1594
self.compute_api.stop(context, instance)
1596
# wait instance for really stopped
1597
start_time = time.time()
1598
while vm_state != vm_states.STOPPED:
1600
instance = self.compute_api.get(context, instance_id)
1601
vm_state = instance['vm_state']
1602
# NOTE(yamahata): timeout and error. 1 hour for now for safety.
1603
# Is it too short/long?
1604
# Or is there any better way?
1605
timeout = 1 * 60 * 60 * 60
1606
if time.time() > start_time + timeout:
1607
raise exception.EC2APIError(
1608
_('Couldn\'t stop instance with in %d sec') % timeout)
1610
src_image = self._get_image(context, instance['image_ref'])
1611
properties = src_image['properties']
1612
if instance['root_device_name']:
1613
properties['root_device_name'] = instance['root_device_name']
1616
bdms = db.block_device_mapping_get_all_by_instance(context,
1622
for attr in ('device_name', 'snapshot_id', 'volume_id',
1623
'volume_size', 'delete_on_termination', 'no_device',
1625
val = getattr(bdm, attr)
1629
volume_id = m.get('volume_id')
1630
if m.get('snapshot_id') and volume_id:
1631
# create snapshot based on volume_id
1632
volume = self.volume_api.get(context, volume_id)
1633
# NOTE(yamahata): Should we wait for snapshot creation?
1634
# Linux LVM snapshot creation completes in
1635
# short time, it doesn't matter for now.
1636
snapshot = self.volume_api.create_snapshot_force(
1637
context, volume, volume['display_name'],
1638
volume['display_description'])
1639
m['snapshot_id'] = snapshot['id']
1645
for m in _properties_get_mappings(properties):
1646
virtual_name = m['virtual']
1647
if virtual_name in ('ami', 'root'):
1650
assert block_device.is_swap_or_ephemeral(virtual_name)
1651
device_name = m['device']
1652
if device_name in [b['device_name'] for b in mapping
1653
if not b.get('no_device', False)]:
1656
# NOTE(yamahata): swap and ephemeral devices are specified in
1657
# AMI, but disabled for this instance by user.
1658
# So disable those device by no_device.
1659
mapping.append({'device_name': device_name, 'no_device': True})
1662
properties['block_device_mapping'] = mapping
1664
for attr in ('status', 'location', 'id'):
1665
src_image.pop(attr, None)
1667
image_id = self._register_image(context, src_image)
1669
if restart_instance:
1670
self.compute_api.start(context, instance_id=instance_id)
1672
return {'imageId': image_id}