~yolanda.robla/nova/precise-security

« back to all changes in this revision

Viewing changes to .pc/CVE-2012-2654.patch/nova/api/ec2/cloud.py

  • Committer: Package Import Robot
  • Author(s): Steve Beattie
  • Date: 2012-05-29 15:25:43 UTC
  • Revision ID: package-import@ubuntu.com-20120529152543-vrjdr5u54gtw5l99
Tags: 2012.1-0ubuntu2.2
* SECURITY UPDATE: set security groups correctly if IP protocol is
  specified in upper/mixed case
  - debian/patches/CVE-2012-2654.patch: ensure protocols are in
    lowercase for the controllers

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
 
 
3
# Copyright 2010 United States Government as represented by the
 
4
# Administrator of the National Aeronautics and Space Administration.
 
5
# All Rights Reserved.
 
6
#
 
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
 
10
#
 
11
#         http://www.apache.org/licenses/LICENSE-2.0
 
12
#
 
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
 
17
#    under the License.
 
18
 
 
19
"""
 
20
Cloud Controller: Implementation of EC2 REST API calls, which are
 
21
dispatched to other nodes via AMQP RPC. State is via distributed
 
22
datastore.
 
23
"""
 
24
 
 
25
import base64
 
26
import re
 
27
import time
 
28
import urllib
 
29
 
 
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
 
38
from nova import db
 
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
 
48
 
 
49
 
 
50
FLAGS = flags.FLAGS
 
51
flags.DECLARE('dhcp_domain', 'nova.network.manager')
 
52
 
 
53
LOG = logging.getLogger(__name__)
 
54
 
 
55
 
 
56
def validate_ec2_id(val):
 
57
    if not validator.validate_str()(val):
 
58
        raise exception.InvalidInstanceIDMalformed(val)
 
59
    try:
 
60
        ec2utils.ec2_id_to_id(val)
 
61
    except exception.InvalidEc2Id:
 
62
        raise exception.InvalidInstanceIDMalformed(val)
 
63
 
 
64
 
 
65
def _gen_key(context, user_id, key_name):
 
66
    """Generate a key
 
67
 
 
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
 
72
    try:
 
73
        db.key_pair_get(context, user_id, key_name)
 
74
        raise exception.KeyPairExists(key_name=key_name)
 
75
    except exception.NotFound:
 
76
        pass
 
77
    private_key, public_key, fingerprint = crypto.generate_key_pair()
 
78
    key = {}
 
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}
 
85
 
 
86
 
 
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 |
 
91
# stopped 80
 
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,
 
106
}
 
107
 
 
108
 
 
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
 
114
    else:
 
115
        name = _STATE_DESCRIPTION_MAP.get(vm_state, vm_state)
 
116
 
 
117
    return {'code': inst_state.name_to_code(name),
 
118
            'name': name}
 
119
 
 
120
 
 
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
 
130
    """
 
131
    ebs = bdm.pop('ebs', None)
 
132
    if ebs:
 
133
        ec2_id = ebs.pop('snapshot_id', None)
 
134
        if ec2_id:
 
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)
 
141
        bdm.update(ebs)
 
142
    return bdm
 
143
 
 
144
 
 
145
def _properties_get_mappings(properties):
 
146
    return block_device.mappings_prepend_dev(properties.get('mappings', []))
 
147
 
 
148
 
 
149
def _format_block_device_mapping(bdm):
 
150
    """Contruct BlockDeviceMappingItemType
 
151
    {'device_name': '...', 'snapshot_id': , ...}
 
152
    => BlockDeviceMappingItemType
 
153
    """
 
154
    keys = (('deviceName', 'device_name'),
 
155
             ('virtualName', 'virtual_name'))
 
156
    item = {}
 
157
    for name, k in keys:
 
158
        if k in bdm:
 
159
            item[name] = bdm[k]
 
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'))
 
167
        ebs = {}
 
168
        for name, k in ebs_keys:
 
169
            if k in bdm:
 
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])
 
174
                else:
 
175
                    ebs[name] = bdm[k]
 
176
        assert 'snapshotId' in ebs
 
177
        item['ebs'] = ebs
 
178
    return item
 
179
 
 
180
 
 
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'])]
 
186
 
 
187
    block_device_mapping = [_format_block_device_mapping(bdm) for bdm in
 
188
                            properties.get('block_device_mapping', [])]
 
189
 
 
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']:
 
194
                del mappings[i]
 
195
                break
 
196
        mappings.append(bdm)
 
197
 
 
198
    # NOTE(yamahata): trim ebs.no_device == true. Is this necessary?
 
199
    mappings = [bdm for bdm in mappings if not (bdm.get('noDevice', False))]
 
200
 
 
201
    if mappings:
 
202
        result['blockDeviceMapping'] = mappings
 
203
 
 
204
 
 
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.
 
209
"""
 
210
    def __init__(self):
 
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)
 
217
 
 
218
    def __str__(self):
 
219
        return 'CloudController'
 
220
 
 
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':
 
225
            state = 'available'
 
226
        return image['properties'].get('image_state', state)
 
227
 
 
228
    def describe_availability_zones(self, context, **kwargs):
 
229
        if ('zone_name' in kwargs and
 
230
            'verbose' in kwargs['zone_name'] and
 
231
            context.is_admin):
 
232
            return self._describe_availability_zones_verbose(context,
 
233
                                                             **kwargs)
 
234
        else:
 
235
            return self._describe_availability_zones(context, **kwargs)
 
236
 
 
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)
 
241
        available_zones = []
 
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)
 
251
        result = []
 
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}
 
259
 
 
260
    def _describe_availability_zones_verbose(self, context, **kwargs):
 
261
        rv = {'availabilityZoneInfo': [{'zoneName': 'nova',
 
262
                                        'zoneState': 'available'}]}
 
263
 
 
264
        services = db.service_get_all(context, False)
 
265
        hosts = []
 
266
        for host in [service['host'] for service in services]:
 
267
            if not host in hosts:
 
268
                hosts.append(host)
 
269
        for host in hosts:
 
270
            rv['availabilityZoneInfo'].append({'zoneName': '|- %s' % host,
 
271
                                               'zoneState': ''})
 
272
            hsvcs = [service for service in services
 
273
                     if service['host'] == host]
 
274
            for svc in hsvcs:
 
275
                alive = utils.service_is_up(svc)
 
276
                art = (alive and ":-)") or "XXX"
 
277
                active = 'enabled'
 
278
                if svc['disabled']:
 
279
                    active = 'disabled'
 
280
                rv['availabilityZoneInfo'].append({
 
281
                        'zoneName': '| |- %s' % svc['binary'],
 
282
                        'zoneState': '%s %s %s' % (active, art,
 
283
                                                   svc['updated_at'])})
 
284
        return rv
 
285
 
 
286
    def describe_regions(self, context, region_name=None, **kwargs):
 
287
        if FLAGS.region_list:
 
288
            regions = []
 
289
            for region in FLAGS.region_list:
 
290
                name, _sep, host = region.partition('=')
 
291
                endpoint = '%s://%s:%s%s' % (FLAGS.ec2_scheme,
 
292
                                             host,
 
293
                                             FLAGS.ec2_port,
 
294
                                             FLAGS.ec2_path)
 
295
                regions.append({'regionName': name,
 
296
                                'regionEndpoint': endpoint})
 
297
        else:
 
298
            regions = [{'regionName': 'nova',
 
299
                        'regionEndpoint': '%s://%s:%s%s' % (FLAGS.ec2_scheme,
 
300
                                                            FLAGS.ec2_host,
 
301
                                                            FLAGS.ec2_port,
 
302
                                                            FLAGS.ec2_path)}]
 
303
        return {'regionInfo': regions}
 
304
 
 
305
    def describe_snapshots(self,
 
306
                           context,
 
307
                           snapshot_id=None,
 
308
                           owner=None,
 
309
                           restorable_by=None,
 
310
                           **kwargs):
 
311
        if snapshot_id:
 
312
            snapshots = []
 
313
            for ec2_id in snapshot_id:
 
314
                internal_id = ec2utils.ec2_id_to_id(ec2_id)
 
315
                snapshot = self.volume_api.get_snapshot(
 
316
                    context,
 
317
                    snapshot_id=internal_id)
 
318
                snapshots.append(snapshot)
 
319
        else:
 
320
            snapshots = self.volume_api.get_all_snapshots(context)
 
321
        snapshots = [self._format_snapshot(context, s) for s in snapshots]
 
322
        return {'snapshotSet': snapshots}
 
323
 
 
324
    def _format_snapshot(self, context, snapshot):
 
325
        s = {}
 
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']
 
334
        return s
 
335
 
 
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,
 
339
                  context=context)
 
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(
 
343
                context,
 
344
                volume,
 
345
                None,
 
346
                kwargs.get('description'))
 
347
        return self._format_snapshot(context, snapshot)
 
348
 
 
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)
 
353
        return True
 
354
 
 
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]
 
359
 
 
360
        result = []
 
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):
 
365
                result.append({
 
366
                    'keyName': key_pair['name'],
 
367
                    'keyFingerprint': key_pair['fingerprint'],
 
368
                })
 
369
 
 
370
        return {'keySet': result}
 
371
 
 
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)
 
378
 
 
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)
 
383
 
 
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
 
390
 
 
391
    def import_key_pair(self, context, key_name, public_key_material,
 
392
                        **kwargs):
 
393
        LOG.audit(_("Import key %s"), key_name, context=context)
 
394
        try:
 
395
            db.key_pair_get(context, context.user_id, key_name)
 
396
            raise exception.KeyPairExists(key_name=key_name)
 
397
        except exception.NotFound:
 
398
            pass
 
399
        public_key = base64.b64decode(public_key_material)
 
400
        fingerprint = crypto.generate_fingerprint(public_key)
 
401
        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}
 
409
 
 
410
    def delete_key_pair(self, context, key_name, **kwargs):
 
411
        LOG.audit(_("Delete key pair %s"), key_name, context=context)
 
412
        try:
 
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
 
416
            pass
 
417
        return True
 
418
 
 
419
    def describe_security_groups(self, context, group_name=None, group_id=None,
 
420
                                 **kwargs):
 
421
        self.compute_api.ensure_default_security_group(context)
 
422
        if group_name or group_id:
 
423
            groups = []
 
424
            if group_name:
 
425
                for name in group_name:
 
426
                    group = db.security_group_get_by_name(context,
 
427
                                                          context.project_id,
 
428
                                                          name)
 
429
                    groups.append(group)
 
430
            if group_id:
 
431
                for gid in group_id:
 
432
                    group = db.security_group_get(context, gid)
 
433
                    groups.append(group)
 
434
        elif context.is_admin:
 
435
            groups = db.security_group_get_all(context)
 
436
        else:
 
437
            groups = db.security_group_get_by_project(context,
 
438
                                                      context.project_id)
 
439
        groups = [self._format_security_group(context, g) for g in groups]
 
440
 
 
441
        return {'securityGroupInfo':
 
442
                list(sorted(groups,
 
443
                            key=lambda k: (k['ownerId'], k['groupName'])))}
 
444
 
 
445
    def _format_security_group(self, context, group):
 
446
        g = {}
 
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:
 
452
            r = {}
 
453
            r['groups'] = []
 
454
            r['ipRanges'] = []
 
455
            if rule.group_id:
 
456
                source_group = db.security_group_get(context, rule.group_id)
 
457
                r['groups'] += [{'groupName': source_group.name,
 
458
                                 'userId': source_group.project_id}]
 
459
                if rule.protocol:
 
460
                    r['ipProtocol'] = rule.protocol
 
461
                    r['fromPort'] = rule.from_port
 
462
                    r['toPort'] = rule.to_port
 
463
                    g['ipPermissions'] += [dict(r)]
 
464
                else:
 
465
                    for protocol, min_port, max_port in (('icmp', -1, -1),
 
466
                                                         ('tcp', 1, 65535),
 
467
                                                         ('udp', 1, 65535)):
 
468
                        r['ipProtocol'] = protocol
 
469
                        r['fromPort'] = min_port
 
470
                        r['toPort'] = max_port
 
471
                        g['ipPermissions'] += [dict(r)]
 
472
            else:
 
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]
 
478
        return g
 
479
 
 
480
    def _rule_args_to_dict(self, context, kwargs):
 
481
        rules = []
 
482
        if not 'groups' in kwargs and not 'ip_ranges' in kwargs:
 
483
            rule = self._rule_dict_last_step(context, **kwargs)
 
484
            if rule:
 
485
                rules.append(rule)
 
486
            return rules
 
487
        if 'ip_ranges' in kwargs:
 
488
            rules = self._cidr_args_split(kwargs)
 
489
        else:
 
490
            rules = [kwargs]
 
491
        finalset = []
 
492
        for rule in rules:
 
493
            if 'groups' in rule:
 
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)
 
498
            else:
 
499
                final = self._rule_dict_last_step(context, **rule)
 
500
                finalset.append(final)
 
501
        return finalset
 
502
 
 
503
    def _cidr_args_split(self, kwargs):
 
504
        cidr_args_split = []
 
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
 
512
 
 
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
 
527
 
 
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):
 
532
 
 
533
        values = {}
 
534
 
 
535
        if source_security_group_name:
 
536
            source_project_id = self._get_source_project_id(context,
 
537
                source_security_group_owner_id)
 
538
 
 
539
            source_security_group = db.security_group_get_by_name(
 
540
                    context.elevated(),
 
541
                    source_project_id,
 
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']
 
547
        elif cidr_ip:
 
548
            # If this fails, it throws an exception. This is what we want.
 
549
            cidr_ip = urllib.unquote(cidr_ip).decode()
 
550
 
 
551
            if not utils.is_valid_cidr(cidr_ip):
 
552
                # Raise exception for non-valid address
 
553
                raise exception.EC2APIError(_("Invalid CIDR"))
 
554
 
 
555
            values['cidr'] = cidr_ip
 
556
        else:
 
557
            values['cidr'] = '0.0.0.0/0'
 
558
 
 
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):
 
565
                from_port = -1
 
566
                to_port = -1
 
567
            elif (ip_proto_upper in ['TCP', 'UDP'] and from_port is None
 
568
                  and to_port is None):
 
569
                from_port = 1
 
570
                to_port = 65535
 
571
 
 
572
        if ip_protocol and from_port is not None and to_port is not None:
 
573
 
 
574
            ip_protocol = str(ip_protocol)
 
575
            try:
 
576
                # Verify integer conversions
 
577
                from_port = int(from_port)
 
578
                to_port = int(to_port)
 
579
            except ValueError:
 
580
                if ip_protocol.upper() == 'ICMP':
 
581
                    raise exception.InvalidInput(reason="Type and"
 
582
                         " Code must be integers for ICMP protocol type")
 
583
                else:
 
584
                    raise exception.InvalidInput(reason="To and From ports "
 
585
                          "must be integers")
 
586
 
 
587
            if ip_protocol.upper() not in ['TCP', 'UDP', 'ICMP']:
 
588
                raise exception.InvalidIpProtocol(protocol=ip_protocol)
 
589
 
 
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")
 
597
 
 
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")
 
604
 
 
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")
 
612
 
 
613
            values['protocol'] = ip_protocol
 
614
            values['from_port'] = from_port
 
615
            values['to_port'] = to_port
 
616
        else:
 
617
            # If cidr based filtering, protocol and ports are mandatory
 
618
            if 'cidr' in values:
 
619
                return None
 
620
 
 
621
        return values
 
622
 
 
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.
 
626
        """
 
627
        for rule in security_group.rules:
 
628
            is_duplicate = True
 
629
            keys = ('group_id', 'cidr', 'from_port', 'to_port', 'protocol')
 
630
            for key in keys:
 
631
                if rule.get(key) != values.get(key):
 
632
                    is_duplicate = False
 
633
                    break
 
634
            if is_duplicate:
 
635
                return rule['id']
 
636
        return False
 
637
 
 
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
 
645
        if group_name:
 
646
            security_group = db.security_group_get_by_name(context,
 
647
                                                           context.project_id,
 
648
                                                           group_name)
 
649
            if not security_group:
 
650
                raise notfound(security_group_id=group_name)
 
651
        if group_id:
 
652
            security_group = db.security_group_get(context, group_id)
 
653
            if not security_group:
 
654
                raise notfound(security_group_id=group_id)
 
655
 
 
656
        msg = _("Revoke security group ingress %s")
 
657
        LOG.audit(msg, security_group['name'], context=context)
 
658
        prevalues = []
 
659
        try:
 
660
            prevalues = kwargs['ip_permissions']
 
661
        except KeyError:
 
662
            prevalues.append(kwargs)
 
663
        rule_id = None
 
664
        rule_ids = []
 
665
        for values in prevalues:
 
666
            rulesvalues = self._rule_args_to_dict(context, values)
 
667
            if not rulesvalues:
 
668
                err = _("%s Not enough parameters to build a valid rule")
 
669
                raise exception.EC2APIError(err % rulesvalues)
 
670
 
 
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,
 
674
                                                           values_for_rule)
 
675
                if rule_id:
 
676
                    db.security_group_rule_destroy(context, rule_id)
 
677
                    rule_ids.append(rule_id)
 
678
        if rule_id:
 
679
            # NOTE(vish): we removed a rule, so refresh
 
680
            self.compute_api.trigger_security_group_rules_refresh(
 
681
                    context,
 
682
                    security_group_id=security_group['id'])
 
683
            self.sgh.trigger_security_group_rule_destroy_refresh(
 
684
                    context, rule_ids)
 
685
            return True
 
686
        raise exception.EC2APIError(_("No rule for the specified parameters."))
 
687
 
 
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
 
691
    #              is sketchy.
 
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
 
699
        if group_name:
 
700
            security_group = db.security_group_get_by_name(context,
 
701
                                                           context.project_id,
 
702
                                                           group_name)
 
703
            if not security_group:
 
704
                raise notfound(security_group_id=group_name)
 
705
        if group_id:
 
706
            security_group = db.security_group_get(context, group_id)
 
707
            if not security_group:
 
708
                raise notfound(security_group_id=group_id)
 
709
 
 
710
        msg = _("Authorize security group ingress %s")
 
711
        LOG.audit(msg, security_group['name'], context=context)
 
712
        prevalues = []
 
713
        try:
 
714
            prevalues = kwargs['ip_permissions']
 
715
        except KeyError:
 
716
            prevalues.append(kwargs)
 
717
        postvalues = []
 
718
        for values in prevalues:
 
719
            rulesvalues = self._rule_args_to_dict(context, values)
 
720
            if not rulesvalues:
 
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,
 
726
                                                    values_for_rule):
 
727
                    err = _('%s - This rule already exists in group')
 
728
                    raise exception.EC2APIError(err % values_for_rule)
 
729
                postvalues.append(values_for_rule)
 
730
 
 
731
        allowed = quota.allowed_security_group_rules(context,
 
732
                                                   security_group['id'],
 
733
                                                   1)
 
734
        if allowed < 1:
 
735
            msg = _("Quota exceeded, too many security group rules.")
 
736
            raise exception.EC2APIError(msg)
 
737
 
 
738
        rule_ids = []
 
739
        for values_for_rule in postvalues:
 
740
            security_group_rule = db.security_group_rule_create(
 
741
                    context,
 
742
                    values_for_rule)
 
743
            rule_ids.append(security_group_rule['id'])
 
744
 
 
745
        if postvalues:
 
746
            self.compute_api.trigger_security_group_rules_refresh(
 
747
                    context,
 
748
                    security_group_id=security_group['id'])
 
749
            self.sgh.trigger_security_group_rule_create_refresh(
 
750
                    context, rule_ids)
 
751
            return True
 
752
 
 
753
        raise exception.EC2APIError(_("No rule for the specified parameters."))
 
754
 
 
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(':')
 
759
 
 
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]
 
765
            else:
 
766
                source_project_id = source_parts[0]
 
767
        else:
 
768
            source_project_id = context.project_id
 
769
 
 
770
        return source_project_id
 
771
 
 
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)
 
783
 
 
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)
 
788
 
 
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)
 
794
 
 
795
        if quota.allowed_security_groups(context, 1) < 1:
 
796
            msg = _("Quota exceeded, too many security groups.")
 
797
            raise exception.EC2APIError(msg)
 
798
 
 
799
        group = {'user_id': context.user_id,
 
800
                 'project_id': context.project_id,
 
801
                 'name': group_name,
 
802
                 'description': group_description}
 
803
        group_ref = db.security_group_create(context, group)
 
804
 
 
805
        self.sgh.trigger_security_group_create_refresh(context, group)
 
806
 
 
807
        return {'securityGroupSet': [self._format_security_group(context,
 
808
                                                                 group_ref)]}
 
809
 
 
810
    def delete_security_group(self, context, group_name=None, group_id=None,
 
811
                              **kwargs):
 
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
 
816
        if group_name:
 
817
            security_group = db.security_group_get_by_name(context,
 
818
                                                           context.project_id,
 
819
                                                           group_name)
 
820
            if not security_group:
 
821
                raise notfound(security_group_id=group_name)
 
822
        elif group_id:
 
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)
 
830
 
 
831
        self.sgh.trigger_security_group_destroy_refresh(context,
 
832
                                                        security_group.id)
 
833
        return True
 
834
 
 
835
    def get_console_output(self, context, instance_id, **kwargs):
 
836
        LOG.audit(_("Get console output for instance %s"), instance_id,
 
837
                  context=context)
 
838
        # instance_id may be passed in as a list of instances
 
839
        if isinstance(instance_id, list):
 
840
            ec2_id = instance_id[0]
 
841
        else:
 
842
            ec2_id = instance_id
 
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)
 
847
        now = utils.utcnow()
 
848
        return {"InstanceId": ec2_id,
 
849
                "Timestamp": now,
 
850
                "output": base64.b64encode(output)}
 
851
 
 
852
    def describe_volumes(self, context, volume_id=None, **kwargs):
 
853
        if volume_id:
 
854
            volumes = []
 
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)
 
860
        else:
 
861
            volumes = self.volume_api.get_all(context)
 
862
        volumes = [self._format_volume(context, v) for v in volumes]
 
863
        return {'volumeSet': volumes}
 
864
 
 
865
    def _format_volume(self, context, volume):
 
866
        instance_ec2_id = None
 
867
        instance_data = 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'])
 
873
        v = {}
 
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']
 
879
        if context.is_admin:
 
880
            v['status'] = '%s (%s, %s, %s, %s)' % (
 
881
                volume['status'],
 
882
                volume['project_id'],
 
883
                volume['host'],
 
884
                instance_data,
 
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']}]
 
893
        else:
 
894
            v['attachmentSet'] = [{}]
 
895
        if volume.get('snapshot_id') is not None:
 
896
            v['snapshotId'] = ec2utils.id_to_ec2_snap_id(volume['snapshot_id'])
 
897
        else:
 
898
            v['snapshotId'] = None
 
899
 
 
900
        return v
 
901
 
 
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,
 
908
                      context=context)
 
909
        else:
 
910
            snapshot = None
 
911
            LOG.audit(_("Create volume of %s GB"), size, context=context)
 
912
 
 
913
        availability_zone = kwargs.get('availability_zone', None)
 
914
 
 
915
        volume = self.volume_api.create(context,
 
916
                                        size,
 
917
                                        None,
 
918
                                        None,
 
919
                                        snapshot,
 
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))
 
925
 
 
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)
 
929
 
 
930
        try:
 
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'))
 
935
 
 
936
        return True
 
937
 
 
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)
 
947
 
 
948
        try:
 
949
            self.compute_api.attach_volume(context, instance,
 
950
                                           volume_id, device)
 
951
        except exception.InvalidVolume:
 
952
            raise exception.EC2APIError(_('Attach Failed.'))
 
953
 
 
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)}
 
961
 
 
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)
 
967
 
 
968
        try:
 
969
            instance = self.compute_api.detach_volume(context,
 
970
                                                      volume_id=volume_id)
 
971
        except exception.InvalidVolume:
 
972
            raise exception.EC2APIError(_('Detach Volume Failed.'))
 
973
 
 
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)}
 
980
 
 
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 == '':
 
984
            return
 
985
        result[key] = ec2utils.glance_id_to_ec2_id(context, kernel_uuid, 'aki')
 
986
 
 
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 == '':
 
990
            return
 
991
        result[key] = ec2utils.glance_id_to_ec2_id(context, ramdisk_uuid,
 
992
                                                   'ari')
 
993
 
 
994
    def describe_instance_attribute(self, context, instance_id, attribute,
 
995
                                    **kwargs):
 
996
        def _unsupported_attribute(instance, result):
 
997
            raise exception.EC2APIError(_('attribute not supported: %s') %
 
998
                                     attribute)
 
999
 
 
1000
        def _format_attr_block_device_mapping(instance, result):
 
1001
            tmp = {}
 
1002
            self._format_instance_root_device_name(instance, tmp)
 
1003
            self._format_instance_bdm(context, instance_id,
 
1004
                                      tmp['rootDeviceName'], result)
 
1005
 
 
1006
        def _format_attr_disable_api_termination(instance, result):
 
1007
            result['disableApiTermination'] = instance['disable_terminate']
 
1008
 
 
1009
        def _format_attr_group_set(instance, result):
 
1010
            CloudController._format_group_set(instance, result)
 
1011
 
 
1012
        def _format_attr_instance_initiated_shutdown_behavior(instance,
 
1013
                                                               result):
 
1014
            if instance['shutdown_terminate']:
 
1015
                result['instanceInitiatedShutdownBehavior'] = 'terminate'
 
1016
            else:
 
1017
                result['instanceInitiatedShutdownBehavior'] = 'stop'
 
1018
 
 
1019
        def _format_attr_instance_type(instance, result):
 
1020
            self._format_instance_type(instance, result)
 
1021
 
 
1022
        def _format_attr_kernel(instance, result):
 
1023
            self._format_kernel_id(context, instance, result, 'kernel')
 
1024
 
 
1025
        def _format_attr_ramdisk(instance, result):
 
1026
            self._format_ramdisk_id(context, instance, result, 'ramdisk')
 
1027
 
 
1028
        def _format_attr_root_device_name(instance, result):
 
1029
            self._format_instance_root_device_name(instance, result)
 
1030
 
 
1031
        def _format_attr_source_dest_check(instance, result):
 
1032
            _unsupported_attribute(instance, result)
 
1033
 
 
1034
        def _format_attr_user_data(instance, result):
 
1035
            result['userData'] = base64.b64decode(instance['user_data'])
 
1036
 
 
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,
 
1049
            }
 
1050
 
 
1051
        fn = attribute_formatter.get(attribute)
 
1052
        if fn is None:
 
1053
            raise exception.EC2APIError(
 
1054
                _('attribute not supported: %s') % attribute)
 
1055
 
 
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)
 
1062
        return result
 
1063
 
 
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)
 
1069
 
 
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)
 
1075
 
 
1076
    def _format_describe_instances(self, context, **kwargs):
 
1077
        return {'reservationSet': self._format_instances(context, **kwargs)}
 
1078
 
 
1079
    def _format_run_instances(self, context, reservation_id):
 
1080
        i = self._format_instances(context, reservation_id=reservation_id)
 
1081
        assert len(i) == 1
 
1082
        return i[0]
 
1083
 
 
1084
    def _format_terminate_instances(self, context, instance_id,
 
1085
                                    previous_states):
 
1086
        instances_set = []
 
1087
        for (ec2_id, previous_state) in zip(instance_id, previous_states):
 
1088
            i = {}
 
1089
            i['instanceId'] = ec2_id
 
1090
            i['previousState'] = _state_description(previous_state['vm_state'],
 
1091
                                        previous_state['shutdown_terminate'])
 
1092
            try:
 
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,
 
1099
                                                        True)
 
1100
            instances_set.append(i)
 
1101
        return {'instancesSet': instances_set}
 
1102
 
 
1103
    def _format_instance_bdm(self, context, instance_id, root_device_name,
 
1104
                             result):
 
1105
        """Format InstanceBlockDeviceMappingResponseItemType"""
 
1106
        root_device_type = 'instance-store'
 
1107
        mapping = []
 
1108
        for bdm in db.block_device_mapping_get_all_by_instance(context,
 
1109
                                                               instance_id):
 
1110
            volume_id = bdm['volume_id']
 
1111
            if (volume_id is None or bdm['no_device']):
 
1112
                continue
 
1113
 
 
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'
 
1118
 
 
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'],
 
1127
                   'ebs': ebs, }
 
1128
            mapping.append(res)
 
1129
 
 
1130
        if mapping:
 
1131
            result['blockDeviceMapping'] = mapping
 
1132
        result['rootDeviceType'] = root_device_type
 
1133
 
 
1134
    @staticmethod
 
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)
 
1138
 
 
1139
    @staticmethod
 
1140
    def _format_instance_type(instance, result):
 
1141
        if instance['instance_type']:
 
1142
            result['instanceType'] = instance['instance_type'].get('name')
 
1143
        else:
 
1144
            result['instanceType'] = None
 
1145
 
 
1146
    @staticmethod
 
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')
 
1154
 
 
1155
    def _format_instances(self, context, instance_id=None, use_v6=False,
 
1156
            **search_opts):
 
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
 
1160
        #               were handed to it
 
1161
        reservations = {}
 
1162
        # NOTE(vish): instance_id is an optional list of ids to filter by
 
1163
        if instance_id:
 
1164
            instances = []
 
1165
            for ec2_id in instance_id:
 
1166
                internal_id = ec2utils.ec2_id_to_id(ec2_id)
 
1167
                try:
 
1168
                    instance = self.compute_api.get(context, internal_id)
 
1169
                except exception.NotFound:
 
1170
                    continue
 
1171
                instances.append(instance)
 
1172
        else:
 
1173
            try:
 
1174
                # always filter out deleted instances
 
1175
                search_opts['deleted'] = False
 
1176
                instances = self.compute_api.get_all(context,
 
1177
                                                     search_opts=search_opts,
 
1178
                                                     sort_dir='asc')
 
1179
            except exception.NotFound:
 
1180
                instances = []
 
1181
        for instance in instances:
 
1182
            if not context.is_admin:
 
1183
                if instance['image_ref'] == str(FLAGS.vpn_image_id):
 
1184
                    continue
 
1185
            i = {}
 
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'])
 
1195
 
 
1196
            fixed_ip = None
 
1197
            floating_ip = None
 
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
 
1207
            else:
 
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']
 
1214
 
 
1215
            if context.is_admin:
 
1216
                i['keyName'] = '%s (%s, %s)' % (i['keyName'],
 
1217
                    instance['project_id'],
 
1218
                    instance['host'])
 
1219
            i['productCodesSet'] = utils.convert_to_list_dict([],
 
1220
                                                              'product_codes')
 
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:
 
1232
                r = {}
 
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)
 
1239
 
 
1240
        return list(reservations.values())
 
1241
 
 
1242
    def describe_addresses(self, context, **kwargs):
 
1243
        return self.format_addresses(context)
 
1244
 
 
1245
    def format_addresses(self, context):
 
1246
        addresses = []
 
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:
 
1250
                continue
 
1251
            address = floating_ip_ref['address']
 
1252
            ec2_id = None
 
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}
 
1266
 
 
1267
    def allocate_address(self, context, **kwargs):
 
1268
        LOG.audit(_("Allocate address"), context=context)
 
1269
        try:
 
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()
 
1276
            else:
 
1277
                raise
 
1278
 
 
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"}
 
1283
 
 
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,
 
1290
                                               instance,
 
1291
                                               address=public_ip)
 
1292
        return {'return': "true"}
 
1293
 
 
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"}
 
1298
 
 
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,
 
1304
                                                           kernel['id'])
 
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,
 
1308
                                                            ramdisk['id'])
 
1309
        for bdm in kwargs.get('block_device_mapping', []):
 
1310
            _parse_block_device_mapping(bdm)
 
1311
 
 
1312
        image = self._get_image(context, kwargs['image_id'])
 
1313
        image_uuid = ec2utils.id_to_glance_id(context, image['id'])
 
1314
 
 
1315
        if image:
 
1316
            image_state = self._get_image_state(image)
 
1317
        else:
 
1318
            raise exception.ImageNotFound(image_id=kwargs['image_id'])
 
1319
 
 
1320
        if image_state != 'available':
 
1321
            raise exception.EC2APIError(_('Image must be available'))
 
1322
 
 
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)
 
1338
 
 
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,
 
1351
                                                instance_id,
 
1352
                                                previous_states)
 
1353
 
 
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')
 
1362
        return True
 
1363
 
 
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)
 
1373
        return True
 
1374
 
 
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)
 
1384
        return True
 
1385
 
 
1386
    def _get_image(self, context, ec2_id):
 
1387
        try:
 
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):
 
1391
            try:
 
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)
 
1398
        return image
 
1399
 
 
1400
    def _format_image(self, image):
 
1401
        """Convert from format defined by GlanceImageService to S3 format."""
 
1402
        i = {}
 
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')
 
1408
        if kernel_id:
 
1409
            i['kernelId'] = ec2utils.image_ec2_id(kernel_id, 'aki')
 
1410
        ramdisk_id = image['properties'].get('ramdisk_id')
 
1411
        if ramdisk_id:
 
1412
            i['ramdiskId'] = ec2utils.image_ec2_id(ramdisk_id, 'ari')
 
1413
 
 
1414
        if FLAGS.auth_strategy == 'deprecated':
 
1415
            i['imageOwnerId'] = image['properties'].get('project_id')
 
1416
        else:
 
1417
            i['imageOwnerId'] = image.get('owner')
 
1418
 
 
1419
        img_loc = image['properties'].get('image_location')
 
1420
        if img_loc:
 
1421
            i['imageLocation'] = img_loc
 
1422
        else:
 
1423
            i['imageLocation'] = "%s (%s)" % (img_loc, name)
 
1424
 
 
1425
        i['name'] = 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
 
1429
            i['name'] = img_loc
 
1430
 
 
1431
        i['imageState'] = self._get_image_state(image)
 
1432
        i['description'] = image.get('description')
 
1433
        display_mapping = {'aki': 'kernel',
 
1434
                           'ari': 'ramdisk',
 
1435
                           'ami': 'machine'}
 
1436
        i['imageType'] = display_mapping.get(image_type)
 
1437
        i['isPublic'] = not not image.get('is_public')
 
1438
        i['architecture'] = image['properties'].get('architecture')
 
1439
 
 
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
 
1451
 
 
1452
        _format_mappings(properties, i)
 
1453
 
 
1454
        return i
 
1455
 
 
1456
    def describe_images(self, context, image_id=None, **kwargs):
 
1457
        # NOTE: image_id is a list!
 
1458
        if image_id:
 
1459
            images = []
 
1460
            for ec2_id in image_id:
 
1461
                try:
 
1462
                    image = self._get_image(context, ec2_id)
 
1463
                except exception.NotFound:
 
1464
                    raise exception.ImageNotFound(image_id=ec2_id)
 
1465
                images.append(image)
 
1466
        else:
 
1467
            images = self.image_service.detail(context)
 
1468
        images = [self._format_image(i) for i in images]
 
1469
        return {'imagesSet': images}
 
1470
 
 
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}
 
1477
 
 
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)
 
1482
        return image_id
 
1483
 
 
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'))
 
1489
 
 
1490
        metadata = {'properties': {'image_location': image_location}}
 
1491
 
 
1492
        if kwargs.get('name'):
 
1493
            metadata['name'] = kwargs['name']
 
1494
        else:
 
1495
            metadata['name'] = image_location
 
1496
 
 
1497
        if 'root_device_name' in kwargs:
 
1498
            metadata['properties']['root_device_name'] = kwargs.get(
 
1499
                                                         'root_device_name')
 
1500
 
 
1501
        mappings = [_parse_block_device_mapping(bdm) for bdm in
 
1502
                    kwargs.get('block_device_mapping', [])]
 
1503
        if mappings:
 
1504
            metadata['properties']['block_device_mapping'] = mappings
 
1505
 
 
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}
 
1511
 
 
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)
 
1515
 
 
1516
        def _launch_permission_attribute(image, result):
 
1517
            result['launchPermission'] = []
 
1518
            if image['is_public']:
 
1519
                result['launchPermission'].append({'group': 'all'})
 
1520
 
 
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
 
1526
 
 
1527
        supported_attributes = {
 
1528
            'blockDeviceMapping': _block_device_mapping_attribute,
 
1529
            'launchPermission': _launch_permission_attribute,
 
1530
            'rootDeviceName': _root_device_name_attribute,
 
1531
            }
 
1532
 
 
1533
        fn = supported_attributes.get(attribute)
 
1534
        if fn is None:
 
1535
            raise exception.EC2APIError(_('attribute not supported: %s')
 
1536
                                     % attribute)
 
1537
        try:
 
1538
            image = self._get_image(context, image_id)
 
1539
        except exception.NotFound:
 
1540
            raise exception.ImageNotFound(image_id=image_id)
 
1541
 
 
1542
        result = {'imageId': image_id}
 
1543
        fn(image, result)
 
1544
        return result
 
1545
 
 
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')
 
1551
                                     % attribute)
 
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)
 
1560
 
 
1561
        try:
 
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']
 
1566
        del(image['id'])
 
1567
 
 
1568
        image['is_public'] = (operation_type == 'add')
 
1569
        try:
 
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)
 
1574
 
 
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))
 
1578
        return result
 
1579
 
 
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(),
 
1587
        #                 do so here
 
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)
 
1593
 
 
1594
        # stop the instance if necessary
 
1595
        restart_instance = False
 
1596
        if not no_reboot:
 
1597
            vm_state = instance['vm_state']
 
1598
 
 
1599
            # if the instance is in subtle state, refuse to proceed.
 
1600
            if vm_state not in (vm_states.ACTIVE, vm_states.SHUTOFF,
 
1601
                                vm_states.STOPPED):
 
1602
                raise exception.InstanceNotRunning(instance_id=ec2_instance_id)
 
1603
 
 
1604
            if vm_state in (vm_states.ACTIVE, vm_states.SHUTOFF):
 
1605
                restart_instance = True
 
1606
                self.compute_api.stop(context, instance)
 
1607
 
 
1608
            # wait instance for really stopped
 
1609
            start_time = time.time()
 
1610
            while vm_state != vm_states.STOPPED:
 
1611
                time.sleep(1)
 
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)
 
1621
 
 
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']
 
1626
 
 
1627
        mapping = []
 
1628
        bdms = db.block_device_mapping_get_all_by_instance(context,
 
1629
                                                           instance_id)
 
1630
        for bdm in bdms:
 
1631
            if bdm.no_device:
 
1632
                continue
 
1633
            m = {}
 
1634
            for attr in ('device_name', 'snapshot_id', 'volume_id',
 
1635
                         'volume_size', 'delete_on_termination', 'no_device',
 
1636
                         'virtual_name'):
 
1637
                val = getattr(bdm, attr)
 
1638
                if val is not None:
 
1639
                    m[attr] = val
 
1640
 
 
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']
 
1652
                del m['volume_id']
 
1653
 
 
1654
            if m:
 
1655
                mapping.append(m)
 
1656
 
 
1657
        for m in _properties_get_mappings(properties):
 
1658
            virtual_name = m['virtual']
 
1659
            if virtual_name in ('ami', 'root'):
 
1660
                continue
 
1661
 
 
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)]:
 
1666
                continue
 
1667
 
 
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})
 
1672
 
 
1673
        if mapping:
 
1674
            properties['block_device_mapping'] = mapping
 
1675
 
 
1676
        for attr in ('status', 'location', 'id'):
 
1677
            src_image.pop(attr, None)
 
1678
 
 
1679
        image_id = self._register_image(context, src_image)
 
1680
 
 
1681
        if restart_instance:
 
1682
            self.compute_api.start(context, instance_id=instance_id)
 
1683
 
 
1684
        return {'imageId': image_id}