~ubuntu-branches/ubuntu/raring/nova/raring-proposed

« back to all changes in this revision

Viewing changes to nova/api/ec2/cloud.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2013-01-11 13:06:56 UTC
  • mto: This revision was merged to the branch mainline in revision 96.
  • Revision ID: package-import@ubuntu.com-20130111130656-z9mceux6qpkqomma
Tags: upstream-2013.1~g2
ImportĀ upstreamĀ versionĀ 2013.1~g2

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
from nova.api.ec2 import ec2utils
29
29
from nova.api.ec2 import inst_state
30
30
from nova.api import validator
 
31
from nova import availability_zones
31
32
from nova import block_device
32
33
from nova import compute
33
34
from nova.compute import api as compute_api
41
42
from nova.openstack.common import log as logging
42
43
from nova.openstack.common import timeutils
43
44
from nova import quota
 
45
from nova import servicegroup
44
46
from nova import utils
45
47
from nova import volume
46
48
 
 
49
ec2_opts = [
 
50
    cfg.StrOpt('ec2_host',
 
51
               default='$my_ip',
 
52
               help='the ip of the ec2 api server'),
 
53
    cfg.StrOpt('ec2_dmz_host',
 
54
               default='$my_ip',
 
55
               help='the internal ip of the ec2 api server'),
 
56
    cfg.IntOpt('ec2_port',
 
57
               default=8773,
 
58
               help='the port of the ec2 api server'),
 
59
    cfg.StrOpt('ec2_scheme',
 
60
               default='http',
 
61
               help='the protocol to use when connecting to the ec2 api '
 
62
                    'server (http, https)'),
 
63
    cfg.StrOpt('ec2_path',
 
64
               default='/services/Cloud',
 
65
               help='the path prefix used to call the ec2 api server'),
 
66
    cfg.ListOpt('region_list',
 
67
                default=[],
 
68
                help='list of region=fqdn pairs separated by commas'),
 
69
]
47
70
 
48
71
CONF = cfg.CONF
49
 
CONF.import_opt('ec2_host', 'nova.config')
50
 
CONF.import_opt('ec2_path', 'nova.config')
51
 
CONF.import_opt('ec2_port', 'nova.config')
52
 
CONF.import_opt('ec2_scheme', 'nova.config')
53
 
CONF.import_opt('region_list', 'nova.config')
 
72
CONF.register_opts(ec2_opts)
 
73
CONF.import_opt('my_ip', 'nova.config')
54
74
CONF.import_opt('vpn_image_id', 'nova.config')
55
75
CONF.import_opt('vpn_key_suffix', 'nova.config')
 
76
CONF.import_opt('internal_service_availability_zone',
 
77
        'nova.availability_zones')
56
78
 
57
79
LOG = logging.getLogger(__name__)
58
80
 
61
83
 
62
84
def validate_ec2_id(val):
63
85
    if not validator.validate_str()(val):
64
 
        raise exception.InvalidInstanceIDMalformed(val)
 
86
        raise exception.InvalidInstanceIDMalformed(val=val)
65
87
    try:
66
88
        ec2utils.ec2_id_to_id(val)
67
89
    except exception.InvalidEc2Id:
68
 
        raise exception.InvalidInstanceIDMalformed(val)
 
90
        raise exception.InvalidInstanceIDMalformed(val=val)
69
91
 
70
92
 
71
93
# EC2 API can return the following values as documented in the EC2 API
112
134
    if ebs:
113
135
        ec2_id = ebs.pop('snapshot_id', None)
114
136
        if ec2_id:
115
 
            id = ec2utils.ec2_vol_id_to_uuid(ec2_id)
116
137
            if ec2_id.startswith('snap-'):
117
 
                bdm['snapshot_id'] = id
 
138
                bdm['snapshot_id'] = ec2utils.ec2_snap_id_to_uuid(ec2_id)
118
139
            elif ec2_id.startswith('vol-'):
119
 
                bdm['volume_id'] = id
 
140
                bdm['volume_id'] = ec2utils.ec2_vol_id_to_uuid(ec2_id)
120
141
            ebs.setdefault('delete_on_termination', True)
121
142
        bdm.update(ebs)
122
143
    return bdm
183
204
 
184
205
 
185
206
class CloudController(object):
186
 
    """ CloudController provides the critical dispatch between
 
207
    """CloudController provides the critical dispatch between
187
208
 inbound API calls through the endpoint and messages
188
209
 sent to the other nodes.
189
210
"""
196
217
                                   volume_api=self.volume_api,
197
218
                                   security_group_api=self.security_group_api)
198
219
        self.keypair_api = compute_api.KeypairAPI()
 
220
        self.servicegroup_api = servicegroup.API()
199
221
 
200
222
    def __str__(self):
201
223
        return 'CloudController'
202
224
 
 
225
    def _enforce_valid_instance_ids(self, context, instance_ids):
 
226
        # NOTE(mikal): Amazon's implementation of the EC2 API requires that
 
227
        # _all_ instance ids passed in be valid.
 
228
        instances = {}
 
229
        if instance_ids:
 
230
            for ec2_id in instance_ids:
 
231
                instance_uuid = ec2utils.ec2_inst_id_to_uuid(context, ec2_id)
 
232
                instance = self.compute_api.get(context, instance_uuid)
 
233
                instances[ec2_id] = instance
 
234
        return instances
 
235
 
203
236
    def _get_image_state(self, image):
204
237
        # NOTE(vish): fallback status if image_state isn't set
205
238
        state = image.get('status')
220
253
        """Return available and unavailable zones."""
221
254
        enabled_services = db.service_get_all(context, False)
222
255
        disabled_services = db.service_get_all(context, True)
 
256
        enabled_services = availability_zones.set_availability_zones(context,
 
257
                enabled_services)
 
258
        disabled_services = availability_zones.set_availability_zones(context,
 
259
                disabled_services)
223
260
 
224
261
        available_zones = []
225
 
        for zone in [service.availability_zone for service
 
262
        for zone in [service['availability_zone'] for service
226
263
                     in enabled_services]:
227
264
            if not zone in available_zones:
228
265
                available_zones.append(zone)
229
266
 
230
267
        not_available_zones = []
231
 
        for zone in [service.availability_zone for service in disabled_services
232
 
                     if not service['availability_zone'] in available_zones]:
233
 
            if not zone in not_available_zones:
 
268
        zones = [service['available_zones'] for service in disabled_services
 
269
                if service['available_zones'] not in available_zones]
 
270
        for zone in zones:
 
271
            if zone not in not_available_zones:
234
272
                not_available_zones.append(zone)
235
 
 
236
273
        return (available_zones, not_available_zones)
237
274
 
238
275
    def _describe_availability_zones(self, context, **kwargs):
241
278
 
242
279
        result = []
243
280
        for zone in available_zones:
 
281
            # Hide internal_service_availability_zone
 
282
            if zone == CONF.internal_service_availability_zone:
 
283
                continue
244
284
            result.append({'zoneName': zone,
245
285
                           'zoneState': "available"})
246
286
        for zone in not_available_zones:
254
294
 
255
295
        # Available services
256
296
        enabled_services = db.service_get_all(context, False)
 
297
        enabled_services = availability_zones.set_availability_zones(context,
 
298
                enabled_services)
257
299
        zone_hosts = {}
258
300
        host_services = {}
259
301
        for service in enabled_services:
260
 
            zone_hosts.setdefault(service.availability_zone, [])
261
 
            if not service.host in zone_hosts[service.availability_zone]:
262
 
                zone_hosts[service.availability_zone].append(service.host)
 
302
            zone_hosts.setdefault(service['availability_zone'], [])
 
303
            if not service['host'] in zone_hosts[service['availability_zone']]:
 
304
                zone_hosts[service['availability_zone']].append(
 
305
                    service['host'])
263
306
 
264
 
            host_services.setdefault(service.host, [])
265
 
            host_services[service.host].append(service)
 
307
            host_services.setdefault(service['availability_zone'] +
 
308
                    service['host'], [])
 
309
            host_services[service['availability_zone'] + service['host']].\
 
310
                    append(service)
266
311
 
267
312
        result = []
268
313
        for zone in available_zones:
272
317
                result.append({'zoneName': '|- %s' % host,
273
318
                               'zoneState': ''})
274
319
 
275
 
                for service in host_services[host]:
276
 
                    alive = utils.service_is_up(service)
 
320
                for service in host_services[zone + host]:
 
321
                    alive = self.servicegroup_api.service_is_up(service)
277
322
                    art = (alive and ":-)") or "XXX"
278
323
                    active = 'enabled'
279
324
                    if service['disabled']:
324
369
                snapshots.append(snapshot)
325
370
        else:
326
371
            snapshots = self.volume_api.get_all_snapshots(context)
327
 
        snapshots = [self._format_snapshot(context, s) for s in snapshots]
328
 
        return {'snapshotSet': snapshots}
 
372
 
 
373
        formatted_snapshots = []
 
374
        for s in snapshots:
 
375
            formatted = self._format_snapshot(context, s)
 
376
            if formatted:
 
377
                formatted_snapshots.append(formatted)
 
378
        return {'snapshotSet': formatted_snapshots}
329
379
 
330
380
    def _format_snapshot(self, context, snapshot):
 
381
        # NOTE(mikal): this is just a set of strings in cinder. If they
 
382
        # implement an enum, then we should move this code to use it. The
 
383
        # valid ec2 statuses are "pending", "completed", and "error".
 
384
        status_map = {'new': 'pending',
 
385
                      'creating': 'pending',
 
386
                      'available': 'completed',
 
387
                      'active': 'completed',
 
388
                      'deleting': 'pending',
 
389
                      'deleted': None,
 
390
                      'error': 'error'}
 
391
 
 
392
        mapped_status = status_map.get(snapshot['status'], snapshot['status'])
 
393
        if not mapped_status:
 
394
            return None
 
395
 
331
396
        s = {}
332
397
        s['snapshotId'] = ec2utils.id_to_ec2_snap_id(snapshot['id'])
333
398
        s['volumeId'] = ec2utils.id_to_ec2_vol_id(snapshot['volume_id'])
334
 
        s['status'] = snapshot['status']
 
399
        s['status'] = mapped_status
335
400
        s['startTime'] = snapshot['created_at']
336
401
        s['progress'] = snapshot['progress']
337
402
        s['ownerId'] = snapshot['project_id']
455
520
 
456
521
    def _format_security_group(self, context, group):
457
522
        g = {}
458
 
        g['groupDescription'] = group.description
459
 
        g['groupName'] = group.name
460
 
        g['ownerId'] = group.project_id
 
523
        g['groupDescription'] = group['description']
 
524
        g['groupName'] = group['name']
 
525
        g['ownerId'] = group['project_id']
461
526
        g['ipPermissions'] = []
462
 
        for rule in group.rules:
 
527
        for rule in group['rules']:
463
528
            r = {}
464
529
            r['groups'] = []
465
530
            r['ipRanges'] = []
466
 
            if rule.group_id:
467
 
                source_group = rule.grantee_group
468
 
                r['groups'] += [{'groupName': source_group.name,
469
 
                                 'userId': source_group.project_id}]
470
 
                if rule.protocol:
471
 
                    r['ipProtocol'] = rule.protocol.lower()
472
 
                    r['fromPort'] = rule.from_port
473
 
                    r['toPort'] = rule.to_port
 
531
            if rule['group_id']:
 
532
                source_group = rule['grantee_group']
 
533
                r['groups'] += [{'groupName': source_group['name'],
 
534
                                 'userId': source_group['project_id']}]
 
535
                if rule['protocol']:
 
536
                    r['ipProtocol'] = rule['protocol'].lower()
 
537
                    r['fromPort'] = rule['from_port']
 
538
                    r['toPort'] = rule['to_port']
474
539
                    g['ipPermissions'] += [dict(r)]
475
540
                else:
476
541
                    for protocol, min_port, max_port in (('icmp', -1, -1),
481
546
                        r['toPort'] = max_port
482
547
                        g['ipPermissions'] += [dict(r)]
483
548
            else:
484
 
                r['ipProtocol'] = rule.protocol
485
 
                r['fromPort'] = rule.from_port
486
 
                r['toPort'] = rule.to_port
487
 
                r['ipRanges'] += [{'cidrIp': rule.cidr}]
 
549
                r['ipProtocol'] = rule['protocol']
 
550
                r['fromPort'] = rule['from_port']
 
551
                r['toPort'] = rule['to_port']
 
552
                r['ipRanges'] += [{'cidrIp': rule['cidr']}]
488
553
                g['ipPermissions'] += [r]
489
554
        return g
490
555
 
592
657
            rulesvalues = self._rule_args_to_dict(context, values)
593
658
            self._validate_rulevalues(rulesvalues)
594
659
            for values_for_rule in rulesvalues:
595
 
                values_for_rule['parent_group_id'] = security_group.id
 
660
                values_for_rule['parent_group_id'] = security_group['id']
596
661
 
597
662
                rule_ids.append(self.security_group_api.rule_exists(
598
663
                                             security_group, values_for_rule))
625
690
            rulesvalues = self._rule_args_to_dict(context, values)
626
691
            self._validate_rulevalues(rulesvalues)
627
692
            for values_for_rule in rulesvalues:
628
 
                values_for_rule['parent_group_id'] = security_group.id
 
693
                values_for_rule['parent_group_id'] = security_group['id']
629
694
                if self.security_group_api.rule_exists(security_group,
630
695
                                                       values_for_rule):
631
696
                    err = _('%s - This rule already exists in group')
937
1002
    def describe_instances(self, context, **kwargs):
938
1003
        # Optional DescribeInstances argument
939
1004
        instance_id = kwargs.get('instance_id', None)
 
1005
        instances = self._enforce_valid_instance_ids(context, instance_id)
940
1006
        return self._format_describe_instances(context,
941
 
                instance_id=instance_id)
 
1007
                                               instance_id=instance_id,
 
1008
                                               instance_cache=instances)
942
1009
 
943
1010
    def describe_instances_v6(self, context, **kwargs):
944
1011
        # Optional DescribeInstancesV6 argument
945
1012
        instance_id = kwargs.get('instance_id', None)
 
1013
        instances = self._enforce_valid_instance_ids(context, instance_id)
946
1014
        return self._format_describe_instances(context,
947
 
                instance_id=instance_id, use_v6=True)
 
1015
                                               instance_id=instance_id,
 
1016
                                               instance_cache=instances,
 
1017
                                               use_v6=True)
948
1018
 
949
1019
    def _format_describe_instances(self, context, **kwargs):
950
1020
        return {'reservationSet': self._format_instances(context, **kwargs)}
1026
1096
            security_group_names, 'groupId')
1027
1097
 
1028
1098
    def _format_instances(self, context, instance_id=None, use_v6=False,
1029
 
            **search_opts):
 
1099
            instances_cache=None, **search_opts):
1030
1100
        # TODO(termie): this method is poorly named as its name does not imply
1031
1101
        #               that it will be making a variety of database calls
1032
1102
        #               rather than simply formatting a bunch of instances that
1033
1103
        #               were handed to it
1034
1104
        reservations = {}
 
1105
 
 
1106
        if not instances_cache:
 
1107
            instances_cache = {}
 
1108
 
1035
1109
        # NOTE(vish): instance_id is an optional list of ids to filter by
1036
1110
        if instance_id:
1037
1111
            instances = []
1038
1112
            for ec2_id in instance_id:
1039
 
                try:
1040
 
                    instance_uuid = ec2utils.ec2_inst_id_to_uuid(context,
1041
 
                                                                 ec2_id)
1042
 
                    instance = self.compute_api.get(context, instance_uuid)
1043
 
                except exception.NotFound:
1044
 
                    continue
1045
 
                instances.append(instance)
 
1113
                if ec2_id in instances_cache:
 
1114
                    instances.append(instances_cache[ec2_id])
 
1115
                else:
 
1116
                    try:
 
1117
                        instance_uuid = ec2utils.ec2_inst_id_to_uuid(context,
 
1118
                                                                     ec2_id)
 
1119
                        instance = self.compute_api.get(context, instance_uuid)
 
1120
                    except exception.NotFound:
 
1121
                        continue
 
1122
                    instances.append(instance)
1046
1123
        else:
1047
1124
            try:
1048
1125
                # always filter out deleted instances
1052
1129
                                                     sort_dir='asc')
1053
1130
            except exception.NotFound:
1054
1131
                instances = []
 
1132
 
1055
1133
        for instance in instances:
1056
1134
            if not context.is_admin:
1057
1135
                if instance['image_ref'] == str(CONF.vpn_image_id):