From 1b372a7bddb3095b0672e2de750c43922783b440 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 26 Dec 2011 11:14:47 -0500 Subject: Converting zones into true extension Related to blueprint separate-nova-adminapi Change-Id: I733b05e7de0036f3dce31c95efef150186e302ee --- nova/api/openstack/v2/__init__.py | 10 -- nova/api/openstack/v2/contrib/zones.py | 207 ++++++++++++++++++++++++++++++- nova/api/openstack/v2/zones.py | 215 --------------------------------- 3 files changed, 204 insertions(+), 228 deletions(-) delete mode 100644 nova/api/openstack/v2/zones.py (limited to 'nova/api') diff --git a/nova/api/openstack/v2/__init__.py b/nova/api/openstack/v2/__init__.py index 1dadd7bef..9504e4f7e 100644 --- a/nova/api/openstack/v2/__init__.py +++ b/nova/api/openstack/v2/__init__.py @@ -34,7 +34,6 @@ from nova.api.openstack.v2 import limits from nova.api.openstack.v2 import servers from nova.api.openstack.v2 import server_metadata from nova.api.openstack.v2 import versions -from nova.api.openstack.v2 import zones from nova.api.openstack import wsgi from nova import flags from nova import log as logging @@ -130,15 +129,6 @@ class APIRouter(base_wsgi.Router): mapper.resource(resource.collection, resource.collection, **kargs) def _setup_routes(self, mapper): - if FLAGS.allow_admin_api: - LOG.debug(_("Including admin operations in API.")) - - mapper.resource("zone", "zones", - controller=zones.create_resource(), - collection={'detail': 'GET', - 'info': 'GET', - 'select': 'POST'}) - mapper.connect("versions", "/", controller=versions.create_resource(), action='show') diff --git a/nova/api/openstack/v2/contrib/zones.py b/nova/api/openstack/v2/contrib/zones.py index 89c3ef517..62ce423a7 100644 --- a/nova/api/openstack/v2/contrib/zones.py +++ b/nova/api/openstack/v2/contrib/zones.py @@ -17,16 +17,195 @@ """The zones extension.""" +import json +from nova.api.openstack import common +from nova.api.openstack.v2 import servers +from nova.api.openstack.v2 import extensions +from nova.api.openstack import xmlutil +from nova.api.openstack import wsgi +from nova.compute import api as compute +from nova import crypto +from nova import exception from nova import flags from nova import log as logging -from nova.api.openstack.v2 import extensions +import nova.scheduler.api LOG = logging.getLogger("nova.api.openstack.v2.contrib.zones") FLAGS = flags.FLAGS +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 _exclude_keys(item, keys): + return dict((k, v) for k, v in item.iteritems() if k and (k not in keys)) + + +def _scrub_zone(zone): + return _exclude_keys(zone, ('username', 'password', 'created_at', + 'deleted', 'deleted_at', 'updated_at')) + + +def check_encryption_key(func): + def wrapped(*args, **kwargs): + if not FLAGS.build_plan_encryption_key: + raise exception.Error(_("--build_plan_encryption_key not set")) + return func(*args, **kwargs) + return wrapped + + +class Controller(object): + """Controller for Zone resources.""" + + def __init__(self): + self.compute_api = compute.API() + + def index(self, req): + """Return all zones in brief""" + # Ask the ZoneManager in the Scheduler for most recent data, + # or fall-back to the database ... + items = nova.scheduler.api.get_zone_list(req.environ['nova.context']) + items = common.limited(items, req) + items = [_scrub_zone(item) for item in items] + return dict(zones=items) + + def detail(self, req): + """Return all zones in detail""" + return self.index(req) + + def info(self, req): + """Return name and capabilities for this zone.""" + context = req.environ['nova.context'] + items = nova.scheduler.api.get_zone_capabilities(context) + + zone = dict(name=FLAGS.zone_name) + caps = FLAGS.zone_capabilities + for cap in caps: + key, value = cap.split('=') + zone[key] = value + for item, (min_value, max_value) in items.iteritems(): + zone[item] = "%s,%s" % (min_value, max_value) + return dict(zone=zone) + + def show(self, req, id): + """Return data about the given zone id""" + zone_id = int(id) + context = req.environ['nova.context'] + zone = nova.scheduler.api.zone_get(context, zone_id) + return dict(zone=_scrub_zone(zone)) + + def delete(self, req, id): + """Delete a child zone entry.""" + zone_id = int(id) + nova.scheduler.api.zone_delete(req.environ['nova.context'], zone_id) + return {} + + def create(self, req, body): + """Create a child zone entry.""" + context = req.environ['nova.context'] + zone = nova.scheduler.api.zone_create(context, body["zone"]) + return dict(zone=_scrub_zone(zone)) + + def update(self, req, id, body): + """Update a child zone entry.""" + context = req.environ['nova.context'] + zone_id = int(id) + zone = nova.scheduler.api.zone_update(context, zone_id, body["zone"]) + return dict(zone=_scrub_zone(zone)) + + @check_encryption_key + def select(self, req, body): + """Returns a weighted list of costs to create instances + of desired capabilities.""" + ctx = req.environ['nova.context'] + specs = json.loads(body) + build_plan = nova.scheduler.api.select(ctx, specs=specs) + cooked = self._scrub_build_plan(build_plan) + return {"weights": cooked} + + def _scrub_build_plan(self, build_plan): + """Remove all the confidential data and return a sanitized + version of the build plan. Include an encrypted full version + of the weighting entry so we can get back to it later.""" + encryptor = crypto.encryptor(FLAGS.build_plan_encryption_key) + cooked = [] + for entry in build_plan: + json_entry = json.dumps(entry) + cipher_text = encryptor(json_entry) + cooked.append(dict(weight=entry['weight'], + blob=cipher_text)) + return cooked + + +class CapabilitySelector(object): + def __call__(self, obj, do_raise=False): + return [(k, v) for k, v in obj.items() + if k not in ('id', 'api_url', 'name', 'capabilities')] + + +def make_zone(elem): + #elem = xmlutil.SubTemplateElement(parent, 'zone', selector=selector) + elem.set('id') + elem.set('api_url') + elem.set('name') + elem.set('capabilities') + + cap = xmlutil.SubTemplateElement(elem, xmlutil.Selector(0), + selector=CapabilitySelector()) + cap.text = 1 + + +zone_nsmap = {None: wsgi.XMLNS_V10} + + +class ZoneTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('zone', selector='zone') + make_zone(root) + return xmlutil.MasterTemplate(root, 1, nsmap=zone_nsmap) + + +class ZonesTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('zones') + elem = xmlutil.SubTemplateElement(root, 'zone', selector='zones') + make_zone(elem) + return xmlutil.MasterTemplate(root, 1, nsmap=zone_nsmap) + + +class WeightsTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('weights') + weight = xmlutil.SubTemplateElement(root, 'weight', selector='weights') + blob = xmlutil.SubTemplateElement(weight, 'blob') + blob.text = 'blob' + inner_weight = xmlutil.SubTemplateElement(weight, 'weight') + inner_weight.text = 'weight' + return xmlutil.MasterTemplate(root, 1, nsmap=zone_nsmap) + + +class ZonesXMLSerializer(xmlutil.XMLTemplateSerializer): + def index(self): + return ZonesTemplate() + + def detail(self): + return ZonesTemplate() + + def select(self): + return WeightsTemplate() + + def default(self): + return ZoneTemplate() + + class Zones(extensions.ExtensionDescriptor): """Enables zones-related functionality such as adding child zones, listing child zones, getting the capabilities of the local zone, @@ -37,7 +216,29 @@ class Zones(extensions.ExtensionDescriptor): alias = "os-zones" namespace = "http://docs.openstack.org/compute/ext/zones/api/v1.1" updated = "2011-09-21T00:00:00+00:00" + admin_only = True def get_resources(self): - # Nothing yet. - return [] + body_serializers = { + 'application/xml': ZonesXMLSerializer(), + } + serializer = wsgi.ResponseSerializer(body_serializers) + + body_deserializers = { + 'application/xml': servers.ServerXMLDeserializer(), + } + deserializer = wsgi.RequestDeserializer(body_deserializers) + + #NOTE(bcwaldon): This resource should be prefixed with 'os-' + coll_actions = { + 'detail': 'GET', + 'info': 'GET', + 'select': 'POST', + } + + res = extensions.ResourceExtension('zones', + Controller(), + deserializer=deserializer, + serializer=serializer, + collection_actions=coll_actions) + return [res] diff --git a/nova/api/openstack/v2/zones.py b/nova/api/openstack/v2/zones.py deleted file mode 100644 index 8457eb5dd..000000000 --- a/nova/api/openstack/v2/zones.py +++ /dev/null @@ -1,215 +0,0 @@ -# Copyright 2011 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 json - -from nova.api.openstack import common -from nova.api.openstack.v2 import servers -from nova.api.openstack import xmlutil -from nova.api.openstack import wsgi -from nova.compute import api as compute -from nova import crypto -from nova import exception -from nova import flags -from nova import log as logging -from nova.scheduler import api - - -FLAGS = flags.FLAGS - - -LOG = logging.getLogger('nova.api.openstack.v2.zones') - - -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 _exclude_keys(item, keys): - return dict((k, v) for k, v in item.iteritems() if k and (k not in keys)) - - -def _scrub_zone(zone): - return _exclude_keys(zone, ('username', 'password', 'created_at', - 'deleted', 'deleted_at', 'updated_at')) - - -def check_encryption_key(func): - def wrapped(*args, **kwargs): - if not FLAGS.build_plan_encryption_key: - raise exception.Error(_("--build_plan_encryption_key not set")) - return func(*args, **kwargs) - return wrapped - - -class Controller(object): - """Controller for Zone resources.""" - - def __init__(self): - self.compute_api = compute.API() - - def index(self, req): - """Return all zones in brief""" - # Ask the ZoneManager in the Scheduler for most recent data, - # or fall-back to the database ... - items = api.get_zone_list(req.environ['nova.context']) - items = common.limited(items, req) - items = [_scrub_zone(item) for item in items] - return dict(zones=items) - - def detail(self, req): - """Return all zones in detail""" - return self.index(req) - - def info(self, req): - """Return name and capabilities for this zone.""" - items = api.get_zone_capabilities(req.environ['nova.context']) - - zone = dict(name=FLAGS.zone_name) - caps = FLAGS.zone_capabilities - for cap in caps: - key, value = cap.split('=') - zone[key] = value - for item, (min_value, max_value) in items.iteritems(): - zone[item] = "%s,%s" % (min_value, max_value) - return dict(zone=zone) - - def show(self, req, id): - """Return data about the given zone id""" - zone_id = int(id) - zone = api.zone_get(req.environ['nova.context'], zone_id) - return dict(zone=_scrub_zone(zone)) - - def delete(self, req, id): - """Delete a child zone entry.""" - zone_id = int(id) - api.zone_delete(req.environ['nova.context'], zone_id) - return {} - - def create(self, req, body): - """Create a child zone entry.""" - context = req.environ['nova.context'] - zone = api.zone_create(context, body["zone"]) - return dict(zone=_scrub_zone(zone)) - - def update(self, req, id, body): - """Update a child zone entry.""" - context = req.environ['nova.context'] - zone_id = int(id) - zone = api.zone_update(context, zone_id, body["zone"]) - return dict(zone=_scrub_zone(zone)) - - @check_encryption_key - def select(self, req, body): - """Returns a weighted list of costs to create instances - of desired capabilities.""" - ctx = req.environ['nova.context'] - specs = json.loads(body) - build_plan = api.select(ctx, specs=specs) - cooked = self._scrub_build_plan(build_plan) - return {"weights": cooked} - - def _scrub_build_plan(self, build_plan): - """Remove all the confidential data and return a sanitized - version of the build plan. Include an encrypted full version - of the weighting entry so we can get back to it later.""" - encryptor = crypto.encryptor(FLAGS.build_plan_encryption_key) - cooked = [] - for entry in build_plan: - json_entry = json.dumps(entry) - cipher_text = encryptor(json_entry) - cooked.append(dict(weight=entry['weight'], - blob=cipher_text)) - return cooked - - -class CapabilitySelector(object): - def __call__(self, obj, do_raise=False): - return [(k, v) for k, v in obj.items() - if k not in ('id', 'api_url', 'name', 'capabilities')] - - -def make_zone(elem): - #elem = xmlutil.SubTemplateElement(parent, 'zone', selector=selector) - elem.set('id') - elem.set('api_url') - elem.set('name') - elem.set('capabilities') - - cap = xmlutil.SubTemplateElement(elem, xmlutil.Selector(0), - selector=CapabilitySelector()) - cap.text = 1 - - -zone_nsmap = {None: wsgi.XMLNS_V10} - - -class ZoneTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('zone', selector='zone') - make_zone(root) - return xmlutil.MasterTemplate(root, 1, nsmap=zone_nsmap) - - -class ZonesTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('zones') - elem = xmlutil.SubTemplateElement(root, 'zone', selector='zones') - make_zone(elem) - return xmlutil.MasterTemplate(root, 1, nsmap=zone_nsmap) - - -class WeightsTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('weights') - weight = xmlutil.SubTemplateElement(root, 'weight', selector='weights') - blob = xmlutil.SubTemplateElement(weight, 'blob') - blob.text = 'blob' - inner_weight = xmlutil.SubTemplateElement(weight, 'weight') - inner_weight.text = 'weight' - return xmlutil.MasterTemplate(root, 1, nsmap=zone_nsmap) - - -class ZonesXMLSerializer(xmlutil.XMLTemplateSerializer): - def index(self): - return ZonesTemplate() - - def detail(self): - return ZonesTemplate() - - def select(self): - return WeightsTemplate() - - def default(self): - return ZoneTemplate() - - -def create_resource(): - body_serializers = { - 'application/xml': ZonesXMLSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) - - body_deserializers = { - 'application/xml': servers.ServerXMLDeserializer(), - } - deserializer = wsgi.RequestDeserializer(body_deserializers) - - return wsgi.Resource(Controller(), deserializer, serializer) -- cgit