occi-os/occi_os_api/nova_glue/vm.py

449 lines
13 KiB
Python

# 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.
"""
VM related 'glue' :-)
"""
#pylint: disable=R0914,W0142,R0912,R0915
from nova import compute, volume
from nova import exception
from nova import utils
from nova.compute import vm_states
from nova.compute import task_states
from nova.compute import instance_types
from nova.exception import InstancePasswordSetFailed
from nova.flags import FLAGS
from occi import exceptions
from occi.extensions import infrastructure
from occi_os_api.extensions import os_mixins
from occi_os_api.extensions import os_addon
import logging
# Connection to the nova APIs
COMPUTE_API = compute.API()
VOLUME_API = volume.API()
LOG = logging.getLogger(__name__)
def create_vm(entity, context):
"""
Create a VM for an given OCCI entity.
entity -- the OCCI resource entity.
context -- the os context.
"""
if 'occi.compute.hostname' in entity.attributes:
name = entity.attributes['occi.compute.hostname']
else:
name = None
key_name = key_data = None
password = utils.generate_password(FLAGS.password_length)
access_ip_v4 = None
access_ip_v6 = None
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
kernel_id = ramdisk_id = None
auto_disk_config = None
scheduler_hints = None
resource_template = None
os_template = None
for mixin in entity.mixins:
if isinstance(mixin, os_mixins.ResourceTemplate):
resource_template = mixin
elif isinstance(mixin, os_mixins.OsTemplate):
os_template = mixin
# Look for security group. If the group is non-existant, the
# call to create will fail.
if os_addon.SEC_GROUP in mixin.related:
sg_names.append(mixin.term)
if not os_template:
raise AttributeError('Please provide a valid OS Template.')
if resource_template:
inst_type = compute.instance_types.\
get_instance_type_by_name(resource_template.term)
else:
inst_type = compute.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)
# make the call
try:
(instances, _reservation_id) = COMPUTE_API.create(
context=context,
instance_type=inst_type,
image_href=os_template.os_id,
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 as error:
raise AttributeError(str(error))
# return first instance
return instances[0]
def rebuild_vm(uid, image_href, context):
"""
Rebuilds the specified VM with the supplied OsTemplate mixin.
uid -- id of the instance
image_href -- image reference.
context -- the os context
"""
instance = get_vm(uid, context)
admin_password = utils.generate_password(FLAGS.password_length)
kwargs = {}
try:
COMPUTE_API.rebuild(context, instance, image_href, admin_password,
**kwargs)
except exception.InstanceInvalidState:
raise AttributeError('VM is in an invalid state.')
except exception.ImageNotFound:
raise AttributeError('Cannot find image for rebuild')
def resize_vm(uid, flavor_name, context):
"""
Resizes a VM up or down
Update: libvirt now supports resize see:
http://wiki.openstack.org/HypervisorSupportMatrix
uid -- id of the instance
flavor_name -- image reference.
context -- the os context
"""
instance = get_vm(uid, context)
kwargs = {}
try:
flavor = instance_types.get_instance_type_by_name(flavor_name)
COMPUTE_API.resize(context, instance, flavor_id=flavor['flavorid'],
**kwargs)
ready = False
i = 0
while i < 15:
i += 1
state = get_vm(uid, context)['vm_state']
if state == 'resized':
ready = True
import time
time.sleep(1)
instance = get_vm(uid, context)
COMPUTE_API.confirm_resize(context, instance)
except exception.FlavorNotFound:
raise AttributeError('Unable to locate requested flavor.')
except exception.InstanceInvalidState as error:
raise AttributeError('VM is in an invalid state: ' + str(error))
def delete_vm(uid, context):
"""
Destroy a VM.
uid -- id of the instance
context -- the os context
"""
instance = get_vm(uid, context)
if FLAGS.reclaim_instance_interval:
COMPUTE_API.soft_delete(context, instance)
else:
COMPUTE_API.delete(context, instance)
def suspend_vm(uid, context):
"""
Suspends a VM. Use the start action to unsuspend a VM.
uid -- id of the instance
context -- the os context
"""
instance = get_vm(uid, context)
try:
COMPUTE_API.pause(context, instance)
except Exception as error:
raise exceptions.HTTPError(500, str(error))
def snapshot_vm(uid, image_name, context):
"""
Snapshots a VM. Use the start action to unsuspend a VM.
uid -- id of the instance
image_name -- name of the new image
context -- the os context
"""
instance = get_vm(uid, context)
try:
COMPUTE_API.snapshot(context,
instance,
image_name)
except exception.InstanceInvalidState:
raise AttributeError('VM is not in an valid state.')
def start_vm(uid, 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.
uid -- id of the instance
state -- the state the VM is in (str)
context -- the os context
"""
instance = get_vm(uid, context)
try:
COMPUTE_API.resume(context, instance)
except Exception as error:
raise exceptions.HTTPError(500, 'Error while starting VM: ' + str
(error))
def stop_vm(uid, context):
"""
Stops a VM. Rather than use stop, suspend is used.
OCCI -> graceful, acpioff, poweroff
OS -> unclear
uid -- id of the instance
context -- the os context
"""
instance = get_vm(uid, context)
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)
COMPUTE_API.suspend(context, instance)
except Exception as error:
raise exceptions.HTTPError(500, 'Error while stopping VM: ' + str
(error))
def restart_vm(uid, method, context):
"""
Restarts a VM.
OS types == SOFT, HARD
OCCI -> graceful, warm and cold
mapping:
- SOFT -> graceful, warm
- HARD -> cold
uid -- id of the instance
method -- how the machine should be restarted.
context -- the os context
"""
instance = get_vm(uid, context)
if method in ('graceful', 'warm'):
reboot_type = 'SOFT'
elif method is 'cold':
reboot_type = 'HARD'
else:
raise AttributeError('Unknown method.')
try:
COMPUTE_API.reboot(context, instance, reboot_type)
except exception.InstanceInvalidState:
raise exceptions.HTTPError(406, 'VM is in an invalid state.')
except Exception as error:
raise exceptions.HTTPError(500, 'Error in reboot %s' % error)
def attach_volume(instance_id, volume_id, mount_point, context):
"""
Attaches a storage volume.
instance_id -- Id of the VM.
volume_id -- Id of the storage volume.
mount_point -- Where to mount.
context -- The os security context.
"""
# TODO: check exception handling!
instance = get_vm(instance_id, context)
try:
vol_instance = VOLUME_API.get(context, volume_id)
except exception.NotFound:
raise exceptions.HTTPError(404, 'Volume not found!')
volume_id = vol_instance['id']
try:
COMPUTE_API.attach_volume(
context,
instance,
volume_id,
mount_point)
except Exception as error:
LOG.error(str(error))
raise error
def detach_volume(volume_id, context):
"""
Detach a storage volume.
volume_id -- Id of the volume.
context -- the os context.
"""
#try:
# instance = VOLUME_API.get(context, volume_id)
#except exception.NotFound:
# raise exceptions.HTTPError(404, 'Volume not found!')
#volume_id = instance['id']
try:
#TODO(dizz): see issue #15
COMPUTE_API.detach_volume(context, volume_id)
except Exception as error:
LOG.error(str(error) + '; with id: ' + volume_id)
raise error
def set_password_for_vm(uid, password, context):
"""
Set new password for an VM.
uid -- Id of the instance.
password -- The new password.
context -- The os context.
"""
instance = get_vm(uid, context)
try:
COMPUTE_API.set_admin_password(context, instance, password)
except InstancePasswordSetFailed as error:
LOG.warn('Unable to set password - driver might not support it! ' +
str(error))
def get_vnc(uid, context):
"""
Retrieve VNC console.
uid -- id of the instance
context -- the os context
"""
instance = get_vm(uid, context)
try:
console = COMPUTE_API.get_vnc_console(context, instance, 'novnc')
except exception.InstanceNotFound:
LOG.warn('Console info is not available atm!')
return None
return console
def get_vm(uid, context):
"""
Retrieve an VM instance from nova.
uid -- id of the instance
context -- the os context
"""
try:
instance = COMPUTE_API.get(context, uid)
except exception.NotFound:
raise exceptions.HTTPError(404, 'VM not found!')
return instance
def get_vms(context):
"""
Retrieve all VMs in a given context.
"""
opts = {'deleted': False}
tmp = COMPUTE_API.get_all(context, search_opts=opts)
return tmp
def get_occi_state(uid, context):
"""
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
uid -- Id of the VM.
context -- the os context.
"""
instance = get_vm(uid, context)
state = 'inactive'
actions = []
if instance['vm_state'] in [vm_states.ACTIVE]:
state = 'active'
actions.append(infrastructure.STOP)
actions.append(infrastructure.SUSPEND)
actions.append(infrastructure.RESTART)
elif instance['vm_state'] in [vm_states.BUILDING]:
state = 'inactive'
elif instance['vm_state'] in [vm_states.PAUSED, vm_states.SUSPENDED,
vm_states.STOPPED]:
state = 'inactive'
actions.append(infrastructure.START)
elif instance['vm_state'] in [vm_states.RESCUED,
vm_states.ERROR,
vm_states.DELETED]:
state = 'inactive'
# Some task states require a state
# TODO: check for others!
if instance['vm_state'] in [task_states.IMAGE_SNAPSHOT]:
state = 'inactive'
actions = []
return state, actions