summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortermie <github@anarkystic.com>2011-04-11 11:34:19 -0500
committertermie <github@anarkystic.com>2011-04-11 11:34:19 -0500
commit6ac2b25d77ea71be0f9232b5502a75f255a6b2ec (patch)
treef70c78c4543ed60095227fac9b10096f8a30146c
parent8ae129ace401a1493cd953d3f4b9ecc22d74a70f (diff)
downloadnova-6ac2b25d77ea71be0f9232b5502a75f255a6b2ec.tar.gz
nova-6ac2b25d77ea71be0f9232b5502a75f255a6b2ec.tar.xz
nova-6ac2b25d77ea71be0f9232b5502a75f255a6b2ec.zip
docstring cleanup, direct api, part of compute
-rw-r--r--nova/api/__init__.py2
-rw-r--r--nova/api/direct.py90
-rw-r--r--nova/compute/api.py115
-rw-r--r--nova/compute/instance_types.py35
4 files changed, 165 insertions, 77 deletions
diff --git a/nova/api/__init__.py b/nova/api/__init__.py
index 0fedbbfad..747015af5 100644
--- a/nova/api/__init__.py
+++ b/nova/api/__init__.py
@@ -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."""
diff --git a/nova/api/direct.py b/nova/api/direct.py
index f487df7c7..8ceae299c 100644
--- a/nova/api/direct.py
+++ b/nova/api/direct.py
@@ -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
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 041e0e74a..e6146231c 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -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)
diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py
index b3a5ab0ac..f893f8478 100644
--- a/nova/compute/instance_types.py
+++ b/nova/compute/instance_types.py
@@ -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: