merged trunk

This commit is contained in:
Vishvananda Ishaya
2010-09-30 18:13:45 -07:00
40 changed files with 1202 additions and 265 deletions

View File

@@ -266,6 +266,18 @@ class UserCommands(object):
for user in self.manager.get_users(): for user in self.manager.get_users():
print user.name print user.name
def modify(self, name, access_key, secret_key, is_admin):
"""update a users keys & admin flag
arguments: accesskey secretkey admin
leave any field blank to ignore it, admin should be 'T', 'F', or blank
"""
if not is_admin:
is_admin = None
elif is_admin.upper()[0] == 'T':
is_admin = True
else:
is_admin = False
self.manager.modify_user(name, access_key, secret_key, is_admin)
class ProjectCommands(object): class ProjectCommands(object):
"""Class for managing projects.""" """Class for managing projects."""
@@ -291,7 +303,7 @@ class ProjectCommands(object):
def environment(self, project_id, user_id, filename='novarc'): def environment(self, project_id, user_id, filename='novarc'):
"""Exports environment variables to an sourcable file """Exports environment variables to an sourcable file
arguments: project_id user_id [filename='novarc]""" arguments: project_id user_id [filename='novarc]"""
rc = self.manager.get_environment_rc(project_id, user_id) rc = self.manager.get_environment_rc(user_id, project_id)
with open(filename, 'w') as f: with open(filename, 'w') as f:
f.write(rc) f.write(rc)

42
nova/api/cloud.py Normal file
View File

@@ -0,0 +1,42 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Methods for API calls to control instances via AMQP.
"""
from nova import db
from nova import flags
from nova import rpc
FLAGS = flags.FLAGS
def reboot(instance_id, context=None):
"""Reboot the given instance.
#TODO(gundlach) not actually sure what context is used for by ec2 here
-- I think we can just remove it and use None all the time.
"""
instance_ref = db.instance_get_by_ec2_id(None, instance_id)
host = instance_ref['host']
rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host),
{"method": "reboot_instance",
"args": {"context": None,
"instance_id": instance_ref['id']}})

View File

@@ -158,12 +158,14 @@ class Authorizer(wsgi.Middleware):
'RunInstances': ['projectmanager', 'sysadmin'], 'RunInstances': ['projectmanager', 'sysadmin'],
'TerminateInstances': ['projectmanager', 'sysadmin'], 'TerminateInstances': ['projectmanager', 'sysadmin'],
'RebootInstances': ['projectmanager', 'sysadmin'], 'RebootInstances': ['projectmanager', 'sysadmin'],
'UpdateInstance': ['projectmanager', 'sysadmin'],
'DeleteVolume': ['projectmanager', 'sysadmin'], 'DeleteVolume': ['projectmanager', 'sysadmin'],
'DescribeImages': ['all'], 'DescribeImages': ['all'],
'DeregisterImage': ['projectmanager', 'sysadmin'], 'DeregisterImage': ['projectmanager', 'sysadmin'],
'RegisterImage': ['projectmanager', 'sysadmin'], 'RegisterImage': ['projectmanager', 'sysadmin'],
'DescribeImageAttribute': ['all'], 'DescribeImageAttribute': ['all'],
'ModifyImageAttribute': ['projectmanager', 'sysadmin'], 'ModifyImageAttribute': ['projectmanager', 'sysadmin'],
'UpdateImage': ['projectmanager', 'sysadmin'],
}, },
'AdminController': { 'AdminController': {
# All actions have the same permission: ['none'] (the default) # All actions have the same permission: ['none'] (the default)

View File

@@ -36,6 +36,7 @@ from nova import quota
from nova import rpc from nova import rpc
from nova import utils from nova import utils
from nova.compute.instance_types import INSTANCE_TYPES from nova.compute.instance_types import INSTANCE_TYPES
from nova.api import cloud
from nova.api.ec2 import images from nova.api.ec2 import images
@@ -285,6 +286,9 @@ class CloudController(object):
'volume_id': volume['ec2_id']}] 'volume_id': volume['ec2_id']}]
else: else:
v['attachmentSet'] = [{}] v['attachmentSet'] = [{}]
v['display_name'] = volume['display_name']
v['display_description'] = volume['display_description']
return v return v
def create_volume(self, context, size, **kwargs): def create_volume(self, context, size, **kwargs):
@@ -302,6 +306,8 @@ class CloudController(object):
vol['availability_zone'] = FLAGS.storage_availability_zone vol['availability_zone'] = FLAGS.storage_availability_zone
vol['status'] = "creating" vol['status'] = "creating"
vol['attach_status'] = "detached" vol['attach_status'] = "detached"
vol['display_name'] = kwargs.get('display_name')
vol['display_description'] = kwargs.get('display_description')
volume_ref = db.volume_create(context, vol) volume_ref = db.volume_create(context, vol)
rpc.cast(FLAGS.scheduler_topic, rpc.cast(FLAGS.scheduler_topic,
@@ -368,6 +374,16 @@ class CloudController(object):
lst = [lst] lst = [lst]
return [{label: x} for x in lst] return [{label: x} for x in lst]
def update_volume(self, context, volume_id, **kwargs):
updatable_fields = ['display_name', 'display_description']
changes = {}
for field in updatable_fields:
if field in kwargs:
changes[field] = kwargs[field]
if changes:
db.volume_update(context, volume_id, kwargs)
return True
def describe_instances(self, context, **kwargs): def describe_instances(self, context, **kwargs):
return self._format_describe_instances(context) return self._format_describe_instances(context)
@@ -420,6 +436,8 @@ class CloudController(object):
i['instanceType'] = instance['instance_type'] i['instanceType'] = instance['instance_type']
i['launchTime'] = instance['created_at'] i['launchTime'] = instance['created_at']
i['amiLaunchIndex'] = instance['launch_index'] i['amiLaunchIndex'] = instance['launch_index']
i['displayName'] = instance['display_name']
i['displayDescription'] = instance['display_description']
if not reservations.has_key(instance['reservation_id']): if not reservations.has_key(instance['reservation_id']):
r = {} r = {}
r['reservationId'] = instance['reservation_id'] r['reservationId'] = instance['reservation_id']
@@ -577,6 +595,8 @@ class CloudController(object):
base_options['user_data'] = kwargs.get('user_data', '') base_options['user_data'] = kwargs.get('user_data', '')
base_options['security_group'] = security_group base_options['security_group'] = security_group
base_options['instance_type'] = instance_type 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] type_data = INSTANCE_TYPES[instance_type]
base_options['memory_mb'] = type_data['memory_mb'] base_options['memory_mb'] = type_data['memory_mb']
@@ -665,12 +685,19 @@ class CloudController(object):
def reboot_instances(self, context, instance_id, **kwargs): def reboot_instances(self, context, instance_id, **kwargs):
"""instance_id is a list of instance ids""" """instance_id is a list of instance ids"""
for id_str in instance_id: for id_str in instance_id:
instance_ref = db.instance_get_by_ec2_id(context, id_str) cloud.reboot(id_str, context=context)
host = instance_ref['host'] return True
rpc.cast(db.queue_get_for(context, FLAGS.compute_topic, host),
{"method": "reboot_instance", def update_instance(self, context, instance_id, **kwargs):
"args": {"context": None, updatable_fields = ['display_name', 'display_description']
"instance_id": instance_ref['id']}}) changes = {}
for field in updatable_fields:
if field in kwargs:
changes[field] = kwargs[field]
if changes:
db_context = {}
inst = db.instance_get_by_ec2_id(db_context, instance_id)
db.instance_update(db_context, inst['id'], kwargs)
return True return True
def delete_volume(self, context, volume_id, **kwargs): def delete_volume(self, context, volume_id, **kwargs):
@@ -728,3 +755,7 @@ class CloudController(object):
if not operation_type in ['add', 'remove']: if not operation_type in ['add', 'remove']:
raise exception.ApiError('operation_type must be add or remove') raise exception.ApiError('operation_type must be add or remove')
return images.modify(context, image_id, operation_type) return images.modify(context, image_id, operation_type)
def update_image(self, context, image_id, **kwargs):
result = images.update(context, image_id, dict(kwargs))
return result

View File

@@ -43,6 +43,14 @@ def modify(context, image_id, operation):
return True return True
def update(context, image_id, attributes):
"""update an image's attributes / info.json"""
attributes.update({"image_id": image_id})
conn(context).make_request(
method='POST',
bucket='_images',
query_args=qs(attributes))
return True
def register(context, image_location): def register(context, image_location):
""" rpc call to register a new image based from a manifest """ """ rpc call to register a new image based from a manifest """

View File

@@ -31,6 +31,7 @@ import webob
from nova import flags from nova import flags
from nova import utils from nova import utils
from nova import wsgi from nova import wsgi
from nova.api.rackspace import faults
from nova.api.rackspace import backup_schedules from nova.api.rackspace import backup_schedules
from nova.api.rackspace import flavors from nova.api.rackspace import flavors
from nova.api.rackspace import images from nova.api.rackspace import images
@@ -67,7 +68,7 @@ class AuthMiddleware(wsgi.Middleware):
user = self.auth_driver.authorize_token(req.headers["X-Auth-Token"]) user = self.auth_driver.authorize_token(req.headers["X-Auth-Token"])
if not user: if not user:
return webob.exc.HTTPUnauthorized() return faults.Fault(webob.exc.HTTPUnauthorized())
if not req.environ.has_key('nova.context'): if not req.environ.has_key('nova.context'):
req.environ['nova.context'] = {} req.environ['nova.context'] = {}
@@ -112,8 +113,10 @@ class RateLimitingMiddleware(wsgi.Middleware):
delay = self.get_delay(action_name, username) delay = self.get_delay(action_name, username)
if delay: if delay:
# TODO(gundlach): Get the retry-after format correct. # TODO(gundlach): Get the retry-after format correct.
raise webob.exc.HTTPRequestEntityTooLarge(headers={ exc = webob.exc.HTTPRequestEntityTooLarge(
'Retry-After': time.time() + delay}) explanation='Too many requests.',
headers={'Retry-After': time.time() + delay})
raise faults.Fault(exc)
return self.application return self.application
def get_delay(self, action_name, username): def get_delay(self, action_name, username):
@@ -165,3 +168,23 @@ class APIRouter(wsgi.Router):
controller=sharedipgroups.Controller()) controller=sharedipgroups.Controller())
super(APIRouter, self).__init__(mapper) super(APIRouter, self).__init__(mapper)
def limited(items, req):
"""Return a slice of items according to requested offset and limit.
items - a sliceable
req - wobob.Request possibly containing offset and limit GET variables.
offset is where to start in the list, and limit is the maximum number
of items to return.
If limit is not specified, 0, or > 1000, defaults to 1000.
"""
offset = int(req.GET.get('offset', 0))
limit = int(req.GET.get('limit', 0))
if not limit:
limit = 1000
limit = min(1000, limit)
range_end = offset + limit
return items[offset:range_end]

View File

@@ -11,6 +11,7 @@ from nova import db
from nova import flags from nova import flags
from nova import manager from nova import manager
from nova import utils from nova import utils
from nova.api.rackspace import faults
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@@ -36,13 +37,13 @@ class BasicApiAuthManager(object):
# honor it # honor it
path_info = req.path_info path_info = req.path_info
if len(path_info) > 1: if len(path_info) > 1:
return webob.exc.HTTPUnauthorized() return faults.Fault(webob.exc.HTTPUnauthorized())
try: try:
username, key = req.headers['X-Auth-User'], \ username, key = req.headers['X-Auth-User'], \
req.headers['X-Auth-Key'] req.headers['X-Auth-Key']
except KeyError: except KeyError:
return webob.exc.HTTPUnauthorized() return faults.Fault(webob.exc.HTTPUnauthorized())
username, key = req.headers['X-Auth-User'], req.headers['X-Auth-Key'] username, key = req.headers['X-Auth-User'], req.headers['X-Auth-Key']
token, user = self._authorize_user(username, key) token, user = self._authorize_user(username, key)
@@ -57,7 +58,7 @@ class BasicApiAuthManager(object):
res.status = '204' res.status = '204'
return res return res
else: else:
return webob.exc.HTTPUnauthorized() return faults.Fault(webob.exc.HTTPUnauthorized())
def authorize_token(self, token_hash): def authorize_token(self, token_hash):
""" retrieves user information from the datastore given a token """ retrieves user information from the datastore given a token

View File

@@ -20,6 +20,7 @@ from webob import exc
from nova import wsgi from nova import wsgi
from nova.api.rackspace import _id_translator from nova.api.rackspace import _id_translator
from nova.api.rackspace import faults
import nova.image.service import nova.image.service
class Controller(wsgi.Controller): class Controller(wsgi.Controller):
@@ -27,12 +28,12 @@ class Controller(wsgi.Controller):
pass pass
def index(self, req, server_id): def index(self, req, server_id):
return exc.HTTPNotFound() return faults.Fault(exc.HTTPNotFound())
def create(self, req, server_id): def create(self, req, server_id):
""" No actual update method required, since the existing API allows """ No actual update method required, since the existing API allows
both create and update through a POST """ both create and update through a POST """
return exc.HTTPNotFound() return faults.Fault(exc.HTTPNotFound())
def delete(self, req, server_id): def delete(self, req, server_id):
return exc.HTTPNotFound() return faults.Fault(exc.HTTPNotFound())

View File

@@ -0,0 +1,33 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
APIRequestContext
"""
import random
class Project(object):
def __init__(self, user_id):
self.id = user_id
class APIRequestContext(object):
""" This is an adapter class to get around all of the assumptions made in
the FlatNetworking """
def __init__(self, user_id):
self.user_id = user_id
self.project = Project(user_id)

View File

@@ -0,0 +1,62 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import webob.dec
import webob.exc
from nova import wsgi
class Fault(webob.exc.HTTPException):
"""An RS API fault response."""
_fault_names = {
400: "badRequest",
401: "unauthorized",
403: "resizeNotAllowed",
404: "itemNotFound",
405: "badMethod",
409: "inProgress",
413: "overLimit",
415: "badMediaType",
501: "notImplemented",
503: "serviceUnavailable"}
def __init__(self, exception):
"""Create a Fault for the given webob.exc.exception."""
self.wrapped_exc = exception
@webob.dec.wsgify
def __call__(self, req):
"""Generate a WSGI response based on the exception passed to ctor."""
# Replace the body with fault details.
code = self.wrapped_exc.status_int
fault_name = self._fault_names.get(code, "cloudServersFault")
fault_data = {
fault_name: {
'code': code,
'message': self.wrapped_exc.explanation}}
if code == 413:
retry = self.wrapped_exc.headers['Retry-After']
fault_data[fault_name]['retryAfter'] = retry
# 'code' is an attribute on the fault tag itself
metadata = {'application/xml': {'attributes': {fault_name: 'code'}}}
serializer = wsgi.Serializer(req.environ, metadata)
self.wrapped_exc.body = serializer.to_content_type(fault_data)
return self.wrapped_exc

View File

@@ -15,9 +15,12 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from webob import exc
from nova.api.rackspace import faults
from nova.compute import instance_types from nova.compute import instance_types
from nova import wsgi from nova import wsgi
from webob import exc import nova.api.rackspace
class Controller(wsgi.Controller): class Controller(wsgi.Controller):
"""Flavor controller for the Rackspace API.""" """Flavor controller for the Rackspace API."""
@@ -38,6 +41,7 @@ class Controller(wsgi.Controller):
def detail(self, req): def detail(self, req):
"""Return all flavors in detail.""" """Return all flavors in detail."""
items = [self.show(req, id)['flavor'] for id in self._all_ids()] items = [self.show(req, id)['flavor'] for id in self._all_ids()]
items = nova.api.rackspace.limited(items, req)
return dict(flavors=items) return dict(flavors=items)
def show(self, req, id): def show(self, req, id):
@@ -47,7 +51,7 @@ class Controller(wsgi.Controller):
item = dict(ram=val['memory_mb'], disk=val['local_gb'], item = dict(ram=val['memory_mb'], disk=val['local_gb'],
id=val['flavorid'], name=name) id=val['flavorid'], name=name)
return dict(flavor=item) return dict(flavor=item)
raise exc.HTTPNotFound() raise faults.Fault(exc.HTTPNotFound())
def _all_ids(self): def _all_ids(self):
"""Return the list of all flavorids.""" """Return the list of all flavorids."""

View File

@@ -19,7 +19,9 @@ from webob import exc
from nova import wsgi from nova import wsgi
from nova.api.rackspace import _id_translator from nova.api.rackspace import _id_translator
import nova.api.rackspace
import nova.image.service import nova.image.service
from nova.api.rackspace import faults
class Controller(wsgi.Controller): class Controller(wsgi.Controller):
@@ -45,6 +47,7 @@ class Controller(wsgi.Controller):
def detail(self, req): def detail(self, req):
"""Return all public images in detail.""" """Return all public images in detail."""
data = self._service.index() data = self._service.index()
data = nova.api.rackspace.limited(data, req)
for img in data: for img in data:
img['id'] = self._id_translator.to_rs_id(img['id']) img['id'] = self._id_translator.to_rs_id(img['id'])
return dict(images=data) return dict(images=data)
@@ -58,14 +61,14 @@ class Controller(wsgi.Controller):
def delete(self, req, id): def delete(self, req, id):
# Only public images are supported for now. # Only public images are supported for now.
raise exc.HTTPNotFound() raise faults.Fault(exc.HTTPNotFound())
def create(self, req): def create(self, req):
# Only public images are supported for now, so a request to # Only public images are supported for now, so a request to
# make a backup of a server cannot be supproted. # make a backup of a server cannot be supproted.
raise exc.HTTPNotFound() raise faults.Fault(exc.HTTPNotFound())
def update(self, req, id): def update(self, req, id):
# Users may not modify public images, and that's all that # Users may not modify public images, and that's all that
# we support for now. # we support for now.
raise exc.HTTPNotFound() raise faults.Fault(exc.HTTPNotFound())

View File

@@ -17,33 +17,45 @@
import time import time
import webob
from webob import exc from webob import exc
from nova import flags from nova import flags
from nova import rpc from nova import rpc
from nova import utils from nova import utils
from nova import wsgi from nova import wsgi
from nova.api import cloud
from nova.api.rackspace import _id_translator from nova.api.rackspace import _id_translator
from nova.api.rackspace import context
from nova.api.rackspace import faults
from nova.compute import instance_types
from nova.compute import power_state from nova.compute import power_state
import nova.api.rackspace
import nova.image.service import nova.image.service
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_string('rs_network_manager', 'nova.network.manager.FlatManager',
'Networking for rackspace')
def _instance_id_translator():
""" Helper method for initializing an id translator for Rackspace instance
ids """
return _id_translator.RackspaceAPIIdTranslator( "instance", 'nova')
def translator_instance(): def _image_service():
""" Helper method for initializing the image id translator """ """ Helper method for initializing the image id translator """
service = nova.image.service.ImageService.load() service = nova.image.service.ImageService.load()
return _id_translator.RackspaceAPIIdTranslator( return (service, _id_translator.RackspaceAPIIdTranslator(
"image", service.__class__.__name__) "image", service.__class__.__name__))
def _filter_params(inst_dict): def _filter_params(inst_dict):
""" Extracts all updatable parameters for a server update request """ """ Extracts all updatable parameters for a server update request """
keys = ['name', 'adminPass'] keys = dict(name='name', admin_pass='adminPass')
new_attrs = {} new_attrs = {}
for k in keys: for k, v in keys.items():
if inst_dict.has_key(k): if inst_dict.has_key(v):
new_attrs[k] = inst_dict[k] new_attrs[k] = inst_dict[v]
return new_attrs return new_attrs
def _entity_list(entities): def _entity_list(entities):
@@ -83,7 +95,6 @@ def _entity_inst(inst):
class Controller(wsgi.Controller): class Controller(wsgi.Controller):
""" The Server API controller for the Openstack API """ """ The Server API controller for the Openstack API """
_serialization_metadata = { _serialization_metadata = {
'application/xml': { 'application/xml': {
"attributes": { "attributes": {
@@ -101,42 +112,58 @@ class Controller(wsgi.Controller):
def index(self, req): def index(self, req):
""" Returns a list of server names and ids for a given user """ """ Returns a list of server names and ids for a given user """
user_id = req.environ['nova.context']['user']['id'] return self._items(req, entity_maker=_entity_inst)
instance_list = self.db_driver.instance_get_all_by_user(None, user_id)
res = [_entity_inst(inst)['server'] for inst in instance_list]
return _entity_list(res)
def detail(self, req): def detail(self, req):
""" Returns a list of server details for a given user """ """ Returns a list of server details for a given user """
return self._items(req, entity_maker=_entity_detail)
def _items(self, req, entity_maker):
"""Returns a list of servers for a given user.
entity_maker - either _entity_detail or _entity_inst
"""
user_id = req.environ['nova.context']['user']['id'] user_id = req.environ['nova.context']['user']['id']
res = [_entity_detail(inst)['server'] for inst in instance_list = self.db_driver.instance_get_all_by_user(None, user_id)
self.db_driver.instance_get_all_by_user(None, user_id)] limited_list = nova.api.rackspace.limited(instance_list, req)
res = [entity_maker(inst)['server'] for inst in limited_list]
return _entity_list(res) return _entity_list(res)
def show(self, req, id): def show(self, req, id):
""" Returns server details by server id """ """ Returns server details by server id """
inst_id_trans = _instance_id_translator()
inst_id = inst_id_trans.from_rs_id(id)
user_id = req.environ['nova.context']['user']['id'] user_id = req.environ['nova.context']['user']['id']
inst = self.db_driver.instance_get(None, id) inst = self.db_driver.instance_get_by_ec2_id(None, inst_id)
if inst: if inst:
if inst.user_id == user_id: if inst.user_id == user_id:
return _entity_detail(inst) return _entity_detail(inst)
raise exc.HTTPNotFound() raise faults.Fault(exc.HTTPNotFound())
def delete(self, req, id): def delete(self, req, id):
""" Destroys a server """ """ Destroys a server """
inst_id_trans = _instance_id_translator()
inst_id = inst_id_trans.from_rs_id(id)
user_id = req.environ['nova.context']['user']['id'] user_id = req.environ['nova.context']['user']['id']
instance = self.db_driver.instance_get(None, id) instance = self.db_driver.instance_get_by_ec2_id(None, inst_id)
if instance and instance['user_id'] == user_id: if instance and instance['user_id'] == user_id:
self.db_driver.instance_destroy(None, id) self.db_driver.instance_destroy(None, id)
return exc.HTTPAccepted() return faults.Fault(exc.HTTPAccepted())
return exc.HTTPNotFound() return faults.Fault(exc.HTTPNotFound())
def create(self, req): def create(self, req):
""" Creates a new server for a given user """ """ Creates a new server for a given user """
if not req.environ.has_key('inst_dict'):
return exc.HTTPUnprocessableEntity()
inst = self._build_server_instance(req) env = self._deserialize(req.body, req)
if not env:
return faults.Fault(exc.HTTPUnprocessableEntity())
try:
inst = self._build_server_instance(req, env)
except Exception, e:
return faults.Fault(exc.HTTPUnprocessableEntity())
rpc.cast( rpc.cast(
FLAGS.compute_topic, { FLAGS.compute_topic, {
@@ -146,62 +173,127 @@ class Controller(wsgi.Controller):
def update(self, req, id): def update(self, req, id):
""" Updates the server name or password """ """ Updates the server name or password """
if not req.environ.has_key('inst_dict'): inst_id_trans = _instance_id_translator()
return exc.HTTPUnprocessableEntity() inst_id = inst_id_trans.from_rs_id(id)
user_id = req.environ['nova.context']['user']['id']
instance = self.db_driver.instance_get(None, id) inst_dict = self._deserialize(req.body, req)
if not instance:
return exc.HTTPNotFound()
attrs = req.environ['nova.context'].get('model_attributes', None) if not inst_dict:
if attrs: return faults.Fault(exc.HTTPUnprocessableEntity())
self.db_driver.instance_update(None, id, _filter_params(attrs))
return exc.HTTPNoContent() instance = self.db_driver.instance_get_by_ec2_id(None, inst_id)
if not instance or instance.user_id != user_id:
return faults.Fault(exc.HTTPNotFound())
self.db_driver.instance_update(None, id,
_filter_params(inst_dict['server']))
return faults.Fault(exc.HTTPNoContent())
def action(self, req, id): def action(self, req, id):
""" multi-purpose method used to reboot, rebuild, and """ multi-purpose method used to reboot, rebuild, and
resize a server """ resize a server """
if not req.environ.has_key('inst_dict'): input_dict = self._deserialize(req.body, req)
return exc.HTTPUnprocessableEntity() try:
reboot_type = input_dict['reboot']['type']
except Exception:
raise faults.Fault(webob.exc.HTTPNotImplemented())
opaque_id = _instance_id_translator().from_rs_id(id)
cloud.reboot(opaque_id)
def _build_server_instance(self, req): def _build_server_instance(self, req, env):
"""Build instance data structure and save it to the data store.""" """Build instance data structure and save it to the data store."""
ltime = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()) ltime = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
inst = {} inst = {}
env = req.environ['inst_dict'] inst_id_trans = _instance_id_translator()
image_id = env['server']['imageId']
opaque_id = translator_instance().from_rs_id(image_id)
inst['name'] = env['server']['server_name']
inst['image_id'] = opaque_id
inst['instance_type'] = env['server']['flavorId']
user_id = req.environ['nova.context']['user']['id'] user_id = req.environ['nova.context']['user']['id']
inst['user_id'] = user_id
flavor_id = env['server']['flavorId']
instance_type, flavor = [(k, v) for k, v in
instance_types.INSTANCE_TYPES.iteritems()
if v['flavorid'] == flavor_id][0]
image_id = env['server']['imageId']
img_service, image_id_trans = _image_service()
opaque_image_id = image_id_trans.to_rs_id(image_id)
image = img_service.show(opaque_image_id)
if not image:
raise Exception, "Image not found"
inst['server_name'] = env['server']['name']
inst['image_id'] = opaque_image_id
inst['user_id'] = user_id
inst['launch_time'] = ltime inst['launch_time'] = ltime
inst['mac_address'] = utils.generate_mac() inst['mac_address'] = utils.generate_mac()
inst['project_id'] = user_id
inst['project_id'] = env['project']['id'] inst['state_description'] = 'scheduling'
inst['reservation_id'] = reservation inst['kernel_id'] = image.get('kernelId', FLAGS.default_kernel)
reservation = utils.generate_uid('r') inst['ramdisk_id'] = image.get('ramdiskId', FLAGS.default_ramdisk)
inst['reservation_id'] = utils.generate_uid('r')
address = self.network.allocate_ip( inst['display_name'] = env['server']['name']
inst['user_id'], inst['display_description'] = env['server']['name']
inst['project_id'],
mac=inst['mac_address'])
inst['private_dns_name'] = str(address) #TODO(dietz) this may be ill advised
inst['bridge_name'] = network.BridgedNetwork.get_network_for_project( key_pair_ref = self.db_driver.key_pair_get_all_by_user(
inst['user_id'], None, user_id)[0]
inst['project_id'],
'default')['bridge_name'] inst['key_data'] = key_pair_ref['public_key']
inst['key_name'] = key_pair_ref['name']
#TODO(dietz) stolen from ec2 api, see TODO there
inst['security_group'] = 'default'
# Flavor related attributes
inst['instance_type'] = instance_type
inst['memory_mb'] = flavor['memory_mb']
inst['vcpus'] = flavor['vcpus']
inst['local_gb'] = flavor['local_gb']
ref = self.db_driver.instance_create(None, inst) ref = self.db_driver.instance_create(None, inst)
inst['id'] = ref.id inst['id'] = inst_id_trans.to_rs_id(ref.ec2_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?
inst['launch_index'] = 0
inst['hostname'] = ref.ec2_id
self.db_driver.instance_update(None, inst['id'], inst)
network_manager = utils.import_object(FLAGS.rs_network_manager)
address = network_manager.allocate_fixed_ip(api_context,
inst['id'])
# 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)
rpc.call(network_topic,
{"method": "setup_fixed_ip",
"args": {"context": None,
"address": address}})
return inst return inst
def _get_network_topic(self, user_id):
"""Retrieves the network host for a project"""
network_ref = self.db_driver.project_get_network(None,
user_id)
host = network_ref['host']
if not host:
host = rpc.call(FLAGS.network_topic,
{"method": "set_network_host",
"args": {"context": None,
"project_id": user_id}})
return self.db_driver.queue_get_for(None, FLAGS.network_topic, host)

View File

@@ -256,8 +256,7 @@ class LdapDriver(object):
if not self.__user_exists(uid): if not self.__user_exists(uid):
raise exception.NotFound("User %s doesn't exist" % uid) raise exception.NotFound("User %s doesn't exist" % uid)
self.__remove_from_all(uid) self.__remove_from_all(uid)
self.conn.delete_s('uid=%s,%s' % (uid, self.conn.delete_s(self.__uid_to_dn(uid))
FLAGS.ldap_user_subtree))
def delete_project(self, project_id): def delete_project(self, project_id):
"""Delete a project""" """Delete a project"""
@@ -265,6 +264,19 @@ class LdapDriver(object):
self.__delete_roles(project_dn) self.__delete_roles(project_dn)
self.__delete_group(project_dn) self.__delete_group(project_dn)
def modify_user(self, uid, access_key=None, secret_key=None, admin=None):
"""Modify an existing project"""
if not access_key and not secret_key and admin is None:
return
attr = []
if access_key:
attr.append((self.ldap.MOD_REPLACE, 'accessKey', access_key))
if secret_key:
attr.append((self.ldap.MOD_REPLACE, 'secretKey', secret_key))
if admin is not None:
attr.append((self.ldap.MOD_REPLACE, 'isAdmin', str(admin).upper()))
self.conn.modify_s(self.__uid_to_dn(uid), attr)
def __user_exists(self, uid): def __user_exists(self, uid):
"""Check if user exists""" """Check if user exists"""
return self.get_user(uid) != None return self.get_user(uid) != None

View File

@@ -630,6 +630,12 @@ class AuthManager(object):
with self.driver() as drv: with self.driver() as drv:
drv.delete_user(uid) drv.delete_user(uid)
def modify_user(self, user, access_key=None, secret_key=None, admin=None):
"""Modify credentials for a user"""
uid = User.safe_id(user)
with self.driver() as drv:
drv.modify_user(uid, access_key, secret_key, admin)
def get_credentials(self, user, project=None): def get_credentials(self, user, project=None):
"""Get credential zip for user in project""" """Get credential zip for user in project"""
if not isinstance(user, User): if not isinstance(user, User):

View File

@@ -406,9 +406,12 @@ def network_index_count(context):
return IMPL.network_index_count(context) return IMPL.network_index_count(context)
def network_index_create(context, values): def network_index_create_safe(context, values):
"""Create a network index from the values dict""" """Create a network index from the values dict
return IMPL.network_index_create(context, values)
The index is not returned. If the create violates the unique
constraints because the index already exists, no exception is raised."""
return IMPL.network_index_create_safe(context, values)
def network_set_cidr(context, network_id, cidr): def network_set_cidr(context, network_id, cidr):

View File

@@ -26,7 +26,9 @@ from nova import utils
from nova.db.sqlalchemy import models from nova.db.sqlalchemy import models
from nova.db.sqlalchemy.session import get_session from nova.db.sqlalchemy.session import get_session
from sqlalchemy import or_ from sqlalchemy import or_
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import joinedload_all from sqlalchemy.orm import joinedload_all
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.sql import exists, func from sqlalchemy.sql import exists, func
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@@ -671,11 +673,14 @@ def network_index_count(_context):
return models.NetworkIndex.count() return models.NetworkIndex.count()
def network_index_create(_context, values): def network_index_create_safe(_context, values):
network_index_ref = models.NetworkIndex() network_index_ref = models.NetworkIndex()
for (key, value) in values.iteritems(): for (key, value) in values.iteritems():
network_index_ref[key] = value network_index_ref[key] = value
try:
network_index_ref.save() network_index_ref.save()
except IntegrityError:
pass
def network_set_host(_context, network_id, host_id): def network_set_host(_context, network_id, host_id):

View File

@@ -199,6 +199,8 @@ class Instance(BASE, NovaBase):
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
ec2_id = Column(String(10), unique=True) ec2_id = Column(String(10), unique=True)
admin_pass = Column(String(255))
user_id = Column(String(255)) user_id = Column(String(255))
project_id = Column(String(255)) project_id = Column(String(255))
@@ -239,7 +241,6 @@ class Instance(BASE, NovaBase):
vcpus = Column(Integer) vcpus = Column(Integer)
local_gb = Column(Integer) local_gb = Column(Integer)
hostname = Column(String(255)) hostname = Column(String(255))
host = Column(String(255)) # , ForeignKey('hosts.id')) host = Column(String(255)) # , ForeignKey('hosts.id'))
@@ -253,6 +254,10 @@ class Instance(BASE, NovaBase):
scheduled_at = Column(DateTime) scheduled_at = Column(DateTime)
launched_at = Column(DateTime) launched_at = Column(DateTime)
terminated_at = Column(DateTime) terminated_at = Column(DateTime)
display_name = Column(String(255))
display_description = Column(String(255))
# TODO(vish): see Ewan's email about state improvements, probably # TODO(vish): see Ewan's email about state improvements, probably
# should be in a driver base class or some such # should be in a driver base class or some such
# vmstate_state = running, halted, suspended, paused # vmstate_state = running, halted, suspended, paused
@@ -289,6 +294,10 @@ class Volume(BASE, NovaBase):
launched_at = Column(DateTime) launched_at = Column(DateTime)
terminated_at = Column(DateTime) terminated_at = Column(DateTime)
display_name = Column(String(255))
display_description = Column(String(255))
class Quota(BASE, NovaBase): class Quota(BASE, NovaBase):
"""Represents quota overrides for a project""" """Represents quota overrides for a project"""
__tablename__ = 'quotas' __tablename__ = 'quotas'
@@ -398,7 +407,7 @@ class NetworkIndex(BASE, NovaBase):
""" """
__tablename__ = 'network_indexes' __tablename__ = 'network_indexes'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
index = Column(Integer) index = Column(Integer, unique=True)
network_id = Column(Integer, ForeignKey('networks.id'), nullable=True) network_id = Column(Integer, ForeignKey('networks.id'), nullable=True)
network = relationship(Network, backref=backref('network_index', network = relationship(Network, backref=backref('network_index',
uselist=False)) uselist=False))

View File

@@ -188,6 +188,8 @@ DEFINE_string('rabbit_userid', 'guest', 'rabbit userid')
DEFINE_string('rabbit_password', 'guest', 'rabbit password') DEFINE_string('rabbit_password', 'guest', 'rabbit password')
DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host') DEFINE_string('rabbit_virtual_host', '/', 'rabbit virtual host')
DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to')
DEFINE_string('cc_host', '127.0.0.1', 'ip of api server')
DEFINE_integer('cc_port', 8773, 'cloud controller port')
DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud', DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud',
'Url to ec2 api server') 'Url to ec2 api server')

View File

@@ -43,3 +43,10 @@ class Manager(object):
def periodic_tasks(self, context=None): def periodic_tasks(self, context=None):
"""Tasks to be run at a periodic interval""" """Tasks to be run at a periodic interval"""
yield yield
def init_host(self):
"""Do any initialization that needs to be run if this is a standalone service.
Child classes should override this method.
"""
pass

View File

@@ -39,10 +39,31 @@ flags.DEFINE_string('public_interface', 'vlan1',
'Interface for public IP addresses') 'Interface for public IP addresses')
flags.DEFINE_string('bridge_dev', 'eth0', flags.DEFINE_string('bridge_dev', 'eth0',
'network device for bridges') 'network device for bridges')
flags.DEFINE_string('routing_source_ip', '127.0.0.1',
'Public IP of network host')
flags.DEFINE_bool('use_nova_chains', False,
'use the nova_ routing chains instead of default')
DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)] DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)]
def init_host():
"""Basic networking setup goes here"""
# NOTE(devcamcar): Cloud public DNAT entries, CloudPipe port
# forwarding entries and a default DNAT entry.
_confirm_rule("PREROUTING", "-t nat -s 0.0.0.0/0 "
"-d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT "
"--to-destination %s:%s" % (FLAGS.cc_host, FLAGS.cc_port))
# NOTE(devcamcar): Cloud public SNAT entries and the default
# SNAT rule for outbound traffic.
_confirm_rule("POSTROUTING", "-t nat -s %s "
"-j SNAT --to-source %s"
% (FLAGS.private_range, FLAGS.routing_source_ip))
_confirm_rule("POSTROUTING", "-t nat -s %s -j MASQUERADE" %
FLAGS.private_range)
_confirm_rule("POSTROUTING", "-t nat -s %(range)s -d %(range)s -j ACCEPT" %
{'range': FLAGS.private_range})
def bind_floating_ip(floating_ip): def bind_floating_ip(floating_ip):
"""Bind ip to public interface""" """Bind ip to public interface"""
@@ -58,37 +79,37 @@ def unbind_floating_ip(floating_ip):
def ensure_vlan_forward(public_ip, port, private_ip): def ensure_vlan_forward(public_ip, port, private_ip):
"""Sets up forwarding rules for vlan""" """Sets up forwarding rules for vlan"""
_confirm_rule("FORWARD -d %s -p udp --dport 1194 -j ACCEPT" % private_ip) _confirm_rule("FORWARD", "-d %s -p udp --dport 1194 -j ACCEPT" %
_confirm_rule( private_ip)
"PREROUTING -t nat -d %s -p udp --dport %s -j DNAT --to %s:1194" _confirm_rule("PREROUTING",
"-t nat -d %s -p udp --dport %s -j DNAT --to %s:1194"
% (public_ip, port, private_ip)) % (public_ip, port, private_ip))
def ensure_floating_forward(floating_ip, fixed_ip): def ensure_floating_forward(floating_ip, fixed_ip):
"""Ensure floating ip forwarding rule""" """Ensure floating ip forwarding rule"""
_confirm_rule("PREROUTING -t nat -d %s -j DNAT --to %s" _confirm_rule("PREROUTING", "-t nat -d %s -j DNAT --to %s"
% (floating_ip, fixed_ip)) % (floating_ip, fixed_ip))
_confirm_rule("POSTROUTING -t nat -s %s -j SNAT --to %s" _confirm_rule("POSTROUTING", "-t nat -s %s -j SNAT --to %s"
% (fixed_ip, floating_ip)) % (fixed_ip, floating_ip))
# TODO(joshua): Get these from the secgroup datastore entries # TODO(joshua): Get these from the secgroup datastore entries
_confirm_rule("FORWARD -d %s -p icmp -j ACCEPT" _confirm_rule("FORWARD", "-d %s -p icmp -j ACCEPT"
% (fixed_ip)) % (fixed_ip))
for (protocol, port) in DEFAULT_PORTS: for (protocol, port) in DEFAULT_PORTS:
_confirm_rule( _confirm_rule("FORWARD","-d %s -p %s --dport %s -j ACCEPT"
"FORWARD -d %s -p %s --dport %s -j ACCEPT"
% (fixed_ip, protocol, port)) % (fixed_ip, protocol, port))
def remove_floating_forward(floating_ip, fixed_ip): def remove_floating_forward(floating_ip, fixed_ip):
"""Remove forwarding for floating ip""" """Remove forwarding for floating ip"""
_remove_rule("PREROUTING -t nat -d %s -j DNAT --to %s" _remove_rule("PREROUTING", "-t nat -d %s -j DNAT --to %s"
% (floating_ip, fixed_ip)) % (floating_ip, fixed_ip))
_remove_rule("POSTROUTING -t nat -s %s -j SNAT --to %s" _remove_rule("POSTROUTING", "-t nat -s %s -j SNAT --to %s"
% (fixed_ip, floating_ip)) % (fixed_ip, floating_ip))
_remove_rule("FORWARD -d %s -p icmp -j ACCEPT" _remove_rule("FORWARD", "-d %s -p icmp -j ACCEPT"
% (fixed_ip)) % (fixed_ip))
for (protocol, port) in DEFAULT_PORTS: for (protocol, port) in DEFAULT_PORTS:
_remove_rule("FORWARD -d %s -p %s --dport %s -j ACCEPT" _remove_rule("FORWARD", "-d %s -p %s --dport %s -j ACCEPT"
% (fixed_ip, protocol, port)) % (fixed_ip, protocol, port))
@@ -124,9 +145,10 @@ def ensure_bridge(bridge, interface, net_attrs=None):
net_attrs['gateway'], net_attrs['gateway'],
net_attrs['broadcast'], net_attrs['broadcast'],
net_attrs['netmask'])) net_attrs['netmask']))
_confirm_rule("FORWARD --in-interface %s -j ACCEPT" % bridge)
else: else:
_execute("sudo ifconfig %s up" % bridge) _execute("sudo ifconfig %s up" % bridge)
_confirm_rule("FORWARD", "--in-interface %s -j ACCEPT" % bridge)
_confirm_rule("FORWARD", "--out-interface %s -j ACCEPT" % bridge)
def get_dhcp_hosts(context, network_id): def get_dhcp_hosts(context, network_id):
@@ -195,15 +217,19 @@ def _device_exists(device):
return not err return not err
def _confirm_rule(cmd): def _confirm_rule(chain, cmd):
"""Delete and re-add iptables rule""" """Delete and re-add iptables rule"""
_execute("sudo iptables --delete %s" % (cmd), check_exit_code=False) if FLAGS.use_nova_chains:
_execute("sudo iptables -I %s" % (cmd)) chain = "nova_%s" % chain.lower()
_execute("sudo iptables --delete %s %s" % (chain, cmd), check_exit_code=False)
_execute("sudo iptables -I %s %s" % (chain, cmd))
def _remove_rule(cmd): def _remove_rule(chain, cmd):
"""Remove iptables rule""" """Remove iptables rule"""
_execute("sudo iptables --delete %s" % (cmd)) if FLAGS.use_nova_chains:
chain = "%S" % chain.lower()
_execute("sudo iptables --delete %s %s" % (chain, cmd))
def _dnsmasq_cmd(net): def _dnsmasq_cmd(net):

View File

@@ -236,6 +236,11 @@ class VlanManager(NetworkManager):
if num: if num:
logging.debug("Dissassociated %s stale fixed ip(s)", num) logging.debug("Dissassociated %s stale fixed ip(s)", num)
def init_host(self):
"""Do any initialization that needs to be run if this is a
standalone service.
"""
self.driver.init_host()
def allocate_fixed_ip(self, context, instance_id, *args, **kwargs): def allocate_fixed_ip(self, context, instance_id, *args, **kwargs):
"""Gets a fixed ip from the pool""" """Gets a fixed ip from the pool"""
@@ -354,7 +359,7 @@ class VlanManager(NetworkManager):
This could use a manage command instead of keying off of a flag""" This could use a manage command instead of keying off of a flag"""
if not self.db.network_index_count(context): if not self.db.network_index_count(context):
for index in range(FLAGS.num_networks): for index in range(FLAGS.num_networks):
self.db.network_index_create(context, {'index': index}) self.db.network_index_create_safe(context, {'index': index})
def _on_set_network_host(self, context, network_id): def _on_set_network_host(self, context, network_id):
"""Called when this host becomes the host for a project""" """Called when this host becomes the host for a project"""

View File

@@ -22,7 +22,7 @@
.. automodule:: nova.objectstore .. automodule:: nova.objectstore
:platform: Unix :platform: Unix
:synopsis: Currently a trivial file-based system, getting extended w/ mongo. :synopsis: Currently a trivial file-based system, getting extended w/ swift.
.. moduleauthor:: Jesse Andrews <jesse@ansolabs.com> .. moduleauthor:: Jesse Andrews <jesse@ansolabs.com>
.. moduleauthor:: Devin Carlen <devin.carlen@gmail.com> .. moduleauthor:: Devin Carlen <devin.carlen@gmail.com>
.. moduleauthor:: Vishvananda Ishaya <vishvananda@yahoo.com> .. moduleauthor:: Vishvananda Ishaya <vishvananda@yahoo.com>

View File

@@ -352,6 +352,8 @@ class ImagesResource(resource.Resource):
m[u'imageType'] = m['type'] m[u'imageType'] = m['type']
elif 'imageType' in m: elif 'imageType' in m:
m[u'type'] = m['imageType'] m[u'type'] = m['imageType']
if 'displayName' not in m:
m[u'displayName'] = u''
return m return m
request.write(json.dumps([decorate(i.metadata) for i in images])) request.write(json.dumps([decorate(i.metadata) for i in images]))
@@ -382,16 +384,25 @@ class ImagesResource(resource.Resource):
def render_POST(self, request): # pylint: disable-msg=R0201 def render_POST(self, request): # pylint: disable-msg=R0201
"""Update image attributes: public/private""" """Update image attributes: public/private"""
# image_id required for all requests
image_id = get_argument(request, 'image_id', u'') image_id = get_argument(request, 'image_id', u'')
operation = get_argument(request, 'operation', u'')
image_object = image.Image(image_id) image_object = image.Image(image_id)
if not image_object.is_authorized(request.context): if not image_object.is_authorized(request.context):
logging.debug("not authorized for render_POST in images")
raise exception.NotAuthorized raise exception.NotAuthorized
operation = get_argument(request, 'operation', u'')
if operation:
# operation implies publicity toggle
logging.debug("handling publicity toggle")
image_object.set_public(operation=='add') image_object.set_public(operation=='add')
else:
# other attributes imply update
logging.debug("update user fields")
clean_args = {}
for arg in request.args.keys():
clean_args[arg] = request.args[arg][0]
image_object.update_user_editable_fields(clean_args)
return '' return ''
def render_DELETE(self, request): # pylint: disable-msg=R0201 def render_DELETE(self, request): # pylint: disable-msg=R0201

View File

@@ -82,6 +82,16 @@ class Image(object):
with open(os.path.join(self.path, 'info.json'), 'w') as f: with open(os.path.join(self.path, 'info.json'), 'w') as f:
json.dump(md, f) json.dump(md, f)
def update_user_editable_fields(self, args):
"""args is from the request parameters, so requires extra cleaning"""
fields = {'display_name': 'displayName', 'description': 'description'}
info = self.metadata
for field in fields.keys():
if field in args:
info[fields[field]] = args[field]
with open(os.path.join(self.path, 'info.json'), 'w') as f:
json.dump(info, f)
@staticmethod @staticmethod
def all(): def all():
images = [] images = []

View File

@@ -54,6 +54,7 @@ class Service(object, service.Service):
self.topic = topic self.topic = topic
manager_class = utils.import_class(manager) manager_class = utils.import_class(manager)
self.manager = manager_class(host=host, *args, **kwargs) self.manager = manager_class(host=host, *args, **kwargs)
self.manager.init_host()
self.model_disconnected = False self.model_disconnected = False
super(Service, self).__init__(*args, **kwargs) super(Service, self).__init__(*args, **kwargs)
try: try:

View File

@@ -17,6 +17,7 @@
import unittest import unittest
from nova.api.rackspace import limited
from nova.api.rackspace import RateLimitingMiddleware from nova.api.rackspace import RateLimitingMiddleware
from nova.tests.api.test_helper import * from nova.tests.api.test_helper import *
from webob import Request from webob import Request
@@ -77,3 +78,31 @@ class RateLimitingMiddlewareTest(unittest.TestCase):
self.assertEqual(middleware.limiter.__class__.__name__, "Limiter") self.assertEqual(middleware.limiter.__class__.__name__, "Limiter")
middleware = RateLimitingMiddleware(APIStub(), service_host='foobar') middleware = RateLimitingMiddleware(APIStub(), service_host='foobar')
self.assertEqual(middleware.limiter.__class__.__name__, "WSGIAppProxy") self.assertEqual(middleware.limiter.__class__.__name__, "WSGIAppProxy")
class LimiterTest(unittest.TestCase):
def testLimiter(self):
items = range(2000)
req = Request.blank('/')
self.assertEqual(limited(items, req), items[ :1000])
req = Request.blank('/?offset=0')
self.assertEqual(limited(items, req), items[ :1000])
req = Request.blank('/?offset=3')
self.assertEqual(limited(items, req), items[3:1003])
req = Request.blank('/?offset=2005')
self.assertEqual(limited(items, req), [])
req = Request.blank('/?limit=10')
self.assertEqual(limited(items, req), items[ :10])
req = Request.blank('/?limit=0')
self.assertEqual(limited(items, req), items[ :1000])
req = Request.blank('/?limit=3000')
self.assertEqual(limited(items, req), items[ :1000])
req = Request.blank('/?offset=1&limit=3')
self.assertEqual(limited(items, req), items[1:4])
req = Request.blank('/?offset=3&limit=0')
self.assertEqual(limited(items, req), items[3:1003])
req = Request.blank('/?offset=3&limit=1500')
self.assertEqual(limited(items, req), items[3:1003])
req = Request.blank('/?offset=3000&limit=10')
self.assertEqual(limited(items, req), [])

View File

@@ -1,12 +1,14 @@
import datetime
import unittest
import stubout
import webob import webob
import webob.dec import webob.dec
import unittest
import stubout
import nova.api import nova.api
import nova.api.rackspace.auth import nova.api.rackspace.auth
from nova import auth from nova import auth
from nova.tests.api.rackspace import test_helper from nova.tests.api.rackspace import test_helper
import datetime
class Test(unittest.TestCase): class Test(unittest.TestCase):
def setUp(self): def setUp(self):

View File

@@ -38,7 +38,6 @@ class FlavorsTest(unittest.TestCase):
def test_get_flavor_list(self): def test_get_flavor_list(self):
req = webob.Request.blank('/v1.0/flavors') req = webob.Request.blank('/v1.0/flavors')
res = req.get_response(nova.api.API()) res = req.get_response(nova.api.API())
print res
def test_get_flavor_by_id(self): def test_get_flavor_by_id(self):
pass pass

View File

@@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import stubout
import unittest import unittest
from nova.api.rackspace import images from nova.api.rackspace import images

View File

@@ -26,6 +26,7 @@ import nova.api.rackspace
from nova.api.rackspace import servers from nova.api.rackspace import servers
import nova.db.api import nova.db.api
from nova.db.sqlalchemy.models import Instance from nova.db.sqlalchemy.models import Instance
import nova.rpc
from nova.tests.api.test_helper import * from nova.tests.api.test_helper import *
from nova.tests.api.rackspace import test_helper from nova.tests.api.rackspace import test_helper
@@ -52,8 +53,11 @@ class ServersTest(unittest.TestCase):
test_helper.stub_for_testing(self.stubs) test_helper.stub_for_testing(self.stubs)
test_helper.stub_out_rate_limiting(self.stubs) test_helper.stub_out_rate_limiting(self.stubs)
test_helper.stub_out_auth(self.stubs) test_helper.stub_out_auth(self.stubs)
test_helper.stub_out_id_translator(self.stubs)
test_helper.stub_out_key_pair_funcs(self.stubs)
test_helper.stub_out_image_service(self.stubs)
self.stubs.Set(nova.db.api, 'instance_get_all', return_servers) self.stubs.Set(nova.db.api, 'instance_get_all', return_servers)
self.stubs.Set(nova.db.api, 'instance_get', return_server) self.stubs.Set(nova.db.api, 'instance_get_by_ec2_id', return_server)
self.stubs.Set(nova.db.api, 'instance_get_all_by_user', self.stubs.Set(nova.db.api, 'instance_get_all_by_user',
return_servers) return_servers)
@@ -67,9 +71,6 @@ class ServersTest(unittest.TestCase):
self.assertEqual(res_dict['server']['id'], '1') self.assertEqual(res_dict['server']['id'], '1')
self.assertEqual(res_dict['server']['name'], 'server1') self.assertEqual(res_dict['server']['name'], 'server1')
def test_get_backup_schedule(self):
pass
def test_get_server_list(self): def test_get_server_list(self):
req = webob.Request.blank('/v1.0/servers') req = webob.Request.blank('/v1.0/servers')
res = req.get_response(nova.api.API()) res = req.get_response(nova.api.API())
@@ -82,25 +83,87 @@ class ServersTest(unittest.TestCase):
self.assertEqual(s.get('imageId', None), None) self.assertEqual(s.get('imageId', None), None)
i += 1 i += 1
#def test_create_instance(self): def test_create_instance(self):
# test_helper.stub_out_image_translator(self.stubs) def server_update(context, id, params):
# body = dict(server=dict(
# name='server_test', imageId=2, flavorId=2, metadata={},
# personality = {}
# ))
# req = webob.Request.blank('/v1.0/servers')
# req.method = 'POST'
# req.body = json.dumps(body)
# res = req.get_response(nova.api.API())
# print res
def test_update_server_password(self):
pass pass
def test_update_server_name(self): def instance_create(context, inst):
class Foo(object):
ec2_id = 1
return Foo()
def fake_method(*args, **kwargs):
pass pass
def project_get_network(context, user_id):
return dict(id='1', host='localhost')
def queue_get_for(context, *args):
return 'network_topic'
self.stubs.Set(nova.db.api, 'project_get_network', project_get_network)
self.stubs.Set(nova.db.api, 'instance_create', instance_create)
self.stubs.Set(nova.rpc, 'cast', fake_method)
self.stubs.Set(nova.rpc, 'call', fake_method)
self.stubs.Set(nova.db.api, 'instance_update',
server_update)
self.stubs.Set(nova.db.api, 'queue_get_for', queue_get_for)
self.stubs.Set(nova.network.manager.FlatManager, 'allocate_fixed_ip',
fake_method)
test_helper.stub_out_id_translator(self.stubs)
body = dict(server=dict(
name='server_test', imageId=2, flavorId=2, metadata={},
personality = {}
))
req = webob.Request.blank('/v1.0/servers')
req.method = 'POST'
req.body = json.dumps(body)
res = req.get_response(nova.api.API())
self.assertEqual(res.status_int, 200)
def test_update_no_body(self):
req = webob.Request.blank('/v1.0/servers/1')
req.method = 'PUT'
res = req.get_response(nova.api.API())
self.assertEqual(res.status_int, 422)
def test_update_bad_params(self):
""" Confirm that update is filtering params """
inst_dict = dict(cat='leopard', name='server_test', adminPass='bacon')
self.body = json.dumps(dict(server=inst_dict))
def server_update(context, id, params):
self.update_called = True
filtered_dict = dict(name='server_test', admin_pass='bacon')
self.assertEqual(params, filtered_dict)
self.stubs.Set(nova.db.api, 'instance_update',
server_update)
req = webob.Request.blank('/v1.0/servers/1')
req.method = 'PUT'
req.body = self.body
req.get_response(nova.api.API())
def test_update_server(self):
inst_dict = dict(name='server_test', adminPass='bacon')
self.body = json.dumps(dict(server=inst_dict))
def server_update(context, id, params):
filtered_dict = dict(name='server_test', admin_pass='bacon')
self.assertEqual(params, filtered_dict)
self.stubs.Set(nova.db.api, 'instance_update',
server_update)
req = webob.Request.blank('/v1.0/servers/1')
req.method = 'PUT'
req.body = self.body
req.get_response(nova.api.API())
def test_create_backup_schedules(self): def test_create_backup_schedules(self):
req = webob.Request.blank('/v1.0/servers/1/backup_schedules') req = webob.Request.blank('/v1.0/servers/1/backup_schedules')
req.method = 'POST' req.method = 'POST'

View File

@@ -15,6 +15,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import stubout
import unittest import unittest
from nova.api.rackspace import sharedipgroups from nova.api.rackspace import sharedipgroups

View File

@@ -9,6 +9,7 @@ from nova import utils
from nova import flags from nova import flags
import nova.api.rackspace.auth import nova.api.rackspace.auth
import nova.api.rackspace._id_translator import nova.api.rackspace._id_translator
from nova.image import service
from nova.wsgi import Router from nova.wsgi import Router
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
@@ -40,7 +41,19 @@ def fake_wsgi(self, req):
req.environ['inst_dict'] = json.loads(req.body) req.environ['inst_dict'] = json.loads(req.body)
return self.application return self.application
def stub_out_image_translator(stubs): def stub_out_key_pair_funcs(stubs):
def key_pair(context, user_id):
return [dict(name='key', public_key='public_key')]
stubs.Set(nova.db.api, 'key_pair_get_all_by_user',
key_pair)
def stub_out_image_service(stubs):
def fake_image_show(meh, id):
return dict(kernelId=1, ramdiskId=1)
stubs.Set(nova.image.service.LocalImageService, 'show', fake_image_show)
def stub_out_id_translator(stubs):
class FakeTranslator(object): class FakeTranslator(object):
def __init__(self, id_type, service_name): def __init__(self, id_type, service_name):
pass pass

View File

@@ -0,0 +1,40 @@
import unittest
import webob
import webob.dec
import webob.exc
from nova.api.rackspace import faults
class TestFaults(unittest.TestCase):
def test_fault_parts(self):
req = webob.Request.blank('/.xml')
f = faults.Fault(webob.exc.HTTPBadRequest(explanation='scram'))
resp = req.get_response(f)
first_two_words = resp.body.strip().split()[:2]
self.assertEqual(first_two_words, ['<badRequest', 'code="400">'])
body_without_spaces = ''.join(resp.body.split())
self.assertTrue('<message>scram</message>' in body_without_spaces)
def test_retry_header(self):
req = webob.Request.blank('/.xml')
exc = webob.exc.HTTPRequestEntityTooLarge(explanation='sorry',
headers={'Retry-After': 4})
f = faults.Fault(exc)
resp = req.get_response(f)
first_two_words = resp.body.strip().split()[:2]
self.assertEqual(first_two_words, ['<overLimit', 'code="413">'])
body_sans_spaces = ''.join(resp.body.split())
self.assertTrue('<message>sorry</message>' in body_sans_spaces)
self.assertTrue('<retryAfter>4</retryAfter>' in body_sans_spaces)
self.assertEqual(resp.headers['Retry-After'], 4)
def test_raise(self):
@webob.dec.wsgify
def raiser(req):
raise faults.Fault(webob.exc.HTTPNotFound(explanation='whut?'))
req = webob.Request.blank('/.xml')
resp = req.get_response(raiser)
self.assertEqual(resp.status_int, 404)
self.assertTrue('whut?' in resp.body)

View File

@@ -28,26 +28,71 @@ from nova.api.ec2 import cloud
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
class user_generator(object):
def __init__(self, manager, **user_state):
if 'name' not in user_state:
user_state['name'] = 'test1'
self.manager = manager
self.user = manager.create_user(**user_state)
class AuthTestCase(test.TrialTestCase): def __enter__(self):
flush_db = False return self.user
def __exit__(self, value, type, trace):
self.manager.delete_user(self.user)
class project_generator(object):
def __init__(self, manager, **project_state):
if 'name' not in project_state:
project_state['name'] = 'testproj'
if 'manager_user' not in project_state:
project_state['manager_user'] = 'test1'
self.manager = manager
self.project = manager.create_project(**project_state)
def __enter__(self):
return self.project
def __exit__(self, value, type, trace):
self.manager.delete_project(self.project)
class user_and_project_generator(object):
def __init__(self, manager, user_state={}, project_state={}):
self.manager = manager
if 'name' not in user_state:
user_state['name'] = 'test1'
if 'name' not in project_state:
project_state['name'] = 'testproj'
if 'manager_user' not in project_state:
project_state['manager_user'] = 'test1'
self.user = manager.create_user(**user_state)
self.project = manager.create_project(**project_state)
def __enter__(self):
return (self.user, self.project)
def __exit__(self, value, type, trace):
self.manager.delete_user(self.user)
self.manager.delete_project(self.project)
class AuthManagerTestCase(test.TrialTestCase):
def setUp(self): def setUp(self):
super(AuthTestCase, self).setUp() super(AuthManagerTestCase, self).setUp()
self.flags(connection_type='fake') self.flags(connection_type='fake')
self.manager = manager.AuthManager() self.manager = manager.AuthManager()
def test_001_can_create_users(self): def test_create_and_find_user(self):
self.manager.create_user('test1', 'access', 'secret') with user_generator(self.manager):
self.manager.create_user('test2') self.assert_(self.manager.get_user('test1'))
def test_002_can_get_user(self): def test_create_and_find_with_properties(self):
user = self.manager.get_user('test1') with user_generator(self.manager, name="herbert", secret="classified",
access="private-party"):
def test_003_can_retreive_properties(self): u = self.manager.get_user('herbert')
user = self.manager.get_user('test1') self.assertEqual('herbert', u.id)
self.assertEqual('test1', user.id) self.assertEqual('herbert', u.name)
self.assertEqual('access', user.access) self.assertEqual('classified', u.secret)
self.assertEqual('secret', user.secret) self.assertEqual('private-party', u.access)
def test_004_signature_is_valid(self): def test_004_signature_is_valid(self):
#self.assertTrue(self.manager.authenticate( **boto.generate_url ... ? ? ? )) #self.assertTrue(self.manager.authenticate( **boto.generate_url ... ? ? ? ))
@@ -64,62 +109,106 @@ class AuthTestCase(test.TrialTestCase):
'export S3_URL="http://127.0.0.1:3333/"\n' + 'export S3_URL="http://127.0.0.1:3333/"\n' +
'export EC2_USER_ID="test1"\n') 'export EC2_USER_ID="test1"\n')
def test_010_can_list_users(self): def test_can_list_users(self):
with user_generator(self.manager):
with user_generator(self.manager, name="test2"):
users = self.manager.get_users() users = self.manager.get_users()
logging.warn(users) self.assert_(filter(lambda u: u.id == 'test1', users))
self.assertTrue(filter(lambda u: u.id == 'test1', users)) self.assert_(filter(lambda u: u.id == 'test2', users))
self.assert_(not filter(lambda u: u.id == 'test3', users))
def test_101_can_add_user_role(self): def test_can_add_and_remove_user_role(self):
with user_generator(self.manager):
self.assertFalse(self.manager.has_role('test1', 'itsec')) self.assertFalse(self.manager.has_role('test1', 'itsec'))
self.manager.add_role('test1', 'itsec') self.manager.add_role('test1', 'itsec')
self.assertTrue(self.manager.has_role('test1', 'itsec')) self.assertTrue(self.manager.has_role('test1', 'itsec'))
def test_199_can_remove_user_role(self):
self.assertTrue(self.manager.has_role('test1', 'itsec'))
self.manager.remove_role('test1', 'itsec') self.manager.remove_role('test1', 'itsec')
self.assertFalse(self.manager.has_role('test1', 'itsec')) self.assertFalse(self.manager.has_role('test1', 'itsec'))
def test_201_can_create_project(self): def test_can_create_and_get_project(self):
project = self.manager.create_project('testproj', 'test1', 'A test project', ['test1']) with user_and_project_generator(self.manager) as (u,p):
self.assertTrue(filter(lambda p: p.name == 'testproj', self.manager.get_projects())) self.assert_(self.manager.get_user('test1'))
self.assertEqual(project.name, 'testproj') self.assert_(self.manager.get_user('test1'))
self.assertEqual(project.description, 'A test project') self.assert_(self.manager.get_project('testproj'))
self.assertEqual(project.project_manager_id, 'test1')
self.assertTrue(project.has_member('test1'))
def test_202_user1_is_project_member(self): def test_can_list_projects(self):
self.assertTrue(self.manager.get_user('test1').is_project_member('testproj')) with user_and_project_generator(self.manager):
with project_generator(self.manager, name="testproj2"):
projects = self.manager.get_projects()
self.assert_(filter(lambda p: p.name == 'testproj', projects))
self.assert_(filter(lambda p: p.name == 'testproj2', projects))
self.assert_(not filter(lambda p: p.name == 'testproj3',
projects))
def test_203_user2_is_not_project_member(self): def test_can_create_and_get_project_with_attributes(self):
self.assertFalse(self.manager.get_user('test2').is_project_member('testproj')) with user_generator(self.manager):
with project_generator(self.manager, description='A test project'):
project = self.manager.get_project('testproj')
self.assertEqual('A test project', project.description)
def test_204_user1_is_project_manager(self): def test_can_create_project_with_manager(self):
self.assertTrue(self.manager.get_user('test1').is_project_manager('testproj')) with user_and_project_generator(self.manager) as (user, project):
self.assertEqual('test1', project.project_manager_id)
self.assertTrue(self.manager.is_project_manager(user, project))
def test_205_user2_is_not_project_manager(self): def test_create_project_assigns_manager_to_members(self):
self.assertFalse(self.manager.get_user('test2').is_project_manager('testproj')) with user_and_project_generator(self.manager) as (user, project):
self.assertTrue(self.manager.is_project_member(user, project))
def test_206_can_add_user_to_project(self): def test_no_extra_project_members(self):
self.manager.add_to_project('test2', 'testproj') with user_generator(self.manager, name='test2') as baduser:
self.assertTrue(self.manager.get_project('testproj').has_member('test2')) with user_and_project_generator(self.manager) as (user, project):
self.assertFalse(self.manager.is_project_member(baduser,
project))
def test_207_can_remove_user_from_project(self): def test_no_extra_project_managers(self):
self.manager.remove_from_project('test2', 'testproj') with user_generator(self.manager, name='test2') as baduser:
self.assertFalse(self.manager.get_project('testproj').has_member('test2')) with user_and_project_generator(self.manager) as (user, project):
self.assertFalse(self.manager.is_project_manager(baduser,
project))
def test_208_can_remove_add_user_with_role(self): def test_can_add_user_to_project(self):
self.manager.add_to_project('test2', 'testproj') with user_generator(self.manager, name='test2') as user:
self.manager.add_role('test2', 'developer', 'testproj') with user_and_project_generator(self.manager) as (_user, project):
self.manager.remove_from_project('test2', 'testproj') self.manager.add_to_project(user, project)
self.assertFalse(self.manager.has_role('test2', 'developer', 'testproj')) project = self.manager.get_project('testproj')
self.manager.add_to_project('test2', 'testproj') self.assertTrue(self.manager.is_project_member(user, project))
self.manager.remove_from_project('test2', 'testproj')
def test_209_can_generate_x509(self): def test_can_remove_user_from_project(self):
# MUST HAVE RUN CLOUD SETUP BY NOW with user_generator(self.manager, name='test2') as user:
self.cloud = cloud.CloudController() with user_and_project_generator(self.manager) as (_user, project):
self.cloud.setup() self.manager.add_to_project(user, project)
_key, cert_str = self.manager._generate_x509_cert('test1', 'testproj') project = self.manager.get_project('testproj')
self.assertTrue(self.manager.is_project_member(user, project))
self.manager.remove_from_project(user, project)
project = self.manager.get_project('testproj')
self.assertFalse(self.manager.is_project_member(user, project))
def test_can_add_remove_user_with_role(self):
with user_generator(self.manager, name='test2') as user:
with user_and_project_generator(self.manager) as (_user, project):
# NOTE(todd): after modifying users you must reload project
self.manager.add_to_project(user, project)
project = self.manager.get_project('testproj')
self.manager.add_role(user, 'developer', project)
self.assertTrue(self.manager.is_project_member(user, project))
self.manager.remove_from_project(user, project)
project = self.manager.get_project('testproj')
self.assertFalse(self.manager.has_role(user, 'developer',
project))
self.assertFalse(self.manager.is_project_member(user, project))
def test_can_generate_x509(self):
# NOTE(todd): this doesn't assert against the auth manager
# so it probably belongs in crypto_unittest
# but I'm leaving it where I found it.
with user_and_project_generator(self.manager) as (user, project):
# NOTE(todd): Should mention why we must setup controller first
# (somebody please clue me in)
cloud_controller = cloud.CloudController()
cloud_controller.setup()
_key, cert_str = self.manager._generate_x509_cert('test1',
'testproj')
logging.debug(cert_str) logging.debug(cert_str)
# Need to verify that it's signed by the right intermediate CA # Need to verify that it's signed by the right intermediate CA
@@ -133,64 +222,103 @@ class AuthTestCase(test.TrialTestCase):
cloud_cert = X509.load_cert_string(cloud_cert) cloud_cert = X509.load_cert_string(cloud_cert)
self.assertTrue(signed_cert.verify(chain_cert.get_pubkey())) self.assertTrue(signed_cert.verify(chain_cert.get_pubkey()))
self.assertTrue(signed_cert.verify(int_cert.get_pubkey())) self.assertTrue(signed_cert.verify(int_cert.get_pubkey()))
if not FLAGS.use_intermediate_ca: if not FLAGS.use_intermediate_ca:
self.assertTrue(signed_cert.verify(cloud_cert.get_pubkey())) self.assertTrue(signed_cert.verify(cloud_cert.get_pubkey()))
else: else:
self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey())) self.assertFalse(signed_cert.verify(cloud_cert.get_pubkey()))
def test_210_can_add_project_role(self): def test_adding_role_to_project_is_ignored_unless_added_to_user(self):
project = self.manager.get_project('testproj') with user_and_project_generator(self.manager) as (user, project):
self.assertFalse(project.has_role('test1', 'sysadmin')) self.assertFalse(self.manager.has_role(user, 'sysadmin', project))
self.manager.add_role('test1', 'sysadmin') self.manager.add_role(user, 'sysadmin', project)
self.assertFalse(project.has_role('test1', 'sysadmin')) # NOTE(todd): it will still show up in get_user_roles(u, project)
project.add_role('test1', 'sysadmin') self.assertFalse(self.manager.has_role(user, 'sysadmin', project))
self.assertTrue(project.has_role('test1', 'sysadmin')) self.manager.add_role(user, 'sysadmin')
self.assertTrue(self.manager.has_role(user, 'sysadmin', project))
def test_211_can_list_project_roles(self): def test_add_user_role_doesnt_infect_project_roles(self):
project = self.manager.get_project('testproj') with user_and_project_generator(self.manager) as (user, project):
user = self.manager.get_user('test1') self.assertFalse(self.manager.has_role(user, 'sysadmin', project))
self.manager.add_role(user, 'netadmin', project) self.manager.add_role(user, 'sysadmin')
self.assertFalse(self.manager.has_role(user, 'sysadmin', project))
def test_can_list_user_roles(self):
with user_and_project_generator(self.manager) as (user, project):
self.manager.add_role(user, 'sysadmin')
roles = self.manager.get_user_roles(user) roles = self.manager.get_user_roles(user)
self.assertTrue('sysadmin' in roles) self.assertTrue('sysadmin' in roles)
self.assertFalse('netadmin' in roles) self.assertFalse('netadmin' in roles)
def test_can_list_project_roles(self):
with user_and_project_generator(self.manager) as (user, project):
self.manager.add_role(user, 'sysadmin')
self.manager.add_role(user, 'sysadmin', project)
self.manager.add_role(user, 'netadmin', project)
project_roles = self.manager.get_user_roles(user, project) project_roles = self.manager.get_user_roles(user, project)
self.assertTrue('sysadmin' in project_roles) self.assertTrue('sysadmin' in project_roles)
self.assertTrue('netadmin' in project_roles) self.assertTrue('netadmin' in project_roles)
# has role should be false because global role is missing # has role should be false user-level role is missing
self.assertFalse(self.manager.has_role(user, 'netadmin', project)) self.assertFalse(self.manager.has_role(user, 'netadmin', project))
def test_can_remove_user_roles(self):
with user_and_project_generator(self.manager) as (user, project):
self.manager.add_role(user, 'sysadmin')
self.assertTrue(self.manager.has_role(user, 'sysadmin'))
self.manager.remove_role(user, 'sysadmin')
self.assertFalse(self.manager.has_role(user, 'sysadmin'))
def test_212_can_remove_project_role(self): def test_removing_user_role_hides_it_from_project(self):
with user_and_project_generator(self.manager) as (user, project):
self.manager.add_role(user, 'sysadmin')
self.manager.add_role(user, 'sysadmin', project)
self.assertTrue(self.manager.has_role(user, 'sysadmin', project))
self.manager.remove_role(user, 'sysadmin')
self.assertFalse(self.manager.has_role(user, 'sysadmin', project))
def test_can_remove_project_role_but_keep_user_role(self):
with user_and_project_generator(self.manager) as (user, project):
self.manager.add_role(user, 'sysadmin')
self.manager.add_role(user, 'sysadmin', project)
self.assertTrue(self.manager.has_role(user, 'sysadmin'))
self.manager.remove_role(user, 'sysadmin', project)
self.assertFalse(self.manager.has_role(user, 'sysadmin', project))
self.assertTrue(self.manager.has_role(user, 'sysadmin'))
def test_can_retrieve_project_by_user(self):
with user_and_project_generator(self.manager) as (user, project):
self.assertEqual(1, len(self.manager.get_projects('test1')))
def test_can_modify_project(self):
with user_and_project_generator(self.manager):
with user_generator(self.manager, name='test2'):
self.manager.modify_project('testproj', 'test2', 'new desc')
project = self.manager.get_project('testproj') project = self.manager.get_project('testproj')
self.assertTrue(project.has_role('test1', 'sysadmin')) self.assertEqual('test2', project.project_manager_id)
project.remove_role('test1', 'sysadmin') self.assertEqual('new desc', project.description)
self.assertFalse(project.has_role('test1', 'sysadmin'))
self.manager.remove_role('test1', 'sysadmin')
self.assertFalse(project.has_role('test1', 'sysadmin'))
def test_214_can_retrieve_project_by_user(self): def test_can_delete_project(self):
project = self.manager.create_project('testproj2', 'test2', 'Another test project', ['test2']) with user_generator(self.manager):
self.assert_(len(self.manager.get_projects()) > 1) self.manager.create_project('testproj', 'test1')
self.assertEqual(len(self.manager.get_projects('test2')), 1) self.assert_(self.manager.get_project('testproj'))
def test_220_can_modify_project(self):
self.manager.modify_project('testproj', 'test2', 'new description')
project = self.manager.get_project('testproj')
self.assertEqual(project.project_manager_id, 'test2')
self.assertEqual(project.description, 'new description')
def test_299_can_delete_project(self):
self.manager.delete_project('testproj') self.manager.delete_project('testproj')
self.assertFalse(filter(lambda p: p.name == 'testproj', self.manager.get_projects())) projectlist = self.manager.get_projects()
self.manager.delete_project('testproj2') self.assert_(not filter(lambda p: p.name == 'testproj',
projectlist))
def test_999_can_delete_users(self): def test_can_delete_user(self):
self.manager.create_user('test1')
self.assert_(self.manager.get_user('test1'))
self.manager.delete_user('test1') self.manager.delete_user('test1')
users = self.manager.get_users() userlist = self.manager.get_users()
self.assertFalse(filter(lambda u: u.id == 'test1', users)) self.assert_(not filter(lambda u: u.id == 'test1', userlist))
self.manager.delete_user('test2')
self.assertEqual(self.manager.get_user('test2'), None) def test_can_modify_users(self):
with user_generator(self.manager):
self.manager.modify_user('test1', 'access', 'secret', True)
user = self.manager.get_user('test1')
self.assertEqual('access', user.access)
self.assertEqual('secret', user.secret)
self.assertTrue(user.is_admin())
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -16,10 +16,13 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json
import logging import logging
from M2Crypto import BIO from M2Crypto import BIO
from M2Crypto import RSA from M2Crypto import RSA
import os
import StringIO import StringIO
import tempfile
import time import time
from twisted.internet import defer from twisted.internet import defer
@@ -36,15 +39,22 @@ from nova.auth import manager
from nova.compute import power_state from nova.compute import power_state
from nova.api.ec2 import context from nova.api.ec2 import context
from nova.api.ec2 import cloud from nova.api.ec2 import cloud
from nova.objectstore import image
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
# Temp dirs for working with image attributes through the cloud controller
# (stole this from objectstore_unittest.py)
OSS_TEMPDIR = tempfile.mkdtemp(prefix='test_oss-')
IMAGES_PATH = os.path.join(OSS_TEMPDIR, 'images')
os.makedirs(IMAGES_PATH)
class CloudTestCase(test.TrialTestCase): class CloudTestCase(test.TrialTestCase):
def setUp(self): def setUp(self):
super(CloudTestCase, self).setUp() super(CloudTestCase, self).setUp()
self.flags(connection_type='fake') self.flags(connection_type='fake', images_path=IMAGES_PATH)
self.conn = rpc.Connection.instance() self.conn = rpc.Connection.instance()
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
@@ -191,3 +201,67 @@ class CloudTestCase(test.TrialTestCase):
#for i in xrange(4): #for i in xrange(4):
# data = self.cloud.get_metadata(instance(i)['private_dns_name']) # data = self.cloud.get_metadata(instance(i)['private_dns_name'])
# self.assert_(data['meta-data']['ami-id'] == 'ami-%s' % i) # self.assert_(data['meta-data']['ami-id'] == 'ami-%s' % i)
@staticmethod
def _fake_set_image_description(ctxt, image_id, description):
from nova.objectstore import handler
class req:
pass
request = req()
request.context = ctxt
request.args = {'image_id': [image_id],
'description': [description]}
resource = handler.ImagesResource()
resource.render_POST(request)
def test_user_editable_image_endpoint(self):
pathdir = os.path.join(FLAGS.images_path, 'ami-testing')
os.mkdir(pathdir)
info = {'isPublic': False}
with open(os.path.join(pathdir, 'info.json'), 'w') as f:
json.dump(info, f)
img = image.Image('ami-testing')
# self.cloud.set_image_description(self.context, 'ami-testing',
# 'Foo Img')
# NOTE(vish): Above won't work unless we start objectstore or create
# a fake version of api/ec2/images.py conn that can
# call methods directly instead of going through boto.
# for now, just cheat and call the method directly
self._fake_set_image_description(self.context, 'ami-testing',
'Foo Img')
self.assertEqual('Foo Img', img.metadata['description'])
self._fake_set_image_description(self.context, 'ami-testing', '')
self.assertEqual('', img.metadata['description'])
def test_update_of_instance_display_fields(self):
inst = db.instance_create({}, {})
self.cloud.update_instance(self.context, inst['ec2_id'],
display_name='c00l 1m4g3')
inst = db.instance_get({}, inst['id'])
self.assertEqual('c00l 1m4g3', inst['display_name'])
db.instance_destroy({}, inst['id'])
def test_update_of_instance_wont_update_private_fields(self):
inst = db.instance_create({}, {})
self.cloud.update_instance(self.context, inst['id'],
mac_address='DE:AD:BE:EF')
inst = db.instance_get({}, inst['id'])
self.assertEqual(None, inst['mac_address'])
db.instance_destroy({}, inst['id'])
def test_update_of_volume_display_fields(self):
vol = db.volume_create({}, {})
self.cloud.update_volume(self.context, vol['id'],
display_name='c00l v0lum3')
vol = db.volume_get({}, vol['id'])
self.assertEqual('c00l v0lum3', vol['display_name'])
db.volume_destroy({}, vol['id'])
def test_update_of_volume_wont_update_private_fields(self):
vol = db.volume_create({}, {})
self.cloud.update_volume(self.context, vol['id'],
mountpoint='/not/here')
vol = db.volume_get({}, vol['id'])
self.assertEqual(None, vol['mountpoint'])
db.volume_destroy({}, vol['id'])

View File

@@ -164,6 +164,12 @@ class ObjectStoreTestCase(test.TrialTestCase):
self.context.project = self.auth_manager.get_project('proj2') self.context.project = self.auth_manager.get_project('proj2')
self.assertFalse(my_img.is_authorized(self.context)) self.assertFalse(my_img.is_authorized(self.context))
# change user-editable fields
my_img.update_user_editable_fields({'display_name': 'my cool image'})
self.assertEqual('my cool image', my_img.metadata['displayName'])
my_img.update_user_editable_fields({'display_name': ''})
self.assert_(not my_img.metadata['displayName'])
class TestHTTPChannel(http.HTTPChannel): class TestHTTPChannel(http.HTTPChannel):
"""Dummy site required for twisted.web""" """Dummy site required for twisted.web"""

View File

@@ -103,7 +103,7 @@ class XenAPIConnection(object):
self._conn.login_with_password(user, pw) self._conn.login_with_password(user, pw)
def list_instances(self): def list_instances(self):
result = [self._conn.xenapi.VM.get_name_label(vm) \ return [self._conn.xenapi.VM.get_name_label(vm) \
for vm in self._conn.xenapi.VM.get_all()] for vm in self._conn.xenapi.VM.get_all()]
@defer.inlineCallbacks @defer.inlineCallbacks

View File

@@ -230,6 +230,15 @@ class Controller(object):
serializer = Serializer(request.environ, _metadata) serializer = Serializer(request.environ, _metadata)
return serializer.to_content_type(data) return serializer.to_content_type(data)
def _deserialize(self, data, request):
"""
Deserialize the request body to the response type requested in request.
Uses self._serialization_metadata if it exists, which is a dict mapping
MIME types to information needed to serialize to that type.
"""
_metadata = getattr(type(self), "_serialization_metadata", {})
serializer = Serializer(request.environ, _metadata)
return serializer.deserialize(data)
class Serializer(object): class Serializer(object):
""" """
@@ -272,10 +281,13 @@ class Serializer(object):
The string must be in the format of a supported MIME type. The string must be in the format of a supported MIME type.
""" """
datastring = datastring.strip() datastring = datastring.strip()
try:
is_xml = (datastring[0] == '<') is_xml = (datastring[0] == '<')
if not is_xml: if not is_xml:
return json.loads(datastring) return json.loads(datastring)
return self._from_xml(datastring) return self._from_xml(datastring)
except:
return None
def _from_xml(self, datastring): def _from_xml(self, datastring):
xmldata = self.metadata.get('application/xml', {}) xmldata = self.metadata.get('application/xml', {})

158
tools/setup_iptables.sh Executable file
View File

@@ -0,0 +1,158 @@
#!/usr/bin/env bash
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# NOTE(vish): This script sets up some reasonable defaults for iptables and
# creates nova-specific chains. If you use this script you should
# run nova-network and nova-compute with --use_nova_chains=True
# NOTE(vish): If you run nova-api on a different port, make sure to change
# the port here
API_PORT=${API_PORT:-"8773"}
if [ -n "$1" ]; then
CMD=$1
else
CMD="all"
fi
if [ -n "$2" ]; then
IP=$2
else
# NOTE(vish): This will just get the first ip in the list, so if you
# have more than one eth device set up, this will fail, and
# you should explicitly pass in the ip of the instance
IP=`ifconfig | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'`
fi
if [ -n "$3" ]; then
PRIVATE_RANGE=$3
else
PRIVATE_RANGE="10.0.0.0/12"
fi
if [ -n "$4" ]; then
# NOTE(vish): Management IP is the ip over which to allow ssh traffic. It
# will also allow traffic to nova-api
MGMT_IP=$4
else
MGMT_IP="$IP"
fi
if [ "$CMD" == "clear" ]; then
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
iptables -F
iptables -t nat -F
iptables -F nova_input
iptables -F nova_output
iptables -F nova_forward
iptables -t nat -F nova_input
iptables -t nat -F nova_output
iptables -t nat -F nova_forward
iptables -t nat -X
iptables -X
fi
if [ "$CMD" == "base" ] || [ "$CMD" == "all" ]; then
iptables -P INPUT DROP
iptables -A INPUT -m state --state INVALID -j DROP
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -m tcp -p tcp -d $MGMT_IP --dport 22 -j ACCEPT
iptables -A INPUT -m udp -p udp --dport 123 -j ACCEPT
iptables -N nova_input
iptables -A INPUT -j nova_input
iptables -A INPUT -p icmp -j ACCEPT
iptables -A INPUT -p tcp -j REJECT --reject-with tcp-reset
iptables -A INPUT -j REJECT --reject-with icmp-port-unreachable
iptables -P FORWARD DROP
iptables -A FORWARD -m state --state INVALID -j DROP
iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
iptables -N nova_forward
iptables -A FORWARD -j nova_forward
# NOTE(vish): DROP on output is too restrictive for now. We need to add
# in a bunch of more specific output rules to use it.
# iptables -P OUTPUT DROP
iptables -A OUTPUT -m state --state INVALID -j DROP
iptables -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -N nova_output
iptables -A OUTPUT -j nova_output
iptables -t nat -N nova_prerouting
iptables -t nat -A PREROUTING -j nova_prerouting
iptables -t nat -N nova_postrouting
iptables -t nat -A POSTROUTING -j nova_postrouting
iptables -t nat -N nova_output
iptables -t nat -A OUTPUT -j nova_output
fi
if [ "$CMD" == "ganglia" ] || [ "$CMD" == "all" ]; then
iptables -A nova_input -m tcp -p tcp -d $IP --dport 8649 -j ACCEPT
iptables -A nova_input -m udp -p udp -d $IP --dport 8649 -j ACCEPT
fi
if [ "$CMD" == "web" ] || [ "$CMD" == "all" ]; then
# NOTE(vish): This opens up ports for web access, allowing web-based
# dashboards to work.
iptables -A nova_input -m tcp -p tcp -d $IP --dport 80 -j ACCEPT
iptables -A nova_input -m tcp -p tcp -d $IP --dport 443 -j ACCEPT
fi
if [ "$CMD" == "objectstore" ] || [ "$CMD" == "all" ]; then
iptables -A nova_input -m tcp -p tcp -d $IP --dport 3333 -j ACCEPT
fi
if [ "$CMD" == "api" ] || [ "$CMD" == "all" ]; then
iptables -A nova_input -m tcp -p tcp -d $IP --dport $API_PORT -j ACCEPT
if [ "$IP" != "$MGMT_IP" ]; then
iptables -A nova_input -m tcp -p tcp -d $MGMT_IP --dport $API_PORT -j ACCEPT
fi
fi
if [ "$CMD" == "redis" ] || [ "$CMD" == "all" ]; then
iptables -A nova_input -m tcp -p tcp -d $IP --dport 6379 -j ACCEPT
fi
if [ "$CMD" == "mysql" ] || [ "$CMD" == "all" ]; then
iptables -A nova_input -m tcp -p tcp -d $IP --dport 3306 -j ACCEPT
fi
if [ "$CMD" == "rabbitmq" ] || [ "$CMD" == "all" ]; then
iptables -A nova_input -m tcp -p tcp -d $IP --dport 4369 -j ACCEPT
iptables -A nova_input -m tcp -p tcp -d $IP --dport 5672 -j ACCEPT
iptables -A nova_input -m tcp -p tcp -d $IP --dport 53284 -j ACCEPT
fi
if [ "$CMD" == "dnsmasq" ] || [ "$CMD" == "all" ]; then
# NOTE(vish): this could theoretically be setup per network
# for each host, but it seems like overkill
iptables -A nova_input -m tcp -p tcp -s $PRIVATE_RANGE --dport 53 -j ACCEPT
iptables -A nova_input -m udp -p udp -s $PRIVATE_RANGE --dport 53 -j ACCEPT
iptables -A nova_input -m udp -p udp --dport 67 -j ACCEPT
fi
if [ "$CMD" == "ldap" ] || [ "$CMD" == "all" ]; then
iptables -A nova_input -m tcp -p tcp -d $IP --dport 389 -j ACCEPT
fi