docstring cleanup, direct api, part of compute

This commit is contained in:
termie
2011-04-11 11:34:19 -05:00
parent 8ae129ace4
commit 6ac2b25d77
4 changed files with 165 additions and 77 deletions

View File

@@ -15,5 +15,3 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""No-op __init__ for directory full of api goodies."""

View File

@@ -44,14 +44,33 @@ from nova import utils
from nova import wsgi
# Global storage for registering modules.
ROUTES = {}
def register_service(path, handle):
"""Register a service handle at a given path.
Services registered in this way will be made available to any instances of
nova.api.direct.Router.
:param path: `routes` path, can be a basic string like "/path"
:param handle: an object whose methods will be made available via the api
"""
ROUTES[path] = handle
class Router(wsgi.Router):
"""A simple WSGI router configured via `register_service`.
This is a quick way to attach multiple services to a given endpoint.
It will automatically load the routes registered in the `ROUTES` global.
TODO(termie): provide a paste-deploy version of this.
"""
def __init__(self, mapper=None):
if mapper is None:
mapper = routes.Mapper()
@@ -66,6 +85,24 @@ class Router(wsgi.Router):
class DelegatedAuthMiddleware(wsgi.Middleware):
"""A simple and naive authentication middleware.
Designed mostly to provide basic support for alternative authentication
schemes, this middleware only desires the identity of the user and will
generate the appropriate nova.context.RequestContext for the rest of the
application. This allows any middleware above it in the stack to
authenticate however it would like while only needing to conform to a
minimal interface.
Expects two headers to determine identity:
- X-OpenStack-User
- X-OpenStack-Project
This middleware is tied to identity management and will need to be kept
in sync with any changes to the way identity is dealt with internally.
"""
def process_request(self, request):
os_user = request.headers['X-OpenStack-User']
os_project = request.headers['X-OpenStack-Project']
@@ -74,6 +111,20 @@ class DelegatedAuthMiddleware(wsgi.Middleware):
class JsonParamsMiddleware(wsgi.Middleware):
"""Middleware to allow method arguments to be passed as serialized JSON.
Accepting arguments as JSON is useful for accepting data that may be more
complex than simple primitives.
In this case we accept it as urlencoded data under the key 'json' as in
json=<urlencoded_json> but this could be extended to accept raw JSON
in the POST body.
Filters out the parameters `self`, `context` and anything beginning with
an underscore.
"""
def process_request(self, request):
if 'json' not in request.params:
return
@@ -92,6 +143,13 @@ class JsonParamsMiddleware(wsgi.Middleware):
class PostParamsMiddleware(wsgi.Middleware):
"""Middleware to allow method arguments to be passed as POST parameters.
Filters out the parameters `self`, `context` and anything beginning with
an underscore.
"""
def process_request(self, request):
params_parsed = request.params
params = {}
@@ -106,12 +164,21 @@ class PostParamsMiddleware(wsgi.Middleware):
class Reflection(object):
"""Reflection methods to list available methods."""
"""Reflection methods to list available methods.
This is an object that expects to be registered via register_service.
These methods allow the endpoint to be self-describing. They introspect
the exposed methods and provide call signatures and documentation for
them allowing quick experimentation.
"""
def __init__(self):
self._methods = {}
self._controllers = {}
def _gather_methods(self):
"""Introspect available methods and generate documentation for them."""
methods = {}
controllers = {}
for route, handler in ROUTES.iteritems():
@@ -185,6 +252,16 @@ class Reflection(object):
class ServiceWrapper(wsgi.Controller):
"""Wrapper to dynamically povide a WSGI controller for arbitrary objects.
With lightweight introspection allows public methods on the object to
be accesed via simple WSGI routing and parameters and serializes the
return values.
Automatically used be nova.api.direct.Router to wrap registered instances.
"""
def __init__(self, service_handle):
self.service_handle = service_handle
@@ -260,7 +337,16 @@ class Limited(object):
class Proxy(object):
"""Pretend a Direct API endpoint is an object."""
"""Pretend a Direct API endpoint is an object.
This is mostly useful in testing at the moment though it should be easily
extendable to provide a basic API library functionality.
In testing we use this to stub out internal objects to verify that results
from the API are serializable.
"""
def __init__(self, app, prefix=None):
self.app = app
self.prefix = prefix

View File

@@ -16,9 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
Handles all requests relating to instances (guest vms).
"""
"""Handles all requests relating to instances (guest vms)."""
import datetime
import re
@@ -86,10 +84,10 @@ class API(base.Base):
{"method": "get_network_topic", "args": {'fake': 1}})
def _check_injected_file_quota(self, context, injected_files):
"""
Enforce quota limits on injected files
"""Enforce quota limits on injected files.
Raises a QuotaError if any limit is exceeded.
Raises a QuotaError if any limit is exceeded
"""
if injected_files is None:
return
@@ -111,8 +109,11 @@ class API(base.Base):
key_name=None, key_data=None, security_group='default',
availability_zone=None, user_data=None, metadata=[],
injected_files=None):
"""Create the number of instances requested if quota and
other arguments check out ok."""
"""Create the number and type of instances requested.
Verifies that quota and other arguments are valid.
"""
if not instance_type:
instance_type = instance_types.get_default_instance_type()
@@ -262,8 +263,7 @@ class API(base.Base):
return [dict(x.iteritems()) for x in instances]
def has_finished_migration(self, context, instance_id):
"""Retrieves whether or not a finished migration exists for
an instance"""
"""Returns true if an instance has a finished migration."""
try:
db.migration_get_by_instance_and_status(context, instance_id,
'finished')
@@ -272,8 +272,10 @@ class API(base.Base):
return False
def ensure_default_security_group(self, context):
""" Create security group for the security context if it
does not already exist
"""Ensure that a context has a security group.
Creates a security group for the security context if it does not
already exist.
:param context: the security context
@@ -289,7 +291,7 @@ class API(base.Base):
db.security_group_create(context, values)
def trigger_security_group_rules_refresh(self, context, security_group_id):
"""Called when a rule is added to or removed from a security_group"""
"""Called when a rule is added to or removed from a security_group."""
security_group = self.db.security_group_get(context, security_group_id)
@@ -305,11 +307,12 @@ class API(base.Base):
"args": {"security_group_id": security_group.id}})
def trigger_security_group_members_refresh(self, context, group_id):
"""Called when a security group gains a new or loses a member
"""Called when a security group gains a new or loses a member.
Sends an update request to each compute node for whom this is
relevant."""
relevant.
"""
# First, we get the security group rules that reference this group as
# the grantee..
security_group_rules = \
@@ -354,7 +357,7 @@ class API(base.Base):
as data fields of the instance to be
updated
:retval None
:returns: None
"""
rv = self.db.instance_update(context, instance_id, kwargs)
@@ -362,6 +365,7 @@ class API(base.Base):
@scheduler_api.reroute_compute("delete")
def delete(self, context, instance_id):
"""Terminate an instance."""
LOG.debug(_("Going to try to terminate %s"), instance_id)
try:
instance = self.get(context, instance_id)
@@ -393,22 +397,28 @@ class API(base.Base):
self.db.instance_destroy(context, instance_id)
def get(self, context, instance_id):
"""Get a single instance with the given ID."""
"""Get a single instance with the given instance_id."""
rv = self.db.instance_get(context, instance_id)
return dict(rv.iteritems())
@scheduler_api.reroute_compute("get")
def routing_get(self, context, instance_id):
"""Use this method instead of get() if this is the only
operation you intend to to. It will route to novaclient.get
if the instance is not found."""
"""A version of get with special routing characteristics.
Use this method instead of get() if this is the only operation you
intend to to. It will route to novaclient.get if the instance is not
found.
"""
return self.get(context, instance_id)
def get_all(self, context, project_id=None, reservation_id=None,
fixed_ip=None):
"""Get all instances, possibly filtered by one of the
given parameters. If there is no filter and the context is
an admin, it will retreive all instances in the system.
"""Get all instances filtered by one of the given parameters.
If there is no filter and the context is an admin, it will retreive
all instances in the system.
"""
if reservation_id is not None:
return self.db.instance_get_all_by_reservation(
@@ -437,7 +447,8 @@ class API(base.Base):
:param params: Optional dictionary of arguments to be passed to the
compute worker
:retval None
:returns: None
"""
if not params:
params = {}
@@ -456,7 +467,7 @@ class API(base.Base):
:param params: Optional dictionary of arguments to be passed to the
compute worker
:retval: Result returned by compute worker
:returns: Result returned by compute worker
"""
if not params:
params = {}
@@ -469,13 +480,14 @@ class API(base.Base):
return rpc.call(context, queue, kwargs)
def _cast_scheduler_message(self, context, args):
"""Generic handler for RPC calls to the scheduler"""
"""Generic handler for RPC calls to the scheduler."""
rpc.cast(context, FLAGS.scheduler_topic, args)
def snapshot(self, context, instance_id, name):
"""Snapshot the given instance.
:retval: A dict containing image metadata
:returns: A dict containing image metadata
"""
properties = {'instance_id': str(instance_id),
'user_id': str(context.user_id)}
@@ -492,7 +504,7 @@ class API(base.Base):
self._cast_compute_message('reboot_instance', context, instance_id)
def revert_resize(self, context, instance_id):
"""Reverts a resize, deleting the 'new' instance in the process"""
"""Reverts a resize, deleting the 'new' instance in the process."""
context = context.elevated()
migration_ref = self.db.migration_get_by_instance_and_status(context,
instance_id, 'finished')
@@ -507,8 +519,7 @@ class API(base.Base):
{'status': 'reverted'})
def confirm_resize(self, context, instance_id):
"""Confirms a migration/resize, deleting the 'old' instance in the
process."""
"""Confirms a migration/resize and deletes the 'old' instance."""
context = context.elevated()
migration_ref = self.db.migration_get_by_instance_and_status(context,
instance_id, 'finished')
@@ -568,10 +579,9 @@ class API(base.Base):
@scheduler_api.reroute_compute("diagnostics")
def get_diagnostics(self, context, instance_id):
"""Retrieve diagnostics for the given instance."""
return self._call_compute_message(
"get_diagnostics",
context,
instance_id)
return self._call_compute_message("get_diagnostics",
context,
instance_id)
def get_actions(self, context, instance_id):
"""Retrieve actions for the given instance."""
@@ -579,12 +589,12 @@ class API(base.Base):
@scheduler_api.reroute_compute("suspend")
def suspend(self, context, instance_id):
"""suspend the instance with instance_id"""
"""Suspend the given instance."""
self._cast_compute_message('suspend_instance', context, instance_id)
@scheduler_api.reroute_compute("resume")
def resume(self, context, instance_id):
"""resume the instance with instance_id"""
"""Resume the given instance."""
self._cast_compute_message('resume_instance', context, instance_id)
@scheduler_api.reroute_compute("rescue")
@@ -599,15 +609,15 @@ class API(base.Base):
def set_admin_password(self, context, instance_id, password=None):
"""Set the root/admin password for the given instance."""
self._cast_compute_message('set_admin_password', context, instance_id,
password)
self._cast_compute_message(
'set_admin_password', context, instance_id, password)
def inject_file(self, context, instance_id):
"""Write a file to the given instance."""
self._cast_compute_message('inject_file', context, instance_id)
def get_ajax_console(self, context, instance_id):
"""Get a url to an AJAX Console"""
"""Get a url to an AJAX Console."""
output = self._call_compute_message('get_ajax_console',
context,
instance_id)
@@ -616,7 +626,7 @@ class API(base.Base):
'args': {'token': output['token'], 'host': output['host'],
'port': output['port']}})
return {'url': '%s/?token=%s' % (FLAGS.ajax_console_proxy_url,
output['token'])}
output['token'])}
def get_vnc_console(self, context, instance_id):
"""Get a url to a VNC Console."""
@@ -638,39 +648,34 @@ class API(base.Base):
'portignore')}
def get_console_output(self, context, instance_id):
"""Get console output for an an instance"""
"""Get console output for an an instance."""
return self._call_compute_message('get_console_output',
context,
instance_id)
def lock(self, context, instance_id):
"""lock the instance with instance_id"""
"""Lock the given instance."""
self._cast_compute_message('lock_instance', context, instance_id)
def unlock(self, context, instance_id):
"""unlock the instance with instance_id"""
"""Unlock the given instance."""
self._cast_compute_message('unlock_instance', context, instance_id)
def get_lock(self, context, instance_id):
"""return the boolean state of (instance with instance_id)'s lock"""
"""Return the boolean state of given instance's lock."""
instance = self.get(context, instance_id)
return instance['locked']
def reset_network(self, context, instance_id):
"""
Reset networking on the instance.
"""
"""Reset networking on the instance."""
self._cast_compute_message('reset_network', context, instance_id)
def inject_network_info(self, context, instance_id):
"""
Inject network info for the instance.
"""
"""Inject network info for the instance."""
self._cast_compute_message('inject_network_info', context, instance_id)
def attach_volume(self, context, instance_id, volume_id, device):
"""Attach an existing volume to an existing instance."""
if not re.match("^/dev/[a-z]d[a-z]+$", device):
raise exception.ApiError(_("Invalid device specified: %s. "
"Example device: /dev/vdb") % device)
@@ -685,6 +690,7 @@ class API(base.Base):
"mountpoint": device}})
def detach_volume(self, context, volume_id):
"""Detach a volume from an instance."""
instance = self.db.volume_get_instance(context.elevated(), volume_id)
if not instance:
raise exception.ApiError(_("Volume isn't attached to anything!"))
@@ -698,6 +704,7 @@ class API(base.Base):
return instance
def associate_floating_ip(self, context, instance_id, address):
"""Associate a floating ip with an instance."""
instance = self.get(context, instance_id)
self.network_api.associate_floating_ip(context,
floating_ip=address,
@@ -709,11 +716,11 @@ class API(base.Base):
return dict(rv.iteritems())
def delete_instance_metadata(self, context, instance_id, key):
"""Delete the given metadata item"""
"""Delete the given metadata item from an instance."""
self.db.instance_metadata_delete(context, instance_id, key)
def update_or_create_instance_metadata(self, context, instance_id,
metadata):
"""Updates or creates instance metadata"""
"""Updates or creates instance metadata."""
self.db.instance_metadata_update_or_create(context, instance_id,
metadata)

View File

@@ -18,9 +18,7 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
The built-in instance properties.
"""
"""Built-in instance properties."""
from nova import context
from nova import db
@@ -34,9 +32,7 @@ LOG = logging.getLogger('nova.instance_types')
def create(name, memory, vcpus, local_gb, flavorid, swap=0,
rxtx_quota=0, rxtx_cap=0):
"""Creates instance types / flavors
arguments: name memory vcpus local_gb flavorid swap rxtx_quota rxtx_cap
"""
"""Creates instance types."""
for option in [memory, vcpus, local_gb, flavorid]:
try:
int(option)
@@ -64,8 +60,7 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0,
def destroy(name):
"""Marks instance types / flavors as deleted
arguments: name"""
"""Marks instance types as deleted."""
if name == None:
raise exception.InvalidInputException(_("No instance type specified"))
else:
@@ -77,8 +72,7 @@ def destroy(name):
def purge(name):
"""Removes instance types / flavors from database
arguments: name"""
"""Removes instance types from database."""
if name == None:
raise exception.InvalidInputException(_("No instance type specified"))
else:
@@ -90,18 +84,19 @@ def purge(name):
def get_all_types(inactive=0):
"""Retrieves non-deleted instance_types.
Pass true as argument if you want deleted instance types returned also."""
"""Get all non-deleted instance_types.
Pass true as argument if you want deleted instance types returned also.
"""
return db.instance_type_get_all(context.get_admin_context(), inactive)
def get_all_flavors():
"""retrieves non-deleted flavors. alias for instance_types.get_all_types().
Pass true as argument if you want deleted instance types returned also."""
return get_all_types(context.get_admin_context())
get_all_flavors = get_all_types
def get_default_instance_type():
"""Get the default instance type."""
name = FLAGS.default_instance_type
try:
return get_instance_type_by_name(name)
@@ -110,7 +105,7 @@ def get_default_instance_type():
def get_instance_type(id):
"""Retrieves single instance type by id"""
"""Retrieves single instance type by id."""
if id is None:
return get_default_instance_type()
try:
@@ -121,7 +116,7 @@ def get_instance_type(id):
def get_instance_type_by_name(name):
"""Retrieves single instance type by name"""
"""Retrieves single instance type by name."""
if name is None:
return get_default_instance_type()
try:
@@ -131,8 +126,10 @@ def get_instance_type_by_name(name):
raise exception.ApiError(_("Unknown instance type: %s") % name)
# TODO(termie): flavor-specific code should probably be in the API that uses
# flavors.
def get_instance_type_by_flavor_id(flavor_id):
"""retrieve instance type by flavor_id"""
"""Retrieve instance type by flavor_id."""
if flavor_id is None:
return get_default_instance_type()
try: