added OCCI OS api

This commit is contained in:
unknown
2012-06-26 18:17:43 +02:00
parent 342d128b6f
commit 3785f2be2d
19 changed files with 2591 additions and 35 deletions

View File

@@ -36,7 +36,7 @@ sample_app entry point is defined in setup.py:
which point to this function call (<module name>:function).
'''
from api.wsgi_app import simple_app
from api import wsgi
def main(global_config, **settings):
@@ -44,4 +44,4 @@ def main(global_config, **settings):
This is the entry point for paste into the OCCI OS world.
'''
# TODO(tmetsch): point to correct OCCI OS app.
return simple_app
return wsgi.OCCIApplication()

16
api/compute/__init__.py Normal file
View File

@@ -0,0 +1,16 @@
# 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.

View File

@@ -0,0 +1,816 @@
# 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.
import uuid
from occi import backend
from occi import core_model
from occi.extensions import infrastructure
from webob import exc
from api.compute import templates
from api.extensions import occi_future
from api.extensions import openstack as os_extns
from nova import compute
from nova.compute import instance_types
from nova.compute import task_states
from nova.compute import vm_states
from nova import exception
from nova import flags
from nova import image
from nova import utils
from nova import log as logging
from nova.network import api as net_api
from nova.rpc import common as rpc_common
FLAGS = flags.FLAGS
# Hi I'm a logger, use me! :-)
LOG = logging.getLogger('nova.api.occi.backends.compute')
class ComputeBackend(backend.KindBackend, backend.ActionBackend):
"""
A Backend for compute instances.
"""
def __init__(self):
super(ComputeBackend, self).__init__()
self.compute_api = compute.API()
self.network_api = net_api.API()
def _check_invalid_attrs(self, resource):
"""
If certain OCCI attributes are received then
an attribute exception is raised.
"""
if (('occi.compute.cores' in resource.attributes) or
('occi.compute.speed' in resource.attributes) or
('occi.compute.memory' in resource.attributes) or
('occi.compute.architecture' in resource.attributes)):
msg = _('There are unsupported attributes in the request.')
LOG.error(msg)
raise AttributeError(msg)
def create(self, resource, extras):
"""
creates the VM!
If a request arrives with explicit values for certain attrs
like occi.compute.cores then a bad request must be issued as
OpenStack does not support this.
"""
resource.links = []
msg = _('Creating the virtual machine.')
LOG.info(msg)
self._check_invalid_attrs(resource)
name = (resource.attributes['occi.compute.hostname']
if 'occi.compute.hostname' in resource.attributes else None)
key_name = key_data = None
#Auto-gen'ed 1st. If OCCI extension supplied this will overwrite this
password = utils.generate_password(FLAGS.password_length)
access_ip_v4 = None
access_ip_v6 = None
# Would be good to specify user_data via OCCI. Look to use
# CompatibleOne extensions spec.
user_data = None
metadata = {}
injected_files = []
min_count = max_count = 1
requested_networks = None
sg_names = []
availability_zone = None
config_drive = None
block_device_mapping = None
# these can be specified through OS Templates
kernel_id = ramdisk_id = None
auto_disk_config = None
scheduler_hints = None
# extract mixin information
rc = oc = 0
for mixin in resource.mixins:
if isinstance(mixin, templates.ResourceTemplate):
r = mixin
rc += 1
elif isinstance(mixin, templates.OsTemplate):
os_tpl = mixin
oc += 1
elif mixin == os_extns.OS_KEY_PAIR_EXT:
attr = 'org.openstack.credentials.publickey.name'
key_name = \
resource.attributes[attr]
attr = 'org.openstack.credentials.publickey.data'
key_data = \
resource.attributes[attr]
elif mixin == os_extns.OS_ADMIN_PWD_EXT:
password = \
resource.attributes['org.openstack.credentials.admin_pwd']
elif mixin == os_extns.OS_ACCESS_IP_EXT:
attr = 'org.openstack.network.access.version'
if resource.attributes[attr] == 'ipv4':
access_ip_v4 = \
resource.attributes['org.openstack.network.access.ip']
elif resource.attributes[attr] == 'ipv6':
access_ip_v6 = \
resource.attributes['org.openstack.network.access.ip']
else:
raise exc.HTTPBadRequest()
#Look for security group. If the group is non-existant, the
#call to create will fail.
if occi_future.SEC_GROUP in mixin.related:
sg_names.append(mixin.term)
if rc < 1 and oc < 1:
msg = _('No resource or OS template in the request.')
LOG.error(msg)
exc.HTTPBadRequest()
if rc > 1 or oc > 1:
msg = _('More than one resource/OS template in the request.')
LOG.error(msg)
raise AttributeError(msg=unicode(msg))
#If no security group, ensure the default is applied
if len(sg_names) == 0:
sg_names.append('default')
flavor_name = r.term
os_tpl_url = os_tpl.os_id
sg_names = list(set(sg_names))
try:
if flavor_name:
inst_type = \
instance_types.get_instance_type_by_name(flavor_name)
else:
inst_type = instance_types.get_default_instance_type()
msg = _('No resource template was found in the request. '
'Using the default: %s') % inst_type['name']
LOG.warn(msg)
(instances, _reservation_id) = self.compute_api.create(
context=extras['nova_ctx'],
instance_type=inst_type,
image_href=os_tpl_url,
kernel_id=kernel_id,
ramdisk_id=ramdisk_id,
min_count=min_count,
max_count=max_count,
display_name=name,
display_description=name,
key_name=key_name,
key_data=key_data,
security_group=sg_names,
availability_zone=availability_zone,
user_data=user_data,
metadata=metadata,
injected_files=injected_files,
admin_password=password,
block_device_mapping=block_device_mapping,
access_ip_v4=access_ip_v4,
access_ip_v6=access_ip_v6,
requested_networks=requested_networks,
config_drive=config_drive,
auto_disk_config=auto_disk_config,
scheduler_hints=scheduler_hints)
except exception.QuotaError as error:
self._handle_quota_error(error)
except exception.InstanceTypeMemoryTooSmall as error:
raise exc.HTTPBadRequest(explanation=unicode(error))
except exception.InstanceTypeDiskTooSmall as error:
raise exc.HTTPBadRequest(explanation=unicode(error))
except exception.ImageNotFound as error:
msg = _("Can not find requested image")
raise exc.HTTPBadRequest(explanation=msg)
except exception.FlavorNotFound as error:
msg = _("Invalid flavor provided.")
raise exc.HTTPBadRequest(explanation=msg)
except exception.KeypairNotFound as error:
msg = _("Invalid key_name provided.")
raise exc.HTTPBadRequest(explanation=msg)
except exception.SecurityGroupNotFound as error:
raise exc.HTTPBadRequest(explanation=unicode(error))
except rpc_common.RemoteError as err:
msg = "%(err_type)s: %(err_msg)s" % \
{'err_type': err.exc_type, 'err_msg': err.value}
raise exc.HTTPBadRequest(explanation=msg)
#add resource attribute values
resource.attributes['occi.core.id'] = instances[0]['uuid']
resource.attributes['occi.compute.hostname'] = instances[0]['hostname']
resource.attributes['occi.compute.architecture'] = \
self._get_vm_arch(extras['nova_ctx'],
os_tpl)
resource.attributes['occi.compute.cores'] = str(instances[0]['vcpus'])
# TODO(dizz): possible blueprint?
# occi.compute.speed is not available in instances by
# default.CPU speed is not available but could be made available
# through: db::nova::compute_nodes::cpu_info
# Additional code is required in:
# nova/nova/virt/libvirt/connection.py::get_cpu_info()
msg = _('Cannot tell what the CPU speed is.')
LOG.info(msg)
resource.attributes['occi.compute.speed'] = str(0.0)
resource.attributes['occi.compute.memory'] = \
str(float(instances[0]['memory_mb']) / 1024)
# the resource is not necessarily active at this stage. review.
resource.attributes['occi.compute.state'] = 'active'
# Once created, the VM is attached to a public network with an
# addresses allocated by DHCP
# A link is created to this network (IP) and set the ip to that of the
# allocated ip
vm_net_info = self._get_adapter_info(instances[0], extras)
self._attach_to_default_network(vm_net_info, resource, extras)
self._get_console_info(resource, instances[0], extras)
self._attach_to_local_storage(inst_type)
#set valid actions
resource.actions = [infrastructure.STOP,
infrastructure.SUSPEND,
infrastructure.RESTART,
os_extns.OS_REVERT_RESIZE,
os_extns.OS_CONFIRM_RESIZE,
os_extns.OS_CREATE_IMAGE]
def _attach_to_local_storage(self, inst_type):
"""
Associate ephemeral or root storage with compute instance
"""
# TODO(dizz): if there is ephemeral or root storage, assciate it!
pass
def _get_vm_arch(self, context, os_template_mixin):
"""
Extract architecture from either:
- image name, title or metadata. The architecture is sometimes
encoded in the image's name
- db::glance::image_properties could be used reliably so long as the
information is supplied when registering an image with glance.
Heuristic:
- if term, title or description has x86_32 or x86_x64 then the arch
is x86 or x64 respectively.
- if associated OS image has properties arch or architecture that
equal x86 or x64.
- else return a default of x86
"""
arch = ''
if ((os_template_mixin.term.find('x86_64')
or os_template_mixin.title.find('x86_64')) >= 0):
arch = 'x64'
elif ((os_template_mixin.term.find('x86_32')
or os_template_mixin.title.find('x86_32')) >= 0):
arch = 'x86'
else:
image_service = image.get_default_image_service()
img = image_service.show(context, os_template_mixin.os_id)
img_props = img['properties']
if ('arch' in img_props):
arch = img['properties']['arch']
elif ('architecture' in img_props):
arch = img['properties']['architecture']
# if all attempts fail set it to a default value
if arch == '':
arch = 'x86'
return arch
def _get_adapter_info(self, instance, extras):
"""
Extracts the VMs network adapter information: interface name,
IP address, gateway and mac address.
"""
# TODO(dizz): currently this assumes one adapter on the VM.
# It's likely that this will not be the case when using Quantum
vm_net_info = {'vm_iface': '', 'address': '', 'gateway': '', 'mac': '',
'allocation': ''}
sj = self.network_api.get_instance_nw_info(extras['nova_ctx'],
instance)
#catches an odd error whereby no network info is returned back
if len(sj) <= 0:
msg = _('No network info was returned either live or cached.')
LOG.warn(msg)
return vm_net_info
vm_net_info['vm_iface'] = sj[0]['network']['meta']['bridge_interface']
#OS-specific if a VM is stopped it has no IP address
if len(sj[0]['network']['subnets'][0]['ips']) > 0:
vm_net_info['address'] = \
sj[0]['network']['subnets'][0]['ips'][0]['address']
else:
vm_net_info['address'] = ''
vm_net_info['gateway'] = \
sj[0]['network']['subnets'][0]['gateway']['address']
vm_net_info['mac'] = sj[0]['address']
if sj[0]['network']['subnets'][0]['ips'][0]['type'] == 'fixed':
vm_net_info['allocation'] = 'static'
else:
vm_net_info['allocation'] = 'dynamic'
return vm_net_info
def _attach_to_default_network(self, vm_net_info, resource, extras):
"""
Associates a network adapter with the relevant network resource.
"""
# check that existing network does not exist
scheme = "http://schemas.ogf.org/occi/infrastructure#"
if len(resource.links) > 0:
for link in resource.links:
if (link.kind.term == "networkinterface"
and link.kind.scheme == scheme):
msg = _('A link to the network already exists. '
'Will update the links attributes.')
LOG.debug(msg)
link.attributes['occi.networkinterface.interface'] = \
vm_net_info['vm_iface']
link.attributes['occi.networkinterface.address'] = \
vm_net_info['address']
link.attributes['occi.networkinterface.gateway'] = \
vm_net_info['gateway']
link.attributes['occi.networkinterface.mac'] = \
vm_net_info['mac']
return
# If the network association does not exist...
# Get a handle to the default network
registry = extras['registry']
default_network = registry.get_resource('/network/DEFAULT_NETWORK',
None)
source = resource
target = default_network
# Create the link to the default network
identifier = str(uuid.uuid4())
link = core_model.Link(identifier, infrastructure.NETWORKINTERFACE,
[infrastructure.IPNETWORKINTERFACE], source, target)
link.attributes['occi.core.id'] = identifier
link.attributes['occi.networkinterface.interface'] = \
vm_net_info['vm_iface']
link.attributes['occi.networkinterface.mac'] = vm_net_info['mac']
link.attributes['occi.networkinterface.state'] = 'active'
link.attributes['occi.networkinterface.address'] = \
vm_net_info['address']
link.attributes['occi.networkinterface.gateway'] = \
vm_net_info['gateway']
link.attributes['occi.networkinterface.allocation'] = \
vm_net_info['allocation']
resource.links.append(link)
registry.add_resource(identifier, link, extras)
def _get_console_info(self, resource, instance, extras):
"""
Adds console access information to the resource.
"""
address = resource.links[0].attributes['occi.networkinterface.address']
ssh_console_present = False
vnc_console_present = False
comp_sch = 'http://schemas.openstack.org/occi/infrastructure/compute#'
for link in resource.links:
if (link.target.kind.term == "ssh_console" and
link.target.kind.scheme == comp_sch):
ssh_console_present = True
elif (link.target.kind.term == "vnc_console" and
link.target.kind.scheme == comp_sch):
vnc_console_present = True
if not ssh_console_present:
registry = extras['registry']
identifier = str(uuid.uuid4())
ssh_console = core_model.Resource(
identifier, occi_future.SSH_CONSOLE, [],
links=None, summary='',
title='')
ssh_console.attributes['occi.core.id'] = identifier
ssh_console.attributes['org.openstack.compute.console.ssh'] = \
'ssh://' + address + ':22'
registry.add_resource(identifier, ssh_console, extras)
identifier = str(uuid.uuid4())
ssh_console_link = core_model.Link(
identifier,
occi_future.CONSOLE_LINK,
[], resource, ssh_console)
ssh_console_link.attributes['occi.core.id'] = identifier
registry.add_resource(identifier, ssh_console_link, extras)
resource.links.append(ssh_console_link)
if not vnc_console_present:
try:
console = self.compute_api.get_vnc_console(extras['nova_ctx'],
instance, 'novnc')
except Exception:
msg = _('Console info is not available yet.')
LOG.debug(msg)
return
registry = extras['registry']
identifier = str(uuid.uuid4())
vnc_console = core_model.Resource(
identifier, occi_future.VNC_CONSOLE, [],
links=None, summary='',
title='')
vnc_console.attributes['occi.core.id'] = identifier
vnc_console.attributes['org.openstack.compute.console.vnc'] = \
console['url']
registry.add_resource(identifier, vnc_console, extras)
identifier = str(uuid.uuid4())
vnc_console_link = core_model.Link(
identifier,
occi_future.CONSOLE_LINK,
[], resource, vnc_console)
vnc_console_link.attributes['occi.core.id'] = identifier
registry.add_resource(identifier, vnc_console_link, extras)
resource.links.append(vnc_console_link)
def _handle_quota_error(self, error):
"""
Reraise quota errors as api-specific http exceptions
"""
# Note this is a direct lift from nova/api/openstack/compute/servers.py
# however as it is protected we cannot import it :-(
code_mappings = {
"OnsetFileLimitExceeded":
_("Personality file limit exceeded"),
"OnsetFilePathLimitExceeded":
_("Personality file path too long"),
"OnsetFileContentLimitExceeded":
_("Personality file content too long"),
# NOTE(bcwaldon): expose the message generated below in order
# to better explain how the quota was exceeded
"InstanceLimitExceeded": error.message,
}
expl = code_mappings.get(error.kwargs['code'], error.message)
raise exc.HTTPRequestEntityTooLarge(explanation=expl,
headers={'Retry-After': 0})
def retrieve(self, entity, extras):
"""
Prepares the resource representation ready for pyssf rendering
"""
context = extras['nova_ctx']
uid = entity.attributes['occi.core.id']
try:
instance = self.compute_api.get(context, uid)
except exception.NotFound:
raise exc.HTTPNotFound()
# See nova/compute/vm_states.py nova/compute/task_states.py
#
# Mapping assumptions:
# - active == VM can service requests from network. These requests
# can be from users or VMs
# - inactive == the oppose! :-)
# - suspended == machine in a frozen state e.g. via suspend or pause
# change password - OS
# confirm resized server
if instance['vm_state'] in (vm_states.ACTIVE,
task_states.UPDATING_PASSWORD,
task_states.RESIZE_VERIFY):
entity.attributes['occi.compute.state'] = 'active'
entity.actions = [infrastructure.STOP,
infrastructure.SUSPEND,
infrastructure.RESTART,
os_extns.OS_CONFIRM_RESIZE,
os_extns.OS_REVERT_RESIZE,
os_extns.OS_CHG_PWD,
os_extns.OS_CREATE_IMAGE]
# reboot server - OS, OCCI
# start server - OCCI
elif instance['vm_state'] in (task_states.STARTING,
task_states.POWERING_ON,
task_states.REBOOTING,
task_states.REBOOTING_HARD):
entity.attributes['occi.compute.state'] = 'inactive'
entity.actions = []
# pause server - OCCI, suspend server - OCCI, stop server - OCCI
elif instance['vm_state'] in (task_states.STOPPING,
task_states.POWERING_OFF):
entity.attributes['occi.compute.state'] = 'inactive'
entity.actions = [infrastructure.START]
# resume server - OCCI
elif instance['vm_state'] in (task_states.RESUMING,
task_states.PAUSING,
task_states.SUSPENDING):
entity.attributes['occi.compute.state'] = 'suspended'
if instance['vm_state'] in (vm_states.PAUSED,
vm_states.SUSPENDED):
entity.actions = [infrastructure.START]
else:
entity.actions = []
# rebuild server - OS
# resize server confirm rebuild
# revert resized server - OS (indirectly OCCI)
elif instance['vm_state'] in (
vm_states.RESIZING,
vm_states.REBUILDING,
task_states.RESIZE_CONFIRMING,
task_states.RESIZE_FINISH,
task_states.RESIZE_MIGRATED,
task_states.RESIZE_MIGRATING,
task_states.RESIZE_PREP,
task_states.RESIZE_REVERTING):
entity.attributes['occi.compute.state'] = 'inactive'
entity.actions = []
#Now we have the instance state, get its updated network info
vm_net_info = self._get_adapter_info(instance, extras)
self._attach_to_default_network(vm_net_info, entity, extras)
self._get_console_info(entity, instance, extras)
return instance
def delete(self, entity, extras):
"""
Deletes the referenced VM.
"""
msg = _('Removing representation of virtual machine with id: %s') % \
entity.identifier
LOG.info(msg)
context = extras['nova_ctx']
uid = entity.attributes['occi.core.id']
try:
instance = self.compute_api.get(context, uid)
except exception.NotFound:
raise exc.HTTPNotFound()
if FLAGS.reclaim_instance_interval:
self.compute_api.soft_delete(context, instance)
else:
self.compute_api.delete(context, instance)
def update(self, old, new, extras):
"""
Updates basic attribute information, resizes the VM and rebuilds the
VM. Only one mixin update per execution.
"""
msg = _('Partial update requested for instance: %s') % \
old.attributes['occi.core.id']
LOG.info(msg)
instance = self.retrieve(old, extras)
if len(new.attributes) > 0:
self._update_attrs(old, new)
# for now we will only handle one mixin change per request
mixin = new.mixins[0]
if isinstance(mixin, templates.ResourceTemplate):
self._update_resize_vm(old, extras, instance, mixin)
elif isinstance(mixin, templates.OsTemplate):
# do we need to check for new os rebuild in new?
self._update_rebuild_vm(old, extras, instance, mixin)
elif isinstance(mixin, occi_future.UserSecurityGroupMixin):
#TODO(dizz): should we implement this here?
msg = _('Updating security rule group')
LOG.info(msg)
raise exc.HTTPBadRequest()
else:
tmpl = '%s%s' % (mixin.scheme, mixin.term)
msg = _('Unrecognised mixin. %s') % tmpl
LOG.error()
raise exc.HTTPBadRequest()
def _update_attrs(self, old, new):
"""
Updates basic attributes. Supports only title and summary changes.
"""
msg = _('Updating mutable attributes of instance')
LOG.info(msg)
if (('occi.core.title' in new.attributes)
or ('occi.core.title' in new.attributes)):
if len(new.attributes['occi.core.title']) > 0:
old.attributes['occi.core.title'] = \
new.attributes['occi.core.title']
if len(new.attributes['occi.core.summary']) > 0:
old.attributes['occi.core.summary'] = \
new.attributes['occi.core.summary']
else:
msg = _('Cannot update the supplied attributes.')
LOG.error(msg)
raise exc.HTTPBadRequest
def _update_resize_vm(self, old, extras, instance, mixin):
"""
Resizes up or down a VM
Update: libvirt now supports resize see:
http://wiki.openstack.org/HypervisorSupportMatrix
"""
msg = _('Resize requested')
LOG.info(msg)
flavor = instance_types.get_instance_type_by_name(mixin.term)
kwargs = {}
try:
self.compute_api.resize(extras['nova_ctx'], instance,
flavor_id=flavor['flavorid'], **kwargs)
except exception.FlavorNotFound:
msg = _("Unable to locate requested flavor.")
raise exc.HTTPBadRequest(explanation=msg)
except exception.CannotResizeToSameSize:
msg = _("Resize requires a change in size.")
raise exc.HTTPBadRequest(explanation=msg)
except exception.InstanceInvalidState:
exc.HTTPConflict()
old.attributes['occi.compute.state'] = 'inactive'
#now update the mixin info
for m in old.mixins:
if m.term == mixin.term and m.scheme == mixin.scheme:
m = mixin
tmpl = '%s%s' % (m.scheme, m.term)
msg = _('Resource template is changed: %s') % tmpl
LOG.debug(msg)
def _update_rebuild_vm(self, old, extras, instance, mixin):
"""
Rebuilds the specified VM with the supplied OsTemplate mixin.
"""
# TODO(dizz): Use the admin_password extension?
msg = _('Rebuild requested')
LOG.info(msg)
image_href = mixin.os_id
admin_password = utils.generate_password(FLAGS.password_length)
kwargs = {}
try:
self.compute_api.rebuild(extras['nova_ctx'], instance,
image_href, admin_password, **kwargs)
except exception.InstanceInvalidState:
exc.HTTPConflict()
except exception.InstanceNotFound:
msg = _("Instance could not be found")
raise exc.HTTPNotFound(explanation=msg)
except exception.ImageNotFound:
msg = _("Cannot find image for rebuild")
raise exc.HTTPBadRequest(explanation=msg)
old.attributes['occi.compute.state'] = 'inactive'
#now update the mixin info
for m in old.mixins:
if m.term == mixin.term and m.scheme == mixin.scheme:
m = mixin
tmpl = '%s%s' % (m.scheme, m.term)
msg = _('OS template is changed: %s') % tmpl
LOG.debug(msg)
def action(self, entity, action, attributes, extras):
"""
Executed when a request for an action against a resource is received.
"""
# As there is no callback mechanism to update the state
# of computes known by occi, a call to get the latest representation
# must be made.
instance = self.retrieve(entity, extras)
context = extras['nova_ctx']
if action not in entity.actions:
raise AttributeError("This action is not currently applicable.")
elif action == infrastructure.START:
self._start_vm(entity, instance, context)
elif action == infrastructure.STOP:
self._stop_vm(entity, attributes, instance, context)
elif action == infrastructure.RESTART:
self._restart_vm(entity, attributes, instance, context)
elif action == infrastructure.SUSPEND:
self._suspend_vm(entity, attributes, instance, context)
else:
raise exc.HTTPBadRequest()
def _start_vm(self, entity, instance, context):
"""
Starts a vm that is in the stopped state. Note, currently we do not
use the nova start and stop, rather the resume/suspend methods. The
start action also unpauses a paused VM.
"""
msg = _('Starting virtual machine with id %s') % entity.identifier
LOG.info(msg)
try:
if entity.attributes['occi.compute.state'] == 'suspended':
self.compute_api.unpause(context, instance)
else:
self.compute_api.resume(context, instance)
except Exception:
msg = _('Error in starting VM')
LOG.error(msg)
raise exc.HTTPServerError()
entity.attributes['occi.compute.state'] = 'active'
entity.actions = [infrastructure.STOP,
infrastructure.SUSPEND,
infrastructure.RESTART,
os_extns.OS_REVERT_RESIZE,
os_extns.OS_CONFIRM_RESIZE,
os_extns.OS_CREATE_IMAGE]
def _stop_vm(self, entity, attributes, instance, context):
"""
Stops a VM. Rather than use stop, suspend is used.
OCCI -> graceful, acpioff, poweroff
OS -> unclear
"""
msg = _('Stopping virtual machine with id %s') % entity.identifier
LOG.info(msg)
if 'method' in attributes:
msg = _('OS only allows one type of stop. '
'What is specified in the request will be ignored.')
LOG.info(msg)
try:
# TODO(dizz): There are issues with the stop and start methods of
# OS. For now we'll use suspend.
# self.compute_api.stop(context, instance)
self.compute_api.suspend(context, instance)
except Exception:
msg = _('Error in stopping VM')
LOG.error(msg)
raise exc.HTTPServerError()
entity.attributes['occi.compute.state'] = 'inactive'
entity.actions = [infrastructure.START]
def _restart_vm(self, entity, attributes, instance, context):
"""
Restarts a VM.
OS types == SOFT, HARD
OCCI -> graceful, warm and cold
mapping:
- SOFT -> graceful, warm
- HARD -> cold
"""
msg = _('Restarting virtual machine with id %s') % entity.identifier
LOG.info(msg)
if not 'method' in attributes:
raise exc.HTTPBadRequest()
if attributes['method'] in ('graceful', 'warm'):
reboot_type = 'SOFT'
elif attributes['method'] is 'cold':
reboot_type = 'HARD'
else:
raise exc.HTTPBadRequest()
try:
self.compute_api.reboot(context, instance, reboot_type)
except exception.InstanceInvalidState:
exc.HTTPConflict()
except Exception as e:
msg = _("Error in reboot %s") % e
LOG.exception(msg)
raise exc.HTTPUnprocessableEntity()
entity.attributes['occi.compute.state'] = 'inactive'
entity.actions = []
def _suspend_vm(self, entity, attributes, instance, context):
"""
Suspends a VM. Use the start action to unsuspend a VM.
"""
msg = _('Stopping (suspending) virtual machine with id %s') % \
entity.identifier
LOG.info(msg)
if 'method' in attributes:
msg = _('OS only allows one type of suspend. '
'What is specified in the request will be ignored.')
LOG.info(msg)
try:
self.compute_api.pause(context, instance)
except Exception:
msg = _('Error in stopping VM.')
LOG.error(msg)
raise exc.HTTPServerError()
entity.attributes['occi.compute.state'] = 'suspended'
entity.actions = [infrastructure.START]

46
api/compute/templates.py Normal file
View File

@@ -0,0 +1,46 @@
# 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.
from occi import core_model
class OsTemplate(core_model.Mixin):
"""
Represents the OS Template mechanism as per OCCI specification.
An OS template is equivalent to an image in OpenStack
"""
def __init__(self, scheme, term, os_id=None, related=None, actions=None,
title='', attributes=None, location=None):
super(OsTemplate, self).__init__(scheme, term, related, actions,
title, attributes, location)
self.os_id = os_id
OS_TEMPLATE = OsTemplate('http://schemas.ogf.org/occi/infrastructure#',
'os_tpl')
class ResourceTemplate(core_model.Mixin):
"""
Represents the Resource Template mechanism as per OCCI specification.
An Resource template is equivocal to a flavor in OpenStack. Implemented
as such for consistency with OsTemplate.
"""
pass
RES_TEMPLATE = ResourceTemplate('http://schemas.ogf.org/occi/infrastructure#',
'resource_tpl')

35
api/extensions/README.rst Normal file
View File

@@ -0,0 +1,35 @@
Copyright (c) 2012, Intel Performance Learning Solutions Ltd.
============================
Creating OCCI Extensions
============================
To create OCCI extensions:
1. Define the method to retreive all extension information. This method
**must** have the following signature:
``def get_extensions()``
It **must** return a list. That list **must** contain at least one dict.
The dict **must** contain a pyssf backend handler. The dict **must** also
contain a list of OCCI Categories that are handled by the pyssf backend
handler. The OCCI Categories and pyssf backend handler **should** be
defined in the same python file.
2. Define the extension categories. Depending on your needs you can define
Kind, Mixin, Link or Action extensions.
3. Define the extension handler(s).
Examples of OCCI extensions can be found in the same directory as this README
file.
* ``fiware.py`` - this includes a very basic extension.
* ``occi_future.py`` - this includes extensions that are considered as
future OCCI-WG extensions.
* ``openstack.py`` - this includes various OpenStack specific extensions.
All extensions are enumerated via ``__init__.py`` and loaded via ``wsgi.py``.

View File

@@ -0,0 +1,50 @@
# 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.
import os
from nova import log as logging
LOG = logging.getLogger('nova.api.occi.extensions')
EXTENSIONS = []
def load_extensions():
"""
Loads compliant extensions found within the extensions module.
See README.rst for details on extensions.
"""
pth = __file__.rpartition(os.sep)
pth = pth[0] + pth[1]
# Walkthrough the extensions directory
msg = _('Loading the following extensions...')
LOG.info(msg)
for _dirpath, _dirnames, filenames in os.walk(pth):
for filename in filenames:
if (filename.endswith('.py') and
not filename.startswith('__init__')):
mod = filename.split('.py')[0]
exec('from %s import %s' % (__package__, mod))
extn = eval(mod).get_extensions()
EXTENSIONS.append(extn)
msg = _('Loading occi extension: %s') % extn
LOG.info(msg)
load_extensions()

36
api/extensions/fiware.py Normal file
View File

@@ -0,0 +1,36 @@
# 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.
from occi import backend
from occi import core_model
######################### FIware Specific Additions ##########################
def get_extensions():
return [
{
'categories': [TCP, ],
'handler': backend.MixinBackend(),
},
]
# Trusted Compute Pool technology mixin definition
_TCP_ATTRIBUTES = {'eu.fi-ware.compute.tcp': '', }
TCP = core_model.Mixin(
'http://schemas.fi-ware.eu/occi/infrastructure/compute#',
'tcp', attributes=_TCP_ATTRIBUTES)

View File

@@ -0,0 +1,333 @@
# 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.
import random
from occi import backend
from occi import core_model
from webob import exc
from nova import compute
from nova import db
from nova import flags
from nova import log as logging
from nova import utils
# TODO(dizz): Remove SSH Console and VNC Console once URI support is added to
# pyssf
#Hi I'm a logger, use me! :-)
LOG = logging.getLogger('nova.api.occi.backends.securityrule')
FLAGS = flags.FLAGS
####################### OCCI Candidate Spec Additions ########################
def get_extensions():
return [
{
'categories': [CONSOLE_LINK, SSH_CONSOLE, VNC_CONSOLE, ],
'handler': backend.KindBackend(),
},
{
'categories': [SEC_RULE, ],
'handler': SecurityRuleBackend(),
},
{
'categories': [SEC_GROUP, ],
'handler': SecurityGroupBackend(),
},
]
# Console Link Extension
CONSOLE_LINK = core_model.Kind(
'http://schemas.ogf.org/infrastructure/compute#',
'console',
[core_model.Link.kind],
None,
'This is a link to the VMs console',
None,
'/compute/consolelink/')
# SSH Console Kind Extension
_SSH_CONSOLE_ATTRIBUTES = {'org.openstack.compute.console.ssh': '', }
SSH_CONSOLE = core_model.Kind(
'http://schemas.openstack.org/occi/infrastructure/compute#',
'ssh_console',
None,
None,
'SSH console kind',
_SSH_CONSOLE_ATTRIBUTES,
'/compute/console/ssh/')
# VNC Console Kind Extension
_VNC_CONSOLE_ATTRIBUTES = {'org.openstack.compute.console.vnc': '', }
VNC_CONSOLE = core_model.Kind(
'http://schemas.openstack.org/occi/infrastructure/compute#',
'vnc_console',
None,
None,
'VNC console kind',
_VNC_CONSOLE_ATTRIBUTES,
'/compute/console/vnc/')
# Network security rule extension to specify firewall rules
_SEC_RULE_ATTRIBUTES = {
'occi.network.security.protocol': '',
'occi.network.security.to': '',
'occi.network.security.from': '',
'occi.network.security.range': '',
}
SEC_RULE = core_model.Kind(
'http://schemas.openstack.org/occi/infrastructure/network/security#',
'rule',
[core_model.Resource.kind],
None,
'Network security rule kind',
_SEC_RULE_ATTRIBUTES,
'/network/security/rule/')
# Network security rule group
SEC_GROUP = core_model.Mixin(
'http://schemas.ogf.org/occi/infrastructure/security#',
'group', attributes=None)
# An extended Mixin, an extension
class UserSecurityGroupMixin(core_model.Mixin):
pass
# The same approach can be used to create and delete VM images.
class SecurityGroupBackend(backend.UserDefinedMixinBackend):
def __init__(self):
super(SecurityGroupBackend, self).__init__()
self.compute_api = compute.API()
self.sgh = utils.import_object(FLAGS.security_group_handler)
def init_sec_group(self, category, extras):
"""
Creates the security group as specified in the request.
"""
#do not recreate default openstack security groups
if (category.scheme ==
'http://schemas.openstack.org/infrastructure/security/group#'):
return
context = extras['nova_ctx']
group_name = category.term.strip()
group_description = (category.title.strip()
if category.title else group_name)
self.compute_api.ensure_default_security_group(context)
if db.security_group_exists(context, context.project_id, group_name):
raise exc.HTTPBadRequest(
explanation=_('Security group %s already exists') % group_name)
group = {'user_id': context.user_id,
'project_id': context.project_id,
'name': group_name,
'description': group_description}
db.security_group_create(context, group)
self.sgh.trigger_security_group_create_refresh(context, group)
def destroy(self, category, extras):
"""
Deletes the specified security group.
"""
context = extras['nova_ctx']
security_group = self._get_sec_group(extras, category)
if db.security_group_in_use(context, security_group['id']):
raise exc.HTTPBadRequest(
explanation=_("Security group is still in use"))
db.security_group_destroy(context, security_group['id'])
self.sgh.trigger_security_group_destroy_refresh(
context, security_group['id'])
def _get_sec_group(self, extras, sec_mixin):
"""
Retreive the security group by name associated with the security mixin.
"""
try:
sec_group = db.security_group_get_by_name(extras['nova_ctx'],
extras['nova_ctx'].project_id, sec_mixin.term)
except Exception:
msg = _('Security group does not exist.')
LOG.error(msg)
raise exc.HTTPBadRequest()
return sec_group
class SecurityRuleBackend(backend.KindBackend):
def __init__(self):
super(SecurityRuleBackend, self).__init__()
self.compute_api = compute.API()
self.sgh = utils.import_object(FLAGS.security_group_handler)
def create(self, entity, extras):
"""
Creates a security rule.
The group to add the rule to must exist.
In OCCI-speak this means the mixin must be supplied with the request
"""
msg = _('Creating a network security rule')
LOG.info(msg)
sec_mixin = self._get_sec_mixin(entity)
security_group = self._get_sec_group(extras, sec_mixin)
sg_rule = self._make_sec_rule(entity, security_group['id'])
if self._security_group_rule_exists(security_group, sg_rule):
#This rule already exists in group
msg = _('This rule already exists in group. %s') % \
str(security_group)
LOG.error(msg)
raise exc.HTTPBadRequest()
db.security_group_rule_create(extras['nova_ctx'], sg_rule)
def _make_sec_rule(self, entity, sec_grp_id):
"""
Create and validate the security rule.
"""
sg_rule = {}
sg_rule['id'] = random.randrange(0, 99999999)
entity.attributes['occi.core.id'] = str(sg_rule['id'])
sg_rule['parent_group_id'] = sec_grp_id
prot = \
entity.attributes['occi.network.security.protocol'].lower().strip()
if prot in ('tcp', 'udp', 'icmp'):
sg_rule['protocol'] = prot
else:
raise exc.HTTPBadRequest()
from_p = entity.attributes['occi.network.security.to'].strip()
from_p = int(from_p)
if (type(from_p) is int) and from_p > 0 and from_p <= 65535:
sg_rule['from_port'] = from_p
else:
raise exc.HTTPBadRequest()
to_p = entity.attributes['occi.network.security.to'].strip()
to_p = int(to_p)
if (type(to_p) is int) and to_p > 0 and to_p <= 65535:
sg_rule['to_port'] = to_p
else:
raise exc.HTTPBadRequest()
if from_p > to_p:
raise exc.HTTPBadRequest()
cidr = entity.attributes['occi.network.security.range'].strip()
if len(cidr) <= 0:
cidr = '0.0.0.0/0'
if utils.is_valid_cidr(cidr):
sg_rule['cidr'] = cidr
else:
raise exc.HTTPBadRequest()
sg_rule['group'] = {}
return sg_rule
def _get_sec_group(self, extras, sec_mixin):
"""
Retreive the security group associated with the security mixin.
"""
try:
sec_group = db.security_group_get_by_name(extras['nova_ctx'],
extras['nova_ctx'].project_id, sec_mixin.term)
except Exception:
# ensure that an OpenStack sec group matches the mixin
# if not, create one.
# This has to be done as pyssf has no way to associate
# a handler for the creation of mixins at the query interface
msg = _('Security group does not exist.')
LOG.error(msg)
raise exc.HTTPBadRequest()
return sec_group
def _get_sec_mixin(self, entity):
"""
Get the security mixin of the supplied entity.
"""
sec_mixin_present = 0
sec_mixin = None
for mixin in entity.mixins:
if SEC_GROUP in mixin.related:
sec_mixin = mixin
sec_mixin_present = sec_mixin_present + 1
if not sec_mixin_present:
# no mixin of the type security group was found
msg = _('No security group mixin was found')
LOG.error(msg)
raise exc.HTTPBadRequest()
if sec_mixin_present > 1:
msg = _('More than one security group mixin was found')
LOG.error(msg)
raise exc.HTTPBadRequest()
return sec_mixin
def _security_group_rule_exists(self, security_group, values):
"""
Indicates whether the specified rule values are already
defined in the given security group.
"""
# Taken directly from security_groups.py as that method is not
# directly import-able.
for rule in security_group['rules']:
is_duplicate = True
keys = ('group_id', 'cidr', 'from_port', 'to_port', 'protocol')
for key in keys:
if rule.get(key) != values.get(key):
is_duplicate = False
break
if is_duplicate:
return True
return False
def delete(self, entity, extras):
"""
Deletes the security rule.
"""
msg = _('Deleting a network security rule')
LOG.info(msg)
self.compute_api.ensure_default_security_group(extras['nova_ctx'])
try:
rule = db.security_group_rule_get(extras['nova_ctx'],
int(entity.attributes['occi.core.id']))
except Exception:
raise exc.HTTPNotFound()
group_id = rule['parent_group_id']
self.compute_api.ensure_default_security_group(extras['nova_ctx'])
security_group = db.security_group_get(extras['nova_ctx'], group_id)
db.security_group_rule_destroy(extras['nova_ctx'], rule['id'])
self.sgh.trigger_security_group_rule_destroy_refresh(
extras['nova_ctx'], [rule['id']])
self.compute_api.trigger_security_group_rules_refresh(
extras['nova_ctx'],
security_group['id'])

286
api/extensions/openstack.py Normal file
View File

@@ -0,0 +1,286 @@
# 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.
from occi import backend
from occi import core_model
from webob import exc
from nova import compute
from nova import exception
from nova import logging
from nova.network import api as net_api
# Hi I'm a logger, use me! :-)
LOG = logging.getLogger('nova.api.occi.backends.compute.os')
######################## OpenStack Specific Addtitions #######################
######### 1. define the method to retreive all extension information #########
def get_extensions():
return [
{
'categories': [OS_CHG_PWD, OS_REVERT_RESIZE,
OS_CONFIRM_RESIZE, OS_CREATE_IMAGE,
OS_ALLOC_FLOATING_IP, OS_DEALLOC_FLOATING_IP, ],
'handler': OsComputeActionBackend(),
},
{
'categories': [OS_KEY_PAIR_EXT, OS_ADMIN_PWD_EXT,
OS_ACCESS_IP_EXT, OS_FLOATING_IP_EXT, ],
'handler': backend.MixinBackend(),
},
]
##### 2. define the extension categories - OpenStack Specific Additions ######
# OS change adminstrative password action
_OS_CHG_PWD_ATTRIBUTES = {'org.openstack.credentials.admin_pwd': '', }
OS_CHG_PWD = core_model.Action(
'http://schemas.openstack.org/instance/action#',
'chg_pwd', 'Removes all data on the server and replaces'
'it with the specified image (via Mixin).',
_OS_CHG_PWD_ATTRIBUTES)
# OS revert a resized VM action
OS_REVERT_RESIZE = core_model.Action(
'http://schemas.openstack.org/instance/action#',
'revert_resize', 'Revert the compute resize and roll back.')
# OS confirm a resized VM action
OS_CONFIRM_RESIZE = core_model.Action(
'http://schemas.openstack.org/instance/action#',
'confirm_resize', 'Confirms the resize action.')
# OS create image from VM action
_OS_CREATE_IMAGE_ATTRIBUTES = {'org.openstack.snapshot.image_name': '', }
OS_CREATE_IMAGE = core_model.Action(
'http://schemas.openstack.org/instance/action#',
'create_image', 'Creates a new image for the given server.',
_OS_CREATE_IMAGE_ATTRIBUTES)
# OS Key pair extension
_OS_KEY_PAIR_ATTRIBUTES = {'org.openstack.credentials.publickey.name': '',
'org.openstack.credentials.publickey.data': '', }
OS_KEY_PAIR_EXT = core_model.Mixin(
'http://schemas.openstack.org/instance/credentials#',
'public_key', attributes=_OS_KEY_PAIR_ATTRIBUTES)
# OS VM Administrative password extension
_OS_ADMIN_PWD_ATTRIBUTES = {'org.openstack.credentials.admin_pwd': '', }
OS_ADMIN_PWD_EXT = core_model.Mixin(
'http://schemas.openstack.org/instance/credentials#',
'admin_pwd', attributes=_OS_ADMIN_PWD_ATTRIBUTES)
# OS access IP extension
_OS_ACCESS_IP_ATTRIBUTES = {'org.openstack.network.access.ip': '',
'org.openstack.network.access.version': ''}
OS_ACCESS_IP_EXT = core_model.Mixin(
'http://schemas.openstack.org/instance/network#',
'access_ip', attributes=_OS_ACCESS_IP_ATTRIBUTES)
# OS floating IP allocation action
# expected parameter is the floating IP pool to take the IP from
OS_ALLOC_FLOATING_IP = core_model.Action(
'http://schemas.openstack.org/instance/action#',
'alloc_float_ip', 'Allocate a floating IP to the '
'compute resource.')
# OS floating IP deallocation action
OS_DEALLOC_FLOATING_IP = core_model.Action(
'http://schemas.openstack.org/instance/action#',
'dealloc_float_ip', 'Deallocate a floating IP from the '
'compute resource.')
# OS floating IP extension
_OS_FLOATING_IP_ATTRIBUTES = {'org.openstack.network.floating.ip': '', }
OS_FLOATING_IP_EXT = core_model.Mixin(
'http://schemas.openstack.org/instance/network#',
'floating_ip', attributes=_OS_FLOATING_IP_ATTRIBUTES)
##################### 3. define the extension handler(s) #####################
class OsComputeActionBackend(backend.ActionBackend):
def __init__(self):
super(OsComputeActionBackend, self).__init__()
self.compute_api = compute.API()
self.network_api = net_api.API()
def action(self, entity, action, attributes, extras):
"""
This is called by pyssf when an action request is issued.
"""
context = extras['nova_ctx']
try:
instance = self.compute_api.get(context,
entity.attributes['occi.core.id'])
except exception.NotFound:
raise exc.HTTPNotFound()
if action == OS_CHG_PWD:
self._os_chg_passwd_vm(entity, attributes, instance, context)
elif action == OS_REVERT_RESIZE:
self._os_revert_resize_vm(entity, instance, context)
elif action == OS_CONFIRM_RESIZE:
self._os_confirm_resize_vm(entity, instance, context)
elif action == OS_CREATE_IMAGE:
self._os_create_image(entity, attributes, instance, context)
elif action == OS_ALLOC_FLOATING_IP:
self._os_allocate_floating_ip(entity, attributes, instance,
context)
elif action == OS_DEALLOC_FLOATING_IP:
self._os_deallocate_floating_ip(entity, context)
else:
raise exc.HTTPBadRequest()
def _os_chg_passwd_vm(self, entity, attributes, instance, context):
"""
Implements changing of a vm's admin password
"""
# Use the password extension?
msg = _('Changing admin password of virtual machine with id %s') % \
entity.identifier
LOG.info(msg)
if 'org.openstack.credentials.admin_pwd' not in attributes:
msg = _("'org.openstack.credentials.admin_pwd' "
"was not supplied in the request.")
LOG.error(msg)
exc.HTTPBadRequest()
new_password = attributes['org.openstack.credentials.admin_pwd']
self.compute_api.set_admin_password(context, instance, new_password)
# No need to update attributes - state remains the same.
def _os_revert_resize_vm(self, entity, instance, context):
"""
Implements reverting of a resized vm
"""
msg = _('Reverting resized virtual machine with id %s') % \
entity.identifier
LOG.info(msg)
try:
self.compute_api.revert_resize(context, instance)
except exception.MigrationNotFound:
msg = _("Instance has not been resized.")
raise exc.HTTPBadRequest(explanation=msg)
except exception.InstanceInvalidState:
exc.HTTPConflict()
except Exception:
msg = _('Error in revert-resize.')
LOG.error(msg)
raise exc.HTTPBadRequest()
entity.attributes['occi.compute.state'] = 'inactive'
def _os_confirm_resize_vm(self, entity, instance, context):
"""
Implements the confirmation of a resized vm.
"""
msg = _('Confirming resize of virtual machine with id %s') % \
entity.identifier
LOG.info(msg)
try:
self.compute_api.confirm_resize(context, instance)
except exception.MigrationNotFound:
msg = _("Instance has not been resized.")
raise exc.HTTPBadRequest(explanation=msg)
except exception.InstanceInvalidState:
exc.HTTPConflict()
except Exception:
msg = _('Error in confirm-resize.')
LOG.error(msg)
raise exc.HTTPBadRequest()
entity.attributes['occi.compute.state'] = 'active'
def _os_create_image(self, entity, attributes, instance, context):
"""
implements the creation of an image of the specified vm
"""
msg = _('Creating image from virtual machine with id %s') % \
entity.identifier
LOG.info(msg)
if 'org.openstack.snapshot.image_name' not in attributes:
exc.HTTPBadRequest()
image_name = attributes['org.openstack.snapshot.image_name']
props = {}
try:
self.compute_api.snapshot(context,
instance,
image_name,
extra_properties=props)
except exception.InstanceInvalidState:
exc.HTTPConflict()
def _os_allocate_floating_ip(self, entity, attributes, instance, context):
"""
This allocates a floating ip from the supplied floating ip pool. The
pool is specified as an optional parameter in the action request.
Currently, this only supports the assignment of 1 floating IP.
"""
for mixin in entity.mixins:
if (mixin.scheme + mixin.term) == OS_FLOATING_IP_EXT.scheme + \
OS_FLOATING_IP_EXT.term:
#TODO(dizz): implement support for multiple floating ips
# needs support in pyssf for URI in link
msg = _('There is already a floating IP assigned to the VM')
LOG.error(msg)
exc.HTTPBadRequest(explanation=msg)
if 'org.openstack.network.floating.pool' not in attributes:
pool = None
else:
pool = attributes['org.openstack.network.floating.pool']
address = self.network_api.allocate_floating_ip(context, pool)
self.compute_api.associate_floating_ip(context, instance, address)
# once the address is allocated we need to reflect that fact
# on the resource holding it.
entity.mixins.append(OS_FLOATING_IP_EXT)
entity.attributes['org.openstack.network.floating.ip'] = address
def _os_deallocate_floating_ip(self, entity, context):
"""
This deallocates a floating ip from the compute resource.
This returns the deallocated IP address to the pool.
"""
address = entity.attributes['org.openstack.network.floating.ip']
self.network_api.disassociate_floating_ip(context, address)
self.network_api.release_floating_ip(context, address)
# remove the mixin
for mixin in entity.mixins:
if (mixin.scheme + mixin.term) == OS_FLOATING_IP_EXT.scheme + \
OS_FLOATING_IP_EXT.term:
entity.mixins.remove(mixin)
entity.attributes.pop('org.openstack.network.floating.ip')

16
api/network/__init__.py Normal file
View File

@@ -0,0 +1,16 @@
# 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.

View File

@@ -0,0 +1,90 @@
# 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.
from occi import backend
from webob import exc
from nova import log as logging
# With Quantum:
# TODO(dizz): implement create - note: this must handle either
# nova-network or quantum APIs - detect via flags and
# secondarily via import exceptions
# implement delete
# implement update
# Also see nova/api/openstack/compute/contrib/multinic.py
#Hi I'm a logger, use me! :-)
LOG = logging.getLogger('nova.api.occi.backends.network.link')
class NetworkInterfaceBackend(backend.KindBackend):
"""
A backend for network links.
"""
def create(self, link, extras):
"""
As nova does not support creation of L2 networks we don't.
"""
# implement with Quantum
raise exc.HTTPBadRequest()
def update(self, old, new, extras):
"""
Allows for the update of network links.
"""
#L8R: here we associate a security group
#L8R: here we could possibly assign a static (floating) ip - request
# must include a ipnetworkinterface mixin
# make sure the link has an IP mixin
# get a reference to the compute instance
# get the security group
# associate the security group with the compute instance
raise exc.HTTPBadRequest()
def delete(self, link, extras):
"""
no-op
"""
pass
class IpNetworkInterfaceBackend(backend.MixinBackend):
"""
A mixin backend for the IpNetworkingInterface.
"""
def create(self, link, extras):
"""
Can't create in nova so we don't either.
"""
raise exc.HTTPBadRequest()
def delete(self, entity, extras):
"""
no-op
"""
pass
def action(self):
"""
no-op
"""
pass

View File

@@ -0,0 +1,62 @@
# 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.
# TODO(dizz): implement create
# implement delete
# implement retreive
# implement actions
# implement updates
# Also see nova/api/openstack/compute/contrib/networks.py
from occi import backend
from webob import exc
from nova import flags
from nova import log as logging
#Hi I'm a logger, use me! :-)
LOG = logging.getLogger('nova.api.occi.backends.network')
FLAGS = flags.FLAGS
class NetworkBackend(backend.KindBackend, backend.ActionBackend):
"""
Backend to handle network resources.
"""
def create(self, entity, extras):
raise exc.HTTPBadRequest()
def delete(self, entity, extras):
pass
def action(self, entity, action, extras):
raise exc.HTTPBadRequest()
class IpNetworkBackend(backend.MixinBackend):
"""
A mixin backend for the IPnetworking.
"""
def create(self, entity, extras):
raise exc.HTTPBadRequest()
def delete(self, entity, extras):
pass

69
api/registry.py Normal file
View File

@@ -0,0 +1,69 @@
# 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.
from occi import registry
from nova.api.occi.extensions import occi_future
class OCCIRegistry(registry.NonePersistentRegistry):
"""
Registry for OpenStack.
"""
def get_extras(self, extras):
sec_extras = None
if extras != None:
sec_extras = {}
sec_extras['user_id'] = extras['nova_ctx'].user_id
sec_extras['project_id'] = extras['nova_ctx'].project_id
return sec_extras
def add_resource(self, key, resource, extras):
"""
Ensures OpenStack keys are used as resource identifiers and sets
user id and tenant id
"""
key = resource.kind.location + resource.attributes['occi.core.id']
resource.identifier = key
super(OCCIRegistry, self).add_resource(key, resource, extras)
def delete_mixin(self, mixin, extras):
"""
Allows for the deletion of user defined mixins.
If the mixin is a security group mixin then that mixin's
backend is called.
"""
if (hasattr(mixin, 'related') and
occi_future.SEC_GROUP in mixin.related):
be = self.get_backend(mixin, extras)
be.destroy(mixin, extras)
super(OCCIRegistry, self).delete_mixin(mixin, extras)
def set_backend(self, category, backend, extras):
"""
Assigns user id and tenant id to user defined mixins
"""
if (hasattr(category, 'related') and
occi_future.SEC_GROUP in category.related):
be = occi_future.SecurityGroupBackend()
backend = be
be.init_sec_group(category, extras)
super(OCCIRegistry, self).set_backend(category, backend, extras)

16
api/storage/__init__.py Normal file
View File

@@ -0,0 +1,16 @@
# 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.

110
api/storage/storagelink.py Normal file
View File

@@ -0,0 +1,110 @@
# 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.
import uuid
from occi import backend
from occi.extensions import infrastructure
from webob import exc
from nova import compute
from nova import log as logging
from nova import volume
#Hi I'm a logger, use me! :-)
LOG = logging.getLogger('nova.api.occi.backends.storage.link')
class StorageLinkBackend(backend.KindBackend):
"""
A backend for the storage links.
"""
def __init__(self):
super(StorageLinkBackend, self).__init__()
self.volume_api = volume.API()
self.compute_api = compute.API()
def create(self, link, extras):
"""
Creates a link from a compute instance to a storage volume.
The user must specify what the device id is to be.
"""
msg = _('Linking compute to storage via StorageLink.')
LOG.info(msg)
inst_to_attach = self._get_inst_to_attach(extras['nova_ctx'], link)
vol_to_attach = self._get_vol_to_attach(extras['nova_ctx'], link)
self.compute_api.attach_volume(
extras['nova_ctx'],
inst_to_attach,
vol_to_attach['id'],
link.attributes['occi.storagelink.deviceid'])
link.attributes['occi.core.id'] = str(uuid.uuid4())
link.attributes['occi.storagelink.deviceid'] = \
link.attributes['occi.storagelink.deviceid']
link.attributes['occi.storagelink.mountpoint'] = ''
link.attributes['occi.storagelink.state'] = 'active'
def _get_inst_to_attach(self, context, link):
"""
Gets the compute instance that is to have the storage attached.
"""
if link.target.kind == infrastructure.COMPUTE:
instance = self.compute_api.get(context,
link.target.attributes['occi.core.id'])
elif link.source.kind == infrastructure.COMPUTE:
instance = self.compute_api.get(context,
link.source.attributes['occi.core.id'])
else:
raise exc.HTTPBadRequest()
return instance
def _get_vol_to_attach(self, context, link):
"""
Gets the storage instance that is to have the compute attached.
"""
if link.target.kind == infrastructure.STORAGE:
vol_to_attach = self.volume_api.get(context,
link.target.attributes['occi.core.id'])
elif link.source.kind == infrastructure.STORAGE:
vol_to_attach = self.volume_api.get(context,
link.source.attributes['occi.core.id'])
else:
raise exc.HTTPBadRequest()
return vol_to_attach
def delete(self, link, extras):
"""
Unlinks the the compute from the storage resource.
"""
msg = _('Unlinking entity from storage via StorageLink.')
LOG.info(msg)
try:
vol_to_detach = self._get_vol_to_attach(extras['nova_ctx'], link)
self.compute_api.detach_volume(extras['nova_ctx'],
vol_to_detach['id'])
except Exception, e:
msg = _('Error in detaching storage volume.')
LOG.error(msg)
raise e

View File

@@ -0,0 +1,244 @@
# 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.
import random
from occi import backend
from occi.extensions import infrastructure
from webob import exc
from nova import exception
from nova import log as logging
from nova import volume
#Hi I'm a logger, use me! :-)
LOG = logging.getLogger('nova.api.occi.backends.storage')
class StorageBackend(backend.KindBackend, backend.ActionBackend):
"""
Backend to handle storage resources.
"""
def __init__(self):
super(StorageBackend, self).__init__()
self.volume_api = volume.API()
def create(self, resource, extras):
"""
Creates a new volume.
"""
if 'occi.storage.size' not in resource.attributes:
exc.HTTPBadRequest()
size = float(resource.attributes['occi.storage.size'])
# TODO(dizz): A blueprint?
# OpenStack deals with size in terms of integer.
# Need to convert float to integer for now and only if the float
# can be losslessly converted to integer
# e.g. See nova/quota.py:allowed_volumes(...)
if not size.is_integer:
msg = _('Volume sizes cannot be specified as fractional floats.')
LOG.error(msg)
raise exc.HTTPBadRequest()
size = str(int(size))
msg = _("Creating volume of %s GB") % size
LOG.info(msg)
disp_name = ''
try:
disp_name = resource.attributes['occi.core.title']
except KeyError:
#Generate more suitable name as it's used for hostname
#where no hostname is supplied.
disp_name = resource.attributes['occi.core.title'] = \
str(random.randrange(0, 99999999)) + \
'-storage.occi-wg.org'
if 'occi.core.summary' in resource.attributes:
disp_descr = resource.attributes['occi.core.summary']
else:
disp_descr = disp_name
snapshot = None
# volume_type can be specified by mixin
volume_type = None
metadata = None
avail_zone = None
new_volume = self.volume_api.create(extras['nova_ctx'],
size,
disp_name,
disp_descr,
snapshot=snapshot,
volume_type=volume_type,
metadata=metadata,
availability_zone=avail_zone)
# Work around problem that instance is lazy-loaded...
new_volume = self.volume_api.get(extras['nova_ctx'], new_volume['id'])
if new_volume['status'] == 'error':
msg = _('There was an error creating the volume')
LOG.error(msg)
raise exc.HTTPServerError(msg)
resource.attributes['occi.core.id'] = str(new_volume['id'])
if new_volume['status'] == 'available':
resource.attributes['occi.storage.state'] = 'online'
resource.actions = [infrastructure.OFFLINE, infrastructure.BACKUP,
infrastructure.SNAPSHOT, infrastructure.RESIZE]
def retrieve(self, entity, extras):
"""
Gets a representation of the storage volume and presents it ready for
rendering by pyssf.
"""
v_id = int(entity.attributes['occi.core.id'])
try:
vol = self.volume_api.get(extras['nova_ctx'], v_id)
except exception.NotFound:
raise exc.HTTPNotFound()
entity.attributes['occi.storage.size'] = str(float(vol['size']))
# OS volume states:
# available, creating, deleting, in-use, error, error_deleting
if vol['status'] == 'available' or vol['status'] == 'in-use':
entity.attributes['occi.storage.state'] = 'online'
entity.actions = [infrastructure.OFFLINE, infrastructure.BACKUP,
infrastructure.SNAPSHOT, infrastructure.RESIZE]
def delete(self, entity, extras):
"""
Deletes the storage resource
"""
msg = _('Removing storage device with id: %s') % entity.identifier
LOG.info(msg)
volume_id = int(entity.attributes['occi.core.id'])
try:
vol = self.volume_api.get(extras['nova_ctx'], volume_id)
self.volume_api.delete(extras['nova_ctx'], vol)
except exception.NotFound:
raise exc.HTTPNotFound()
def action(self, entity, action, attributes, extras):
"""
Executes actions against the target storage resource.
"""
if action not in entity.actions:
raise AttributeError("This action is currently no applicable.")
elif action == infrastructure.ONLINE:
# ONLINE, ready for service, default state of a created volume.
# could this cover the attach functionality in storage link?
# The following is not an approach to use:
# self.volume_api.initialize_connection(context, volume, connector)
# By default storage is ONLINE and can not be brought OFFLINE
msg = _('Online storage action requested resource with id: %s') % \
entity.identifier
LOG.warn(msg)
raise exc.HTTPBadRequest()
elif action == infrastructure.OFFLINE:
# OFFLINE, disconnected? disconnection supported in API otherwise
# not. The following is not an approach to use:
# self.volume_api.terminate_connection(context, volume, connector)
# By default storage cannot be brought OFFLINE
msg = _('Offline storage action requested for resource: %s') % \
entity.identifier
LOG.warn(msg)
raise exc.HTTPBadRequest()
elif action == infrastructure.BACKUP:
# BACKUP: create a complete copy of the volume.
msg = _('Backup action for storage resource with id: %s') % \
entity.identifier
LOG.warn(msg)
raise exc.HTTPBadRequest()
elif action == infrastructure.SNAPSHOT:
# CDMI?!
# SNAPSHOT: create a time-stamped copy of the volume? Supported in
# OS volume API
self._snapshot_storage(entity, extras)
elif action == infrastructure.RESIZE:
# TODO(dizz): not supported by API. A blueprint candidate?
# RESIZE: increase, decrease size of volume. Not supported directly
# by the API
msg = _('Resize storage actio requested resource with id: %s') % \
entity.identifier
LOG.warn(msg)
raise exc.HTTPNotImplemented()
def _snapshot_storage(self, entity, extras, backup=False):
"""
Takes a snapshot of the specified storage resource
"""
msg = _('Snapshoting storage resource with id: %s') % entity.identifier
LOG.info(msg)
volume_id = int(entity.attributes['occi.core.id'])
vol = self.volume_api.get(extras['nova_ctx'], volume_id)
#TODO(dizz): these names/descriptions should be made better.
if backup:
name = 'backup name'
description = 'backup description'
else:
# occi.core.title, occi.core.summary
name = 'snapshot name'
description = 'snapshot description'
self.volume_api.create_snapshot(extras['nova_ctx'],
vol, name, description)
def update(self, old, new, extras):
"""
Updates simple attributes of a storage resource:
occi.core.title, occi.core.summary
"""
# update attributes.
if len(new.attributes) > 0:
msg = _('Updating mutable attributes of volume instance')
LOG.info(msg)
# support only title and summary changes now.
if (('occi.core.title' in new.attributes)
or ('occi.core.title' in new.attributes)):
if len(new.attributes['occi.core.title']) > 0:
old.attributes['occi.core.title'] = \
new.attributes['occi.core.title']
if len(new.attributes['occi.core.summary']) > 0:
old.attributes['occi.core.summary'] = \
new.attributes['occi.core.summary']
else:
msg = _('Cannot update the supplied attributes.')
LOG.error()
raise exc.HTTPBadRequest()
else:
raise exc.HTTPBadRequest()

363
api/wsgi.py Normal file
View File

@@ -0,0 +1,363 @@
# 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.
from occi import backend
from occi import core_model
from occi.extensions import infrastructure
from occi import wsgi as occi_wsgi
# TODO(tmetsch): cleanup!
from api.compute import computeresource
from api.compute import templates
import extensions
from api.extensions import occi_future
from api.network import networklink
from api.network import networkresource
import registry
from api.storage import storagelink
from api.storage import storageresource
from nova.compute import instance_types
from nova import context
from nova import db
from nova import flags
from nova import image
from nova import log
from nova.network import api as net_api
from nova.openstack.common import cfg
from nova import wsgi
#Hi I'm a logger, use me ! :-)
LOG = log.getLogger('nova.api.occi.wsgi')
#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."),
]
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 = computeresource.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 is a list of hashmaps. The hashmap contains the handler.
#The hashmap contains a list of categories to be handled by the handler
for extn in extensions.EXTENSIONS:
#miaow! kittens, kittens, kittens!
for ext in extn:
for cat in ext['categories']:
self.register_backend(cat, ext['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_ip_pool_mixins(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'
msg = _('Registering default network with web app.')
LOG.info(msg)
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 = self._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 _get_net_info(self, net_attrs):
"""
Gets basic information about the default network.
"""
ctx = context.get_admin_context()
network_api = net_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'] = networks[0]['gateway'],
net_attrs['occi.network.allocation'] = 'dhcp'
return net_attrs
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 = image.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=self._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 _get_resource_attributes(self, 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
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_ip_pool_mixins(self, extras):
"""
Gets the list of floating ip pools and registers them as mixins.
"""
network_api = net_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)

View File

@@ -1,32 +0,0 @@
# coding=utf-8
# 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.
'''
TODO(tmetsch): app will be replace by OCCI OS app.
'''
def simple_app(environ, start_response):
'''
Sample WSGI app.
'''
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return ['Hello world!\n',]

View File

@@ -45,7 +45,7 @@ setup(
url='http://intel.com',
license='Apache License, Version 2.0',
include_package_data=True,
packages=['api',],
packages=['api','api.compute','api.network','api.storage','api.extensions'],
zip_safe=False,
install_requires=[
'setuptools',