diff options
| author | Michael Gundlach <michael.gundlach@rackspace.com> | 2010-10-13 16:36:33 -0400 |
|---|---|---|
| committer | Michael Gundlach <michael.gundlach@rackspace.com> | 2010-10-13 16:36:33 -0400 |
| commit | beebed574bba9ef0e7bbeedd554a13ad5ded375a (patch) | |
| tree | 3b35c1e18dc6604023474f871903b0d12eabb02e /nova/api | |
| parent | 79a2c349ca5772a69b6f7f28a768e711d6db1524 (diff) | |
| parent | a4aa6725be683e7e1f35df1e54069b755d19551b (diff) | |
Merge from trunk
Diffstat (limited to 'nova/api')
| -rw-r--r-- | nova/api/ec2/__init__.py | 2 | ||||
| -rw-r--r-- | nova/api/ec2/cloud.py | 258 | ||||
| -rw-r--r-- | nova/api/openstack/servers.py | 35 |
3 files changed, 257 insertions, 38 deletions
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 6b538a7f1..6e771f064 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -142,6 +142,8 @@ class Authorizer(wsgi.Middleware): 'CreateKeyPair': ['all'], 'DeleteKeyPair': ['all'], 'DescribeSecurityGroups': ['all'], + 'AuthorizeSecurityGroupIngress': ['netadmin'], + 'RevokeSecurityGroupIngress': ['netadmin'], 'CreateSecurityGroup': ['netadmin'], 'DeleteSecurityGroup': ['netadmin'], 'GetConsoleOutput': ['projectmanager', 'sysadmin'], diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 619c1a4b0..a7693cadd 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -28,6 +28,8 @@ import logging import os import time +import IPy + from nova import crypto from nova import db from nova import exception @@ -43,6 +45,7 @@ from nova.api.ec2 import images FLAGS = flags.FLAGS flags.DECLARE('storage_availability_zone', 'nova.volume.manager') +InvalidInputException = exception.InvalidInputException class QuotaError(exception.ApiError): """Quota Exceeeded""" @@ -127,6 +130,15 @@ class CloudController(object): result[key] = [line] return result + def _trigger_refresh_security_group(self, security_group): + nodes = set([instance['host'] for instance in security_group.instances + if instance['host'] is not None]) + for node in nodes: + rpc.call('%s.%s' % (FLAGS.compute_topic, node), + { "method": "refresh_security_group", + "args": { "context": None, + "security_group_id": security_group.id}}) + def get_metadata(self, address): instance_ref = db.fixed_ip_get_instance(None, address) if instance_ref is None: @@ -246,28 +258,210 @@ class CloudController(object): pass return True - def describe_security_groups(self, context, group_names, **kwargs): - groups = {'securityGroupSet': []} + def describe_security_groups(self, context, group_name=None, **kwargs): + self._ensure_default_security_group(context) + if context.user.is_admin(): + groups = db.security_group_get_all(context) + else: + groups = db.security_group_get_by_project(context, + context.project.id) + groups = [self._format_security_group(context, g) for g in groups] + if not group_name is None: + groups = [g for g in groups if g.name in group_name] + + return {'securityGroupInfo': groups } + + def _format_security_group(self, context, group): + g = {} + g['groupDescription'] = group.description + g['groupName'] = group.name + g['ownerId'] = group.project_id + g['ipPermissions'] = [] + for rule in group.rules: + r = {} + r['ipProtocol'] = rule.protocol + r['fromPort'] = rule.from_port + r['toPort'] = rule.to_port + r['groups'] = [] + r['ipRanges'] = [] + if rule.group_id: + source_group = db.security_group_get(context, rule.group_id) + r['groups'] += [{'groupName': source_group.name, + 'userId': source_group.project_id}] + else: + r['ipRanges'] += [{'cidrIp': rule.cidr}] + g['ipPermissions'] += [r] + return g + + + def _authorize_revoke_rule_args_to_dict(self, context, + to_port=None, from_port=None, + ip_protocol=None, cidr_ip=None, + user_id=None, + source_security_group_name=None, + source_security_group_owner_id=None): + + values = {} + + if source_security_group_name: + source_project_id = self._get_source_project_id(context, + source_security_group_owner_id) + + source_security_group = \ + db.security_group_get_by_name(context, + source_project_id, + source_security_group_name) + values['group_id'] = source_security_group['id'] + elif cidr_ip: + # If this fails, it throws an exception. This is what we want. + IPy.IP(cidr_ip) + values['cidr'] = cidr_ip + else: + values['cidr'] = '0.0.0.0/0' + + if ip_protocol and from_port and to_port: + from_port = int(from_port) + to_port = int(to_port) + ip_protocol = str(ip_protocol) + + if ip_protocol.upper() not in ['TCP','UDP','ICMP']: + raise InvalidInputException('%s is not a valid ipProtocol' % + (ip_protocol,)) + if ((min(from_port, to_port) < -1) or + (max(from_port, to_port) > 65535)): + raise InvalidInputException('Invalid port range') + + values['protocol'] = ip_protocol + values['from_port'] = from_port + values['to_port'] = to_port + else: + # If cidr based filtering, protocol and ports are mandatory + if 'cidr' in values: + return None + + return values + - # Stubbed for now to unblock other things. - return groups + def _security_group_rule_exists(self, security_group, values): + """Indicates whether the specified rule values are already + defined in the given security group. + """ + for rule in security_group.rules: + if 'group_id' in values: + if rule['group_id'] == values['group_id']: + return True + else: + is_duplicate = True + for key in ('cidr', 'from_port', 'to_port', 'protocol'): + if rule[key] != values[key]: + is_duplicate = False + break + if is_duplicate: + return True + return False + + + def revoke_security_group_ingress(self, context, group_name, **kwargs): + self._ensure_default_security_group(context) + security_group = db.security_group_get_by_name(context, + context.project.id, + group_name) + + criteria = self._authorize_revoke_rule_args_to_dict(context, **kwargs) + if criteria == None: + raise exception.ApiError("No rule for the specified parameters.") + + for rule in security_group.rules: + match = True + for (k,v) in criteria.iteritems(): + if getattr(rule, k, False) != v: + match = False + if match: + db.security_group_rule_destroy(context, rule['id']) + self._trigger_refresh_security_group(security_group) + return True + raise exception.ApiError("No rule for the specified parameters.") + + # TODO(soren): This has only been tested with Boto as the client. + # Unfortunately, it seems Boto is using an old API + # for these operations, so support for newer API versions + # is sketchy. + def authorize_security_group_ingress(self, context, group_name, **kwargs): + self._ensure_default_security_group(context) + security_group = db.security_group_get_by_name(context, + context.project.id, + group_name) + + values = self._authorize_revoke_rule_args_to_dict(context, **kwargs) + values['parent_group_id'] = security_group.id + + if self._security_group_rule_exists(security_group, values): + raise exception.ApiError('This rule already exists in group %s' % + group_name) + + security_group_rule = db.security_group_rule_create(context, values) + + self._trigger_refresh_security_group(security_group) - def create_security_group(self, context, group_name, **kwargs): return True + + def _get_source_project_id(self, context, source_security_group_owner_id): + if source_security_group_owner_id: + # Parse user:project for source group. + source_parts = source_security_group_owner_id.split(':') + + # If no project name specified, assume it's same as user name. + # Since we're looking up by project name, the user name is not + # used here. It's only read for EC2 API compatibility. + if len(source_parts) == 2: + source_project_id = source_parts[1] + else: + source_project_id = source_parts[0] + else: + source_project_id = context.project.id + + return source_project_id + + + def create_security_group(self, context, group_name, group_description): + self._ensure_default_security_group(context) + if db.security_group_exists(context, context.project.id, group_name): + raise exception.ApiError('group %s already exists' % group_name) + + group = {'user_id' : context.user.id, + 'project_id': context.project.id, + 'name': group_name, + 'description': group_description} + group_ref = db.security_group_create(context, group) + + return {'securityGroupSet': [self._format_security_group(context, + group_ref)]} + + def delete_security_group(self, context, group_name, **kwargs): + security_group = db.security_group_get_by_name(context, + context.project.id, + group_name) + db.security_group_destroy(context, security_group.id) return True + def get_console_output(self, context, instance_id, **kwargs): # instance_id is passed in as a list of instances ec2_id = instance_id[0] internal_id = ec2_id_to_internal_id(ec2_id) instance_ref = db.instance_get_by_internal_id(context, internal_id) - return rpc.call('%s.%s' % (FLAGS.compute_topic, - instance_ref['host']), - {"method": "get_console_output", - "args": {"context": None, - "instance_id": instance_ref['id']}}) + output = rpc.call('%s.%s' % (FLAGS.compute_topic, + instance_ref['host']), + { "method" : "get_console_output", + "args" : { "context": None, + "instance_id": instance_ref['id']}}) + + now = datetime.datetime.utcnow() + return { "InstanceId" : ec2_id, + "Timestamp" : now, + "output" : base64.b64encode(output) } def describe_volumes(self, context, **kwargs): if context.user.is_admin(): @@ -545,15 +739,27 @@ class CloudController(object): def _get_network_topic(self, context): """Retrieves the network host for a project""" - network_ref = db.project_get_network(context, context.project.id) + network_ref = self.network_manager.get_network(context) host = network_ref['host'] if not host: host = rpc.call(FLAGS.network_topic, {"method": "set_network_host", "args": {"context": None, - "project_id": context.project.id}}) + "network_id": network_ref['id']}}) return db.queue_get_for(context, FLAGS.network_topic, host) + def _ensure_default_security_group(self, context): + try: + db.security_group_get_by_name(context, + context.project.id, + 'default') + except exception.NotFound: + values = { 'name' : 'default', + 'description' : 'default', + 'user_id' : context.user.id, + 'project_id' : context.project.id } + group = db.security_group_create(context, values) + def run_instances(self, context, **kwargs): instance_type = kwargs.get('instance_type', 'm1.small') if instance_type not in INSTANCE_TYPES: @@ -601,8 +807,17 @@ class CloudController(object): kwargs['key_name']) key_data = key_pair_ref['public_key'] - # TODO: Get the real security group of launch in here - security_group = "default" + security_group_arg = kwargs.get('security_group', ["default"]) + if not type(security_group_arg) is list: + security_group_arg = [security_group_arg] + + security_groups = [] + self._ensure_default_security_group(context) + for security_group_name in security_group_arg: + group = db.security_group_get_by_name(context, + context.project.id, + security_group_name) + security_groups.append(group['id']) reservation_id = utils.generate_uid('r') base_options = {} @@ -616,12 +831,12 @@ class CloudController(object): base_options['user_id'] = context.user.id base_options['project_id'] = context.project.id base_options['user_data'] = kwargs.get('user_data', '') - base_options['security_group'] = security_group - base_options['instance_type'] = instance_type + base_options['display_name'] = kwargs.get('display_name') base_options['display_description'] = kwargs.get('display_description') type_data = INSTANCE_TYPES[instance_type] + base_options['instance_type'] = instance_type base_options['memory_mb'] = type_data['memory_mb'] base_options['vcpus'] = type_data['vcpus'] base_options['local_gb'] = type_data['local_gb'] @@ -630,6 +845,10 @@ class CloudController(object): instance_ref = db.instance_create(context, base_options) inst_id = instance_ref['id'] + for security_group_id in security_groups: + db.instance_add_security_group(context, inst_id, + security_group_id) + inst = {} inst['mac_address'] = utils.generate_mac() inst['launch_index'] = num @@ -637,12 +856,13 @@ class CloudController(object): ec2_id = internal_id_to_ec2_id(internal_id) inst['hostname'] = ec2_id db.instance_update(context, inst_id, inst) + # TODO(vish): This probably should be done in the scheduler + # or in compute as a call. The network should be + # allocated after the host is assigned and setup + # can happen at the same time. address = self.network_manager.allocate_fixed_ip(context, inst_id, vpn) - - # TODO(vish): This probably should be done in the scheduler - # network is setup when host is assigned network_topic = self._get_network_topic(context) rpc.call(network_topic, {"method": "setup_fixed_ip", diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 5d1ed9822..1a0792bf8 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -48,9 +48,9 @@ def _entity_list(entities): return dict(servers=entities) def _entity_detail(inst): - """ Maps everything to valid attributes for return""" - power_mapping = { - power_state.NOSTATE: 'build', + """ Maps everything to Rackspace-like attributes for return""" + power_mapping = { + power_state.NOSTATE: 'build', power_state.RUNNING: 'active', power_state.BLOCKED: 'active', power_state.PAUSED: 'suspended', @@ -60,7 +60,7 @@ def _entity_detail(inst): } inst_dict = {} - mapped_keys = dict(status='state', imageId='image_id', + mapped_keys = dict(status='state', imageId='image_id', flavorId='instance_type', name='server_name', id='id') for k, v in mapped_keys.iteritems(): @@ -83,7 +83,7 @@ class Controller(wsgi.Controller): _serialization_metadata = { 'application/xml': { "attributes": { - "server": [ "id", "imageId", "name", "flavorId", "hostId", + "server": [ "id", "imageId", "name", "flavorId", "hostId", "status", "progress", "progress" ] } } @@ -155,7 +155,7 @@ class Controller(wsgi.Controller): user_id = req.environ['nova.context']['user']['id'] inst_dict = self._deserialize(req.body, req) - + if not inst_dict: return faults.Fault(exc.HTTPUnprocessableEntity()) @@ -163,12 +163,12 @@ class Controller(wsgi.Controller): if not instance or instance.user_id != user_id: return faults.Fault(exc.HTTPNotFound()) - self.db_driver.instance_update(None, int(id), + self.db_driver.instance_update(None, int(id), _filter_params(inst_dict['server'])) return faults.Fault(exc.HTTPNoContent()) def action(self, req, id): - """ multi-purpose method used to reboot, rebuild, and + """ multi-purpose method used to reboot, rebuild, and resize a server """ user_id = req.environ['nova.context']['user']['id'] input_dict = self._deserialize(req.body, req) @@ -195,12 +195,11 @@ class Controller(wsgi.Controller): if v['flavorid'] == flavor_id][0] image_id = env['server']['imageId'] - img_service = utils.import_object(FLAGS.image_service) image = img_service.show(image_id) - if not image: + if not image: raise Exception, "Image not found" inst['server_name'] = env['server']['name'] @@ -236,15 +235,14 @@ class Controller(wsgi.Controller): ref = self.db_driver.instance_create(None, inst) inst['id'] = ref.internal_id - # TODO(dietz): this isn't explicitly necessary, but the networking # calls depend on an object with a project_id property, and therefore # should be cleaned up later api_context = context.APIRequestContext(user_id) - + inst['mac_address'] = utils.generate_mac() - - #TODO(dietz) is this necessary? + + #TODO(dietz) is this necessary? inst['launch_index'] = 0 inst['hostname'] = str(ref.internal_id) @@ -256,21 +254,20 @@ class Controller(wsgi.Controller): # TODO(vish): This probably should be done in the scheduler # network is setup when host is assigned - network_topic = self._get_network_topic(user_id) + network_topic = self._get_network_topic(None) rpc.call(network_topic, {"method": "setup_fixed_ip", "args": {"context": None, "address": address}}) return inst - def _get_network_topic(self, user_id): + def _get_network_topic(self, context): """Retrieves the network host for a project""" - network_ref = self.db_driver.project_get_network(None, - user_id) + network_ref = self.network_manager.get_network(context) host = network_ref['host'] if not host: host = rpc.call(FLAGS.network_topic, {"method": "set_network_host", "args": {"context": None, - "project_id": user_id}}) + "network_id": network_ref['id']}}) return self.db_driver.queue_get_for(None, FLAGS.network_topic, host) |
