summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorjaypipes@gmail.com <>2010-10-13 14:31:32 -0400
committerjaypipes@gmail.com <>2010-10-13 14:31:32 -0400
commitcb6104915cc0760733a9c1fb895de8e6ca914a17 (patch)
tree5dada03ad0927ddc10c5dd5bbf78fb05fd006243 /nova/api
parentc71de09a529b0a326265b0e401f49d4ef8ae6807 (diff)
parenta4aa6725be683e7e1f35df1e54069b755d19551b (diff)
Merge trunk
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/__init__.py30
-rw-r--r--nova/api/ec2/__init__.py2
-rw-r--r--nova/api/ec2/cloud.py271
-rw-r--r--nova/api/ec2/images.py3
-rw-r--r--nova/api/openstack/__init__.py (renamed from nova/api/rackspace/__init__.py)26
-rw-r--r--nova/api/openstack/_id_translator.py (renamed from nova/api/rackspace/_id_translator.py)0
-rw-r--r--nova/api/openstack/auth.py (renamed from nova/api/rackspace/auth.py)4
-rw-r--r--nova/api/openstack/backup_schedules.py (renamed from nova/api/rackspace/backup_schedules.py)2
-rw-r--r--nova/api/openstack/context.py (renamed from nova/api/rackspace/context.py)0
-rw-r--r--nova/api/openstack/faults.py (renamed from nova/api/rackspace/faults.py)0
-rw-r--r--nova/api/openstack/flavors.py (renamed from nova/api/rackspace/flavors.py)8
-rw-r--r--nova/api/openstack/images.py (renamed from nova/api/rackspace/images.py)6
-rw-r--r--nova/api/openstack/notes.txt (renamed from nova/api/rackspace/notes.txt)4
-rw-r--r--nova/api/openstack/ratelimiting/__init__.py (renamed from nova/api/rackspace/ratelimiting/__init__.py)0
-rw-r--r--nova/api/openstack/servers.py (renamed from nova/api/rackspace/servers.py)69
-rw-r--r--nova/api/openstack/sharedipgroups.py (renamed from nova/api/rackspace/sharedipgroups.py)0
-rw-r--r--nova/api/rackspace/ratelimiting/tests.py237
17 files changed, 321 insertions, 341 deletions
diff --git a/nova/api/__init__.py b/nova/api/__init__.py
index 744abd621..8ec7094d7 100644
--- a/nova/api/__init__.py
+++ b/nova/api/__init__.py
@@ -27,16 +27,16 @@ from nova import flags
from nova import wsgi
from nova.api import cloudpipe
from nova.api import ec2
-from nova.api import rackspace
+from nova.api import openstack
from nova.api.ec2 import metadatarequesthandler
-flags.DEFINE_string('rsapi_subdomain', 'rs',
- 'subdomain running the RS API')
+flags.DEFINE_string('osapi_subdomain', 'api',
+ 'subdomain running the OpenStack API')
flags.DEFINE_string('ec2api_subdomain', 'ec2',
'subdomain running the EC2 API')
flags.DEFINE_string('FAKE_subdomain', None,
- 'set to rs or ec2 to fake the subdomain of the host for testing')
+ 'set to api or ec2 to fake the subdomain of the host for testing')
FLAGS = flags.FLAGS
@@ -44,21 +44,21 @@ class API(wsgi.Router):
"""Routes top-level requests to the appropriate controller."""
def __init__(self):
- rsdomain = {'sub_domain': [FLAGS.rsapi_subdomain]}
+ osapidomain = {'sub_domain': [FLAGS.osapi_subdomain]}
ec2domain = {'sub_domain': [FLAGS.ec2api_subdomain]}
- # If someone wants to pretend they're hitting the RS subdomain
- # on their local box, they can set FAKE_subdomain to 'rs', which
- # removes subdomain restrictions from the RS routes below.
- if FLAGS.FAKE_subdomain == 'rs':
- rsdomain = {}
+ # If someone wants to pretend they're hitting the OSAPI subdomain
+ # on their local box, they can set FAKE_subdomain to 'api', which
+ # removes subdomain restrictions from the OpenStack API routes below.
+ if FLAGS.FAKE_subdomain == 'api':
+ osapidomain = {}
elif FLAGS.FAKE_subdomain == 'ec2':
ec2domain = {}
mapper = routes.Mapper()
mapper.sub_domains = True
- mapper.connect("/", controller=self.rsapi_versions,
- conditions=rsdomain)
- mapper.connect("/v1.0/{path_info:.*}", controller=rackspace.API(),
- conditions=rsdomain)
+ mapper.connect("/", controller=self.osapi_versions,
+ conditions=osapidomain)
+ mapper.connect("/v1.0/{path_info:.*}", controller=openstack.API(),
+ conditions=osapidomain)
mapper.connect("/", controller=self.ec2api_versions,
conditions=ec2domain)
@@ -81,7 +81,7 @@ class API(wsgi.Router):
super(API, self).__init__(mapper)
@webob.dec.wsgify
- def rsapi_versions(self, req):
+ def osapi_versions(self, req):
"""Respond to a request for all OpenStack API versions."""
response = {
"versions": [
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 175bb493c..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, ec2_id_list, **kwargs):
- # ec2_id_list is passed in as a list of instances
- ec2_id = ec2_id_list[0]
+
+ 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",
@@ -659,7 +879,12 @@ class CloudController(object):
return self._format_run_instances(context, reservation_id)
- def terminate_instances(self, context, ec2_id_list, **kwargs):
+ def terminate_instances(self, context, instance_id, **kwargs):
+ """Terminate each instance in instance_id, which is a list of ec2 ids.
+
+ instance_id is a kwarg so its name cannot be modified.
+ """
+ ec2_id_list = instance_id
logging.debug("Going to start terminating instances")
for id_str in ec2_id_list:
internal_id = ec2_id_to_internal_id(id_str)
diff --git a/nova/api/ec2/images.py b/nova/api/ec2/images.py
index cb54cdda2..f0a43dad6 100644
--- a/nova/api/ec2/images.py
+++ b/nova/api/ec2/images.py
@@ -69,6 +69,9 @@ def list(context, filter_list=[]):
optionally filtered by a list of image_id """
+ if FLAGS.connection_type == 'fake':
+ return [{ 'imageId' : 'bar'}]
+
# FIXME: send along the list of only_images to check for
response = conn(context).make_request(
method='GET',
diff --git a/nova/api/rackspace/__init__.py b/nova/api/openstack/__init__.py
index 89a4693ad..5e81ba2bd 100644
--- a/nova/api/rackspace/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -17,7 +17,7 @@
# under the License.
"""
-WSGI middleware for Rackspace API controllers.
+WSGI middleware for OpenStack API controllers.
"""
import json
@@ -31,30 +31,30 @@ import webob
from nova import flags
from nova import utils
from nova import wsgi
-from nova.api.rackspace import faults
-from nova.api.rackspace import backup_schedules
-from nova.api.rackspace import flavors
-from nova.api.rackspace import images
-from nova.api.rackspace import ratelimiting
-from nova.api.rackspace import servers
-from nova.api.rackspace import sharedipgroups
+from nova.api.openstack import faults
+from nova.api.openstack import backup_schedules
+from nova.api.openstack import flavors
+from nova.api.openstack import images
+from nova.api.openstack import ratelimiting
+from nova.api.openstack import servers
+from nova.api.openstack import sharedipgroups
from nova.auth import manager
FLAGS = flags.FLAGS
flags.DEFINE_string('nova_api_auth',
- 'nova.api.rackspace.auth.BasicApiAuthManager',
- 'The auth mechanism to use for the Rackspace API implemenation')
+ 'nova.api.openstack.auth.BasicApiAuthManager',
+ 'The auth mechanism to use for the OpenStack API implemenation')
class API(wsgi.Middleware):
- """WSGI entry point for all Rackspace API requests."""
+ """WSGI entry point for all OpenStack API requests."""
def __init__(self):
app = AuthMiddleware(RateLimitingMiddleware(APIRouter()))
super(API, self).__init__(app)
class AuthMiddleware(wsgi.Middleware):
- """Authorize the rackspace API request or return an HTTP Forbidden."""
+ """Authorize the openstack API request or return an HTTP Forbidden."""
def __init__(self, application):
self.auth_driver = utils.import_class(FLAGS.nova_api_auth)()
@@ -145,7 +145,7 @@ class RateLimitingMiddleware(wsgi.Middleware):
class APIRouter(wsgi.Router):
"""
- Routes requests on the Rackspace API to the appropriate controller
+ Routes requests on the OpenStack API to the appropriate controller
and method.
"""
diff --git a/nova/api/rackspace/_id_translator.py b/nova/api/openstack/_id_translator.py
index 333aa8434..333aa8434 100644
--- a/nova/api/rackspace/_id_translator.py
+++ b/nova/api/openstack/_id_translator.py
diff --git a/nova/api/rackspace/auth.py b/nova/api/openstack/auth.py
index c45156ebd..4c909293e 100644
--- a/nova/api/rackspace/auth.py
+++ b/nova/api/openstack/auth.py
@@ -11,7 +11,7 @@ from nova import db
from nova import flags
from nova import manager
from nova import utils
-from nova.api.rackspace import faults
+from nova.api.openstack import faults
FLAGS = flags.FLAGS
@@ -19,7 +19,7 @@ class Context(object):
pass
class BasicApiAuthManager(object):
- """ Implements a somewhat rudimentary version of Rackspace Auth"""
+ """ Implements a somewhat rudimentary version of OpenStack Auth"""
def __init__(self, host=None, db_driver=None):
if not host:
diff --git a/nova/api/rackspace/backup_schedules.py b/nova/api/openstack/backup_schedules.py
index 9c0d41fa0..76ad6ef87 100644
--- a/nova/api/rackspace/backup_schedules.py
+++ b/nova/api/openstack/backup_schedules.py
@@ -19,7 +19,7 @@ import time
from webob import exc
from nova import wsgi
-from nova.api.rackspace import faults
+from nova.api.openstack import faults
import nova.image.service
class Controller(wsgi.Controller):
diff --git a/nova/api/rackspace/context.py b/nova/api/openstack/context.py
index 77394615b..77394615b 100644
--- a/nova/api/rackspace/context.py
+++ b/nova/api/openstack/context.py
diff --git a/nova/api/rackspace/faults.py b/nova/api/openstack/faults.py
index 32e5c866f..32e5c866f 100644
--- a/nova/api/rackspace/faults.py
+++ b/nova/api/openstack/faults.py
diff --git a/nova/api/rackspace/flavors.py b/nova/api/openstack/flavors.py
index 916449854..793984a5d 100644
--- a/nova/api/rackspace/flavors.py
+++ b/nova/api/openstack/flavors.py
@@ -17,13 +17,13 @@
from webob import exc
-from nova.api.rackspace import faults
+from nova.api.openstack import faults
from nova.compute import instance_types
from nova import wsgi
-import nova.api.rackspace
+import nova.api.openstack
class Controller(wsgi.Controller):
- """Flavor controller for the Rackspace API."""
+ """Flavor controller for the OpenStack API."""
_serialization_metadata = {
'application/xml': {
@@ -41,7 +41,7 @@ class Controller(wsgi.Controller):
def detail(self, req):
"""Return all flavors in detail."""
items = [self.show(req, id)['flavor'] for id in self._all_ids()]
- items = nova.api.rackspace.limited(items, req)
+ items = nova.api.openstack.limited(items, req)
return dict(flavors=items)
def show(self, req, id):
diff --git a/nova/api/rackspace/images.py b/nova/api/openstack/images.py
index 82dcd2049..aa438739c 100644
--- a/nova/api/rackspace/images.py
+++ b/nova/api/openstack/images.py
@@ -20,9 +20,9 @@ from webob import exc
from nova import flags
from nova import utils
from nova import wsgi
-import nova.api.rackspace
+import nova.api.openstack
import nova.image.service
-from nova.api.rackspace import faults
+from nova.api.openstack import faults
FLAGS = flags.FLAGS
@@ -49,7 +49,7 @@ class Controller(wsgi.Controller):
def detail(self, req):
"""Return all public images in detail."""
data = self._service.index()
- data = nova.api.rackspace.limited(data, req)
+ data = nova.api.openstack.limited(data, req)
return dict(images=data)
def show(self, req, id):
diff --git a/nova/api/rackspace/notes.txt b/nova/api/openstack/notes.txt
index e133bf5ea..2330f1002 100644
--- a/nova/api/rackspace/notes.txt
+++ b/nova/api/openstack/notes.txt
@@ -10,11 +10,11 @@ image ids are URIs.
LocalImageService(ImageService):
image ids are random strings.
-RackspaceAPITranslationStore:
+OpenstackAPITranslationStore:
translates RS server/images/flavor/etc ids into formats required
by a given ImageService strategy.
-api.rackspace.images.Controller:
+api.openstack.images.Controller:
uses an ImageService strategy behind the scenes to do its fetching; it just
converts int image id into a strategy-specific image id.
diff --git a/nova/api/rackspace/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py
index f843bac0f..f843bac0f 100644
--- a/nova/api/rackspace/ratelimiting/__init__.py
+++ b/nova/api/openstack/ratelimiting/__init__.py
diff --git a/nova/api/rackspace/servers.py b/nova/api/openstack/servers.py
index b23867bbf..1a0792bf8 100644
--- a/nova/api/rackspace/servers.py
+++ b/nova/api/openstack/servers.py
@@ -25,27 +25,15 @@ from nova import rpc
from nova import utils
from nova import wsgi
from nova.api import cloud
-from nova.api.rackspace import _id_translator
-from nova.api.rackspace import context
-from nova.api.rackspace import faults
+from nova.api.openstack import context
+from nova.api.openstack import faults
from nova.compute import instance_types
from nova.compute import power_state
-import nova.api.rackspace
+import nova.api.openstack
import nova.image.service
FLAGS = flags.FLAGS
-def _instance_id_translator():
- """ Helper method for initializing an id translator for Rackspace instance
- ids """
- return _id_translator.RackspaceAPIIdTranslator( "instance", 'nova')
-
-def _image_service():
- """ Helper method for initializing the image id translator """
- service = utils.import_object(FLAGS.image_service)
- return (service, _id_translator.RackspaceAPIIdTranslator(
- "image", service.__class__.__name__))
-
def _filter_params(inst_dict):
""" Extracts all updatable parameters for a server update request """
keys = dict(name='name', admin_pass='adminPass')
@@ -61,8 +49,8 @@ def _entity_list(entities):
def _entity_detail(inst):
""" Maps everything to Rackspace-like attributes for return"""
- power_mapping = {
- power_state.NOSTATE: 'build',
+ power_mapping = {
+ power_state.NOSTATE: 'build',
power_state.RUNNING: 'active',
power_state.BLOCKED: 'active',
power_state.PAUSED: 'suspended',
@@ -72,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():
@@ -90,12 +78,12 @@ def _entity_inst(inst):
return dict(server=dict(id=inst['id'], name=inst['server_name']))
class Controller(wsgi.Controller):
- """ The Server API controller for the Openstack API """
+ """ The Server API controller for the OpenStack API """
_serialization_metadata = {
'application/xml': {
"attributes": {
- "server": [ "id", "imageId", "name", "flavorId", "hostId",
+ "server": [ "id", "imageId", "name", "flavorId", "hostId",
"status", "progress", "progress" ]
}
}
@@ -122,7 +110,7 @@ class Controller(wsgi.Controller):
"""
user_id = req.environ['nova.context']['user']['id']
instance_list = self.db_driver.instance_get_all_by_user(None, user_id)
- limited_list = nova.api.rackspace.limited(instance_list, req)
+ limited_list = nova.api.openstack.limited(instance_list, req)
res = [entity_maker(inst)['server'] for inst in limited_list]
return _entity_list(res)
@@ -167,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())
@@ -175,20 +163,23 @@ 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)
try:
reboot_type = input_dict['reboot']['type']
except Exception:
raise faults.Fault(webob.exc.HTTPNotImplemented())
- opaque_id = _instance_id_translator().from_rs_id(int(id))
- cloud.reboot(opaque_id)
+ inst_ref = self.db.instance_get_by_internal_id(None, int(id))
+ if not inst_ref or (inst_ref and not inst_ref.user_id == user_id):
+ return faults.Fault(exc.HTTPUnprocessableEntity())
+ cloud.reboot(id)
def _build_server_instance(self, req, env):
"""Build instance data structure and save it to the data store."""
@@ -204,17 +195,15 @@ class Controller(wsgi.Controller):
if v['flavorid'] == flavor_id][0]
image_id = env['server']['imageId']
-
- img_service, image_id_trans = _image_service()
+ img_service = utils.import_object(FLAGS.image_service)
- opaque_image_id = image_id_trans.to_rs_id(image_id)
- image = img_service.show(opaque_image_id)
+ image = img_service.show(image_id)
- if not image:
+ if not image:
raise Exception, "Image not found"
inst['server_name'] = env['server']['name']
- inst['image_id'] = opaque_image_id
+ inst['image_id'] = image_id
inst['user_id'] = user_id
inst['launch_time'] = ltime
inst['mac_address'] = utils.generate_mac()
@@ -246,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)
@@ -266,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)
diff --git a/nova/api/rackspace/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py
index 4d2d0ede1..4d2d0ede1 100644
--- a/nova/api/rackspace/sharedipgroups.py
+++ b/nova/api/openstack/sharedipgroups.py
diff --git a/nova/api/rackspace/ratelimiting/tests.py b/nova/api/rackspace/ratelimiting/tests.py
deleted file mode 100644
index 4c9510917..000000000
--- a/nova/api/rackspace/ratelimiting/tests.py
+++ /dev/null
@@ -1,237 +0,0 @@
-import httplib
-import StringIO
-import time
-import unittest
-import webob
-
-import nova.api.rackspace.ratelimiting as ratelimiting
-
-class LimiterTest(unittest.TestCase):
-
- def setUp(self):
- self.limits = {
- 'a': (5, ratelimiting.PER_SECOND),
- 'b': (5, ratelimiting.PER_MINUTE),
- 'c': (5, ratelimiting.PER_HOUR),
- 'd': (1, ratelimiting.PER_SECOND),
- 'e': (100, ratelimiting.PER_SECOND)}
- self.rl = ratelimiting.Limiter(self.limits)
-
- def exhaust(self, action, times_until_exhausted, **kwargs):
- for i in range(times_until_exhausted):
- when = self.rl.perform(action, **kwargs)
- self.assertEqual(when, None)
- num, period = self.limits[action]
- delay = period * 1.0 / num
- # Verify that we are now thoroughly delayed
- for i in range(10):
- when = self.rl.perform(action, **kwargs)
- self.assertAlmostEqual(when, delay, 2)
-
- def test_second(self):
- self.exhaust('a', 5)
- time.sleep(0.2)
- self.exhaust('a', 1)
- time.sleep(1)
- self.exhaust('a', 5)
-
- def test_minute(self):
- self.exhaust('b', 5)
-
- def test_one_per_period(self):
- def allow_once_and_deny_once():
- when = self.rl.perform('d')
- self.assertEqual(when, None)
- when = self.rl.perform('d')
- self.assertAlmostEqual(when, 1, 2)
- return when
- time.sleep(allow_once_and_deny_once())
- time.sleep(allow_once_and_deny_once())
- allow_once_and_deny_once()
-
- def test_we_can_go_indefinitely_if_we_spread_out_requests(self):
- for i in range(200):
- when = self.rl.perform('e')
- self.assertEqual(when, None)
- time.sleep(0.01)
-
- def test_users_get_separate_buckets(self):
- self.exhaust('c', 5, username='alice')
- self.exhaust('c', 5, username='bob')
- self.exhaust('c', 5, username='chuck')
- self.exhaust('c', 0, username='chuck')
- self.exhaust('c', 0, username='bob')
- self.exhaust('c', 0, username='alice')
-
-
-class FakeLimiter(object):
- """Fake Limiter class that you can tell how to behave."""
- def __init__(self, test):
- self._action = self._username = self._delay = None
- self.test = test
- def mock(self, action, username, delay):
- self._action = action
- self._username = username
- self._delay = delay
- def perform(self, action, username):
- self.test.assertEqual(action, self._action)
- self.test.assertEqual(username, self._username)
- return self._delay
-
-
-class WSGIAppTest(unittest.TestCase):
-
- def setUp(self):
- self.limiter = FakeLimiter(self)
- self.app = ratelimiting.WSGIApp(self.limiter)
-
- def test_invalid_methods(self):
- requests = []
- for method in ['GET', 'PUT', 'DELETE']:
- req = webob.Request.blank('/limits/michael/breakdance',
- dict(REQUEST_METHOD=method))
- requests.append(req)
- for req in requests:
- self.assertEqual(req.get_response(self.app).status_int, 405)
-
- def test_invalid_urls(self):
- requests = []
- for prefix in ['limit', '', 'limiter2', 'limiter/limits', 'limiter/1']:
- req = webob.Request.blank('/%s/michael/breakdance' % prefix,
- dict(REQUEST_METHOD='POST'))
- requests.append(req)
- for req in requests:
- self.assertEqual(req.get_response(self.app).status_int, 404)
-
- def verify(self, url, username, action, delay=None):
- """Make sure that POSTing to the given url causes the given username
- to perform the given action. Make the internal rate limiter return
- delay and make sure that the WSGI app returns the correct response.
- """
- req = webob.Request.blank(url, dict(REQUEST_METHOD='POST'))
- self.limiter.mock(action, username, delay)
- resp = req.get_response(self.app)
- if not delay:
- self.assertEqual(resp.status_int, 200)
- else:
- self.assertEqual(resp.status_int, 403)
- self.assertEqual(resp.headers['X-Wait-Seconds'], "%.2f" % delay)
-
- def test_good_urls(self):
- self.verify('/limiter/michael/hoot', 'michael', 'hoot')
-
- def test_escaping(self):
- self.verify('/limiter/michael/jump%20up', 'michael', 'jump up')
-
- def test_response_to_delays(self):
- self.verify('/limiter/michael/hoot', 'michael', 'hoot', 1)
- self.verify('/limiter/michael/hoot', 'michael', 'hoot', 1.56)
- self.verify('/limiter/michael/hoot', 'michael', 'hoot', 1000)
-
-
-class FakeHttplibSocket(object):
- """a fake socket implementation for httplib.HTTPResponse, trivial"""
-
- def __init__(self, response_string):
- self._buffer = StringIO.StringIO(response_string)
-
- def makefile(self, _mode, _other):
- """Returns the socket's internal buffer"""
- return self._buffer
-
-
-class FakeHttplibConnection(object):
- """A fake httplib.HTTPConnection
-
- Requests made via this connection actually get translated and routed into
- our WSGI app, we then wait for the response and turn it back into
- an httplib.HTTPResponse.
- """
- def __init__(self, app, host, is_secure=False):
- self.app = app
- self.host = host
-
- def request(self, method, path, data='', headers={}):
- req = webob.Request.blank(path)
- req.method = method
- req.body = data
- req.headers = headers
- req.host = self.host
- # Call the WSGI app, get the HTTP response
- resp = str(req.get_response(self.app))
- # For some reason, the response doesn't have "HTTP/1.0 " prepended; I
- # guess that's a function the web server usually provides.
- resp = "HTTP/1.0 %s" % resp
- sock = FakeHttplibSocket(resp)
- self.http_response = httplib.HTTPResponse(sock)
- self.http_response.begin()
-
- def getresponse(self):
- return self.http_response
-
-
-def wire_HTTPConnection_to_WSGI(host, app):
- """Monkeypatches HTTPConnection so that if you try to connect to host, you
- are instead routed straight to the given WSGI app.
-
- After calling this method, when any code calls
-
- httplib.HTTPConnection(host)
-
- the connection object will be a fake. Its requests will be sent directly
- to the given WSGI app rather than through a socket.
-
- Code connecting to hosts other than host will not be affected.
-
- This method may be called multiple times to map different hosts to
- different apps.
- """
- class HTTPConnectionDecorator(object):
- """Wraps the real HTTPConnection class so that when you instantiate
- the class you might instead get a fake instance."""
- def __init__(self, wrapped):
- self.wrapped = wrapped
- def __call__(self, connection_host, *args, **kwargs):
- if connection_host == host:
- return FakeHttplibConnection(app, host)
- else:
- return self.wrapped(connection_host, *args, **kwargs)
- httplib.HTTPConnection = HTTPConnectionDecorator(httplib.HTTPConnection)
-
-
-class WSGIAppProxyTest(unittest.TestCase):
-
- def setUp(self):
- """Our WSGIAppProxy is going to call across an HTTPConnection to a
- WSGIApp running a limiter. The proxy will send input, and the proxy
- should receive that same input, pass it to the limiter who gives a
- result, and send the expected result back.
-
- The HTTPConnection isn't real -- it's monkeypatched to point straight
- at the WSGIApp. And the limiter isn't real -- it's a fake that
- behaves the way we tell it to.
- """
- self.limiter = FakeLimiter(self)
- app = ratelimiting.WSGIApp(self.limiter)
- wire_HTTPConnection_to_WSGI('100.100.100.100:80', app)
- self.proxy = ratelimiting.WSGIAppProxy('100.100.100.100:80')
-
- def test_200(self):
- self.limiter.mock('conquer', 'caesar', None)
- when = self.proxy.perform('conquer', 'caesar')
- self.assertEqual(when, None)
-
- def test_403(self):
- self.limiter.mock('grumble', 'proletariat', 1.5)
- when = self.proxy.perform('grumble', 'proletariat')
- self.assertEqual(when, 1.5)
-
- def test_failure(self):
- def shouldRaise():
- self.limiter.mock('murder', 'brutus', None)
- self.proxy.perform('stab', 'brutus')
- self.assertRaises(AssertionError, shouldRaise)
-
-
-if __name__ == '__main__':
- unittest.main()