added OCCI OS api
This commit is contained in:
@@ -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
16
api/compute/__init__.py
Normal 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.
|
||||
816
api/compute/computeresource.py
Normal file
816
api/compute/computeresource.py
Normal 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
46
api/compute/templates.py
Normal 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
35
api/extensions/README.rst
Normal 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``.
|
||||
50
api/extensions/__init__.py
Normal file
50
api/extensions/__init__.py
Normal 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
36
api/extensions/fiware.py
Normal 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)
|
||||
333
api/extensions/occi_future.py
Normal file
333
api/extensions/occi_future.py
Normal 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
286
api/extensions/openstack.py
Normal 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
16
api/network/__init__.py
Normal 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.
|
||||
90
api/network/networklink.py
Normal file
90
api/network/networklink.py
Normal 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
|
||||
62
api/network/networkresource.py
Normal file
62
api/network/networkresource.py
Normal 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
69
api/registry.py
Normal 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
16
api/storage/__init__.py
Normal 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
110
api/storage/storagelink.py
Normal 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
|
||||
244
api/storage/storageresource.py
Normal file
244
api/storage/storageresource.py
Normal 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
363
api/wsgi.py
Normal 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)
|
||||
@@ -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',]
|
||||
Reference in New Issue
Block a user