summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2013-01-15 21:39:16 +0000
committerGerrit Code Review <review@openstack.org>2013-01-15 21:39:16 +0000
commita88152a476de4e5fc04d5b4e3b098527fdfb5a8f (patch)
tree980db596c0bef2d37765c58159a21ee0cab0310b /nova/api
parentf578fc4e220f4d57aebd6289bbf8b0181c26a066 (diff)
parent6b4ad2d7d2f431c153e9e2a655271932b8b378f0 (diff)
downloadnova-a88152a476de4e5fc04d5b4e3b098527fdfb5a8f.tar.gz
nova-a88152a476de4e5fc04d5b4e3b098527fdfb5a8f.tar.xz
nova-a88152a476de4e5fc04d5b4e3b098527fdfb5a8f.zip
Merge "Cells: Add cells API extension"
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/openstack/compute/contrib/cells.py303
1 files changed, 303 insertions, 0 deletions
diff --git a/nova/api/openstack/compute/contrib/cells.py b/nova/api/openstack/compute/contrib/cells.py
new file mode 100644
index 000000000..03e2e4ca2
--- /dev/null
+++ b/nova/api/openstack/compute/contrib/cells.py
@@ -0,0 +1,303 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011-2012 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.
+
+"""The cells extension."""
+from xml.dom import minidom
+from xml.parsers import expat
+
+from webob import exc
+
+from nova.api.openstack import common
+from nova.api.openstack import extensions
+from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
+from nova.cells import rpcapi as cells_rpcapi
+from nova.compute import api as compute
+from nova import db
+from nova import exception
+from nova.openstack.common import cfg
+from nova.openstack.common import log as logging
+from nova.openstack.common import timeutils
+
+
+LOG = logging.getLogger(__name__)
+CONF = cfg.CONF
+CONF.import_opt('name', 'nova.cells.opts', group='cells')
+CONF.import_opt('capabilities', 'nova.cells.opts', group='cells')
+
+authorize = extensions.extension_authorizer('compute', 'cells')
+
+
+def make_cell(elem):
+ elem.set('name')
+ elem.set('username')
+ elem.set('type')
+ elem.set('rpc_host')
+ elem.set('rpc_port')
+
+ caps = xmlutil.SubTemplateElement(elem, 'capabilities',
+ selector='capabilities')
+ cap = xmlutil.SubTemplateElement(caps, xmlutil.Selector(0),
+ selector=xmlutil.get_items)
+ cap.text = 1
+
+
+cell_nsmap = {None: wsgi.XMLNS_V10}
+
+
+class CellTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('cell', selector='cell')
+ make_cell(root)
+ return xmlutil.MasterTemplate(root, 1, nsmap=cell_nsmap)
+
+
+class CellsTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('cells')
+ elem = xmlutil.SubTemplateElement(root, 'cell', selector='cells')
+ make_cell(elem)
+ return xmlutil.MasterTemplate(root, 1, nsmap=cell_nsmap)
+
+
+class CellDeserializer(wsgi.XMLDeserializer):
+ """Deserializer to handle xml-formatted cell create requests."""
+
+ def _extract_capabilities(self, cap_node):
+ caps = {}
+ for cap in cap_node.childNodes:
+ cap_name = cap.tagName
+ caps[cap_name] = self.extract_text(cap)
+ return caps
+
+ def _extract_cell(self, node):
+ cell = {}
+ cell_node = self.find_first_child_named(node, 'cell')
+
+ extract_fns = {'capabilities': self._extract_capabilities}
+
+ for child in cell_node.childNodes:
+ name = child.tagName
+ extract_fn = extract_fns.get(name, self.extract_text)
+ cell[name] = extract_fn(child)
+ return cell
+
+ def default(self, string):
+ """Deserialize an xml-formatted cell create request."""
+ try:
+ node = minidom.parseString(string)
+ except expat.ExpatError:
+ msg = _("cannot understand XML")
+ raise exception.MalformedRequestBody(reason=msg)
+
+ return {'body': {'cell': self._extract_cell(node)}}
+
+
+def _filter_keys(item, keys):
+ """
+ Filters all model attributes except for keys
+ item is a dict
+
+ """
+ return dict((k, v) for k, v in item.iteritems() if k in keys)
+
+
+def _scrub_cell(cell, detail=False):
+ keys = ['name', 'username', 'rpc_host', 'rpc_port']
+ if detail:
+ keys.append('capabilities')
+
+ cell_info = _filter_keys(cell, keys)
+ cell_info['type'] = 'parent' if cell['is_parent'] else 'child'
+ return cell_info
+
+
+class Controller(object):
+ """Controller for Cell resources."""
+
+ def __init__(self):
+ self.compute_api = compute.API()
+ self.cells_rpcapi = cells_rpcapi.CellsAPI()
+
+ def _get_cells(self, ctxt, req, detail=False):
+ """Return all cells."""
+ # Ask the CellsManager for the most recent data
+ items = self.cells_rpcapi.get_cell_info_for_neighbors(ctxt)
+ items = common.limited(items, req)
+ items = [_scrub_cell(item, detail=detail) for item in items]
+ return dict(cells=items)
+
+ @wsgi.serializers(xml=CellsTemplate)
+ def index(self, req):
+ """Return all cells in brief."""
+ ctxt = req.environ['nova.context']
+ authorize(ctxt)
+ return self._get_cells(ctxt, req)
+
+ @wsgi.serializers(xml=CellsTemplate)
+ def detail(self, req):
+ """Return all cells in detail."""
+ ctxt = req.environ['nova.context']
+ authorize(ctxt)
+ return self._get_cells(ctxt, req, detail=True)
+
+ @wsgi.serializers(xml=CellTemplate)
+ def info(self, req):
+ """Return name and capabilities for this cell."""
+ context = req.environ['nova.context']
+ authorize(context)
+ cell_capabs = {}
+ my_caps = CONF.cells.capabilities
+ for cap in my_caps:
+ key, value = cap.split('=')
+ cell_capabs[key] = value
+ cell = {'name': CONF.cells.name,
+ 'type': 'self',
+ 'rpc_host': None,
+ 'rpc_port': 0,
+ 'username': None,
+ 'capabilities': cell_capabs}
+ return dict(cell=cell)
+
+ @wsgi.serializers(xml=CellTemplate)
+ def show(self, req, id):
+ """Return data about the given cell name. 'id' is a cell name."""
+ context = req.environ['nova.context']
+ authorize(context)
+ try:
+ cell = db.cell_get(context, id)
+ except exception.CellNotFound:
+ raise exc.HTTPNotFound()
+ return dict(cell=_scrub_cell(cell))
+
+ def delete(self, req, id):
+ """Delete a child or parent cell entry. 'id' is a cell name."""
+ context = req.environ['nova.context']
+ authorize(context)
+ num_deleted = db.cell_delete(context, id)
+ if num_deleted == 0:
+ raise exc.HTTPNotFound()
+ return {}
+
+ def _validate_cell_name(self, cell_name):
+ """Validate cell name is not empty and doesn't contain '!' or '.'."""
+ if not cell_name:
+ msg = _("Cell name cannot be empty")
+ LOG.error(msg)
+ raise exc.HTTPBadRequest(explanation=msg)
+ if '!' in cell_name or '.' in cell_name:
+ msg = _("Cell name cannot contain '!' or '.'")
+ LOG.error(msg)
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ def _validate_cell_type(self, cell_type):
+ """Validate cell_type is 'parent' or 'child'."""
+ if cell_type not in ['parent', 'child']:
+ msg = _("Cell type must be 'parent' or 'child'")
+ LOG.error(msg)
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ def _convert_cell_type(self, cell):
+ """Convert cell['type'] to is_parent boolean."""
+ if 'type' in cell:
+ self._validate_cell_type(cell['type'])
+ cell['is_parent'] = cell['type'] == 'parent'
+ del cell['type']
+ else:
+ cell['is_parent'] = False
+
+ @wsgi.serializers(xml=CellTemplate)
+ @wsgi.deserializers(xml=CellDeserializer)
+ def create(self, req, body):
+ """Create a child cell entry."""
+ context = req.environ['nova.context']
+ authorize(context)
+ if 'cell' not in body:
+ msg = _("No cell information in request")
+ LOG.error(msg)
+ raise exc.HTTPBadRequest(explanation=msg)
+ cell = body['cell']
+ if 'name' not in cell:
+ msg = _("No cell name in request")
+ LOG.error(msg)
+ raise exc.HTTPBadRequest(explanation=msg)
+ self._validate_cell_name(cell['name'])
+ self._convert_cell_type(cell)
+ cell = db.cell_create(context, cell)
+ return dict(cell=_scrub_cell(cell))
+
+ @wsgi.serializers(xml=CellTemplate)
+ @wsgi.deserializers(xml=CellDeserializer)
+ def update(self, req, id, body):
+ """Update a child cell entry. 'id' is the cell name to update."""
+ context = req.environ['nova.context']
+ authorize(context)
+ if 'cell' not in body:
+ msg = _("No cell information in request")
+ LOG.error(msg)
+ raise exc.HTTPBadRequest(explanation=msg)
+ cell = body['cell']
+ cell.pop('id', None)
+ if 'name' in cell:
+ self._validate_cell_name(cell['name'])
+ self._convert_cell_type(cell)
+ try:
+ cell = db.cell_update(context, id, cell)
+ except exception.CellNotFound:
+ raise exc.HTTPNotFound()
+ return dict(cell=_scrub_cell(cell))
+
+ def sync_instances(self, req, body):
+ """Tell all cells to sync instance info."""
+ context = req.environ['nova.context']
+ authorize(context)
+ project_id = body.pop('project_id', None)
+ deleted = body.pop('deleted', False)
+ updated_since = body.pop('updated_since', None)
+ if body:
+ msg = _("Only 'updated_since' and 'project_id' are understood.")
+ raise exc.HTTPBadRequest(explanation=msg)
+ if updated_since:
+ try:
+ timeutils.parse_isotime(updated_since)
+ except ValueError:
+ msg = _('Invalid changes-since value')
+ raise exc.HTTPBadRequest(explanation=msg)
+ self.cells_rpcapi.sync_instances(context, project_id=project_id,
+ updated_since=updated_since, deleted=deleted)
+
+
+class Cells(extensions.ExtensionDescriptor):
+ """Enables cells-related functionality such as adding neighbor cells,
+ listing neighbor cells, and getting the capabilities of the local cell.
+ """
+
+ name = "Cells"
+ alias = "os-cells"
+ namespace = "http://docs.openstack.org/compute/ext/cells/api/v1.1"
+ updated = "2011-09-21T00:00:00+00:00"
+
+ def get_resources(self):
+ coll_actions = {
+ 'detail': 'GET',
+ 'info': 'GET',
+ 'sync_instances': 'POST',
+ }
+
+ res = extensions.ResourceExtension('os-cells',
+ Controller(), collection_actions=coll_actions)
+ return [res]