occi-os/api/wsgi.py
2012-10-08 15:08:53 +02:00

373 lines
14 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (c) 2012, Intel Performance Learning Solutions Ltd.
#
# 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.
"""
OCCI WSGI app :-)
"""
# W0613:unused args,R0903:too few pub methods
# pylint: disable=W0613,R0903
import logging
from nova import flags
from nova import wsgi
from nova import context
from nova import db
from nova.image import glance
from nova.compute import instance_types
from nova.network import api
from nova.openstack.common import cfg
from api import registry
from api.compute import compute_resource, openstack
from api.compute import templates
from api.extensions import occi_future
from api.network import networklink
from api.network import networkresource
from api.storage import storagelink
from api.storage import storageresource
from occi import backend
from occi import core_model
from occi import wsgi as occi_wsgi
from occi.extensions import infrastructure
LOG = logging.getLogger('nova.api.wsgi.occi')
#Setup options
OCCI_OPTS = [
cfg.BoolOpt("show_default_net_config",
default=False,
help="Show the default network configuration to " \
"clients"),
cfg.BoolOpt("filter_kernel_and_ram_images",
default=True,
help="Whether to show the Kernel and RAM images to " \
"clients"),
cfg.StrOpt("net_manager",
default="nova",
help="The network manager to use with the OCCI API."),
cfg.IntOpt("occiapi_listen_port",
default=8787,
help="Port OCCI interface will listen on."),
cfg.StrOpt("occi_custom_location_hostname",
default=None,
help="Override OCCI location hostname with custom value")
]
FLAGS = flags.FLAGS
FLAGS.register_opts(OCCI_OPTS)
MIXIN_BACKEND = backend.MixinBackend()
class OCCIApplication(occi_wsgi.Application, wsgi.Application):
"""
Adapter which 'translates' represents a nova WSGI application into and OCCI
WSGI application.
"""
def __init__(self):
"""
Initialize the WSGI OCCI application.
"""
super(OCCIApplication, self).__init__(registry=registry.OCCIRegistry())
self.net_manager = FLAGS.get("net_manager", "nova")
self.no_default_network = True
self._register_occi_infra()
self._register_occi_extensions()
def _register_occi_infra(self):
"""
Registers the OCCI infrastructure resources to ensure compliance
with GFD184
"""
compute_backend = compute_resource.ComputeBackend()
if self.net_manager == "quantum":
msg = 'The quantum backend is currently not supported.'
LOG.error(msg)
raise Exception()
elif self.net_manager == "nova":
network_backend = networkresource.NetworkBackend()
networkinterface_backend = networklink.NetworkInterfaceBackend()
ipnetwork_backend = networkresource.IpNetworkBackend()
ipnetworking_backend = networklink.IpNetworkInterfaceBackend()
storage_backend = storageresource.StorageBackend()
storage_link_backend = storagelink.StorageLinkBackend()
# register kinds with backends
self.register_backend(infrastructure.COMPUTE, compute_backend)
self.register_backend(infrastructure.START, compute_backend)
self.register_backend(infrastructure.STOP, compute_backend)
self.register_backend(infrastructure.RESTART, compute_backend)
self.register_backend(infrastructure.SUSPEND, compute_backend)
self.register_backend(templates.OS_TEMPLATE, MIXIN_BACKEND)
self.register_backend(templates.RES_TEMPLATE, MIXIN_BACKEND)
self.register_backend(infrastructure.NETWORK, network_backend)
self.register_backend(infrastructure.UP, network_backend)
self.register_backend(infrastructure.DOWN, network_backend)
self.register_backend(infrastructure.NETWORKINTERFACE,
networkinterface_backend)
self.register_backend(infrastructure.IPNETWORK, ipnetwork_backend)
self.register_backend(infrastructure.IPNETWORKINTERFACE,
ipnetworking_backend)
self.register_backend(infrastructure.STORAGE, storage_backend)
self.register_backend(infrastructure.ONLINE, storage_backend)
self.register_backend(infrastructure.OFFLINE, storage_backend)
self.register_backend(infrastructure.BACKUP, storage_backend)
self.register_backend(infrastructure.SNAPSHOT, storage_backend)
self.register_backend(infrastructure.RESIZE, storage_backend)
self.register_backend(infrastructure.STORAGELINK, storage_link_backend)
def _register_occi_extensions(self):
"""
Register OCCI extensions contained within the 'extension' package.
"""
extensions = openstack.get_extensions()
for item in extensions:
for cat in item['categories']:
LOG.warn('adding:' + str(cat) + str(item['handler']))
self.register_backend(cat, item['handler'])
def __call__(self, environ, response):
"""
This will be called as defined by WSGI.
Deals with incoming requests and outgoing responses
Takes the incoming request, sends it on to the OCCI WSGI application,
which finds the appropriate backend for it and then executes the
request. The backend then is responsible for the return content.
environ -- The environ.
response -- The response.
"""
extras = {'nova_ctx': environ['nova.context']}
# When the API boots the network services may not be started.
# The call to the service is a sync RPC over rabbitmq and will block.
# We must register the network only once and once all services are
# available. Hence we perform the registration once here.
if self.no_default_network:
self._register_default_network()
# register/refresh openstack images
self._refresh_os_mixins(extras)
# register/refresh openstack instance types (flavours)
self._refresh_resource_mixins(extras)
# register/refresh the openstack security groups as Mixins
self._refresh_security_mixins(extras)
# register/refresh the openstack floating IP pools as Mixins
self._refresh_floating_ippools(extras)
return self._call_occi(environ, response, nova_ctx=extras['nova_ctx'],
registry=self.registry)
def _register_default_network(self):
"""
By default nova attaches a compute resource to a network.
In the OCCI model this is represented as a Network resource.
This method constructs that Network resource.
"""
#TODO(dizz): verify behaviour with quantum backend
# i.e. cover the case where there are > 1 networks
name = 'DEFAULT_NETWORK'
show_default_net_config = FLAGS.get("show_default_net_config", False)
net_attrs = {
'occi.core.id': name,
'occi.network.vlan': '',
'occi.network.label': 'public',
'occi.network.state': 'up',
'occi.network.address': '',
'occi.network.gateway': '',
'occi.network.allocation': '',
}
if not show_default_net_config:
net_attrs = get_net_info(net_attrs)
default_network = core_model.Resource(name, infrastructure.NETWORK,
[infrastructure.IPNETWORK], [],
'This is the network all VMs are attached to.',
'Default Network')
default_network.attributes = net_attrs
self.registry.add_resource(name, default_network, None)
self.no_default_network = False
def _refresh_os_mixins(self, extras):
"""
Register images as OsTemplate mixins from
information retrieved from glance (shared and user-specific).
"""
template_schema = 'http://schemas.openstack.org/template/os#'
image_service = glance.get_default_image_service()
images = image_service.detail(extras['nova_ctx'])
filter_kernel_and_ram_images = \
FLAGS.get("filter_kernel_and_ram_images", True)
for img in images:
# If the image is a kernel or ram one
# and we're not to filter them out then register it.
if (((img['container_format'] or img['disk_format'])
in ('ari', 'aki')) and filter_kernel_and_ram_images):
msg = 'Not registering kernel/RAM image.'
LOG.warn(msg)
continue
os_template = templates.OsTemplate(
term=img['name'],
scheme=template_schema,
os_id=img['id'],
related=[templates.OS_TEMPLATE],
attributes=None,
title='This is an OS ' + img['name'] + \
' VM image',
location='/' + img['name'] + '/')
msg = ('Registering an OS image type as: %s') % str(os_template)
LOG.debug(msg)
try:
self.registry.get_backend(os_template, extras)
except AttributeError:
self.register_backend(os_template, MIXIN_BACKEND)
def _refresh_resource_mixins(self, extras):
"""
Register the flavors as ResourceTemplates to which the user has access.
"""
template_schema = 'http://schemas.openstack.org/template/resource#'
os_flavours = instance_types.get_all_types()
for itype in os_flavours:
resource_template = templates.ResourceTemplate(
term=itype,
scheme=template_schema,
related=[templates.RES_TEMPLATE],
attributes=get_resource_attributes(os_flavours[itype]),
title='This is an openstack ' + itype + ' flavor.',
location='/' + itype + '/')
msg = ('Registering an OpenStack flavour/instance type: %s') % \
str(resource_template)
LOG.debug(msg)
try:
self.registry.get_backend(resource_template, extras)
except AttributeError:
self.register_backend(resource_template, MIXIN_BACKEND)
def _refresh_security_mixins(self, extras):
"""
Registers security groups as security mixins
"""
# ensures that preexisting openstack security groups are
# added and only once.
# collect these and add them to an exclusion list so they're
# not created again when listing non-user-defined sec. groups
excld_grps = []
for cat in self.registry.get_categories(extras):
if (isinstance(cat, core_model.Mixin) and
occi_future.SEC_GROUP in cat.related):
excld_grps.append(cat.term)
groups = db.security_group_get_by_project(extras['nova_ctx'],
extras['nova_ctx'].project_id)
sec_grp = 'http://schemas.openstack.org/infrastructure/security/group#'
for group in groups:
if group['name'] not in excld_grps:
sec_mix = occi_future.UserSecurityGroupMixin(
term=group['name'],
scheme=sec_grp,
related=[occi_future.SEC_GROUP],
attributes=None,
title=group['name'],
location='/security/' + group['name'] + '/')
try:
self.registry.get_backend(sec_mix, extras)
except AttributeError:
self.register_backend(sec_mix, MIXIN_BACKEND)
def _refresh_floating_ippools(self, extras):
"""
Gets the list of floating ip pools and registers them as mixins.
"""
network_api = api.API()
pools = network_api.get_floating_ip_pools(extras['nova_ctx'])
for pool in pools:
pool_mixin = core_model.Mixin(
term=pool['name'],
scheme='http://schemas.openstack.org/instance/network/pool/floating#',
related=[],
attributes=None,
title="This is a floating IP pool",
location='/network/pool/floating/')
try:
self.registry.get_backend(pool_mixin, extras)
except AttributeError:
self.register_backend(pool_mixin, MIXIN_BACKEND)
def get_net_info(net_attrs):
"""
Gets basic information about the default network.
"""
ctx = context.get_admin_context()
network_api = api.API()
networks = network_api.get_all(ctx)
if len(networks) > 1:
msg = ('There is more that one network.'
'Using the first network: %s') % networks[0]['id']
LOG.warn(msg)
net_attrs['occi.network.address'] = networks[0]['cidr']
net_attrs['occi.network.label'] = 'public'
net_attrs['occi.network.state'] = 'up'
net_attrs['occi.network.gateway'] = str(networks[0]['gateway'])
net_attrs['occi.network.allocation'] = 'dhcp'
return net_attrs
def get_resource_attributes(attrs):
"""
Gets the attributes required to render occi compliant compute
resource information.
"""
#TODO(dizz): This is hardcoded atm. Might be good to have
# it configurable
attrs = {
'occi.compute.cores': 'immutable',
'occi.compute.memory': 'immutable',
'org.openstack.compute.swap': 'immutable',
'org.openstack.compute.storage.root': 'immutable',
'org.openstack.compute.storage.ephemeral': 'immutable',
}
return attrs