In preparation for XenAPI support, refactor the interface between
nova.compute and the hypervisor (i.e. libvirt). compute.node is no longer coupled tightly with libvirt. Instead, hypervisor connections are handled through a simple abstract interface. This has the additional advantage that there is no need to riddle the code with FLAGS.fake_libvirt checks, as we now have an interface behind which we can mock. The libvirt-specific code, and the fakevirt code used for unit tests, have moved into nova.virt. The fake_libvirt flag has been replaced with a connection_type flag, that will allow us to specify different connection types. The disk image handling (S3 or local disk image fetch) has moved into nova.virt.images, where it will be easier to share between connection types. The power_state values (Instance.RUNNING etc) and the INSTANCE_TYPES dictionary have moved into their own files (nova.compute.instance_types and nova.compute.power_state) so that we can share them without mutual dependencies between nova.compute.node and nova.virt.libvirt_conn.
This commit is contained in:
parent
d5309eff30
commit
f39d6549d4
@ -71,7 +71,7 @@ def main(argv=None):
|
||||
FLAGS.fake_rabbit = True
|
||||
FLAGS.redis_db = 8
|
||||
FLAGS.network_size = 32
|
||||
FLAGS.fake_libvirt=True
|
||||
FLAGS.connection_type = 'fake'
|
||||
FLAGS.fake_network=True
|
||||
FLAGS.fake_users = True
|
||||
action = argv[1]
|
||||
|
@ -18,10 +18,10 @@
|
||||
Nova Fakes
|
||||
==========
|
||||
|
||||
The :mod:`fakevirt` Module
|
||||
The :mod:`virt.fake` Module
|
||||
--------------------------
|
||||
|
||||
.. automodule:: nova.fakevirt
|
||||
.. automodule:: nova.virt.fake
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
30
nova/compute/instance_types.py
Normal file
30
nova/compute/instance_types.py
Normal file
@ -0,0 +1,30 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
# Copyright (c) 2010 Citrix Systems, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
The built-in instance properties.
|
||||
"""
|
||||
|
||||
INSTANCE_TYPES = {}
|
||||
INSTANCE_TYPES['m1.tiny'] = {'memory_mb': 512, 'vcpus': 1, 'local_gb': 0}
|
||||
INSTANCE_TYPES['m1.small'] = {'memory_mb': 1024, 'vcpus': 1, 'local_gb': 10}
|
||||
INSTANCE_TYPES['m1.medium'] = {'memory_mb': 2048, 'vcpus': 2, 'local_gb': 10}
|
||||
INSTANCE_TYPES['m1.large'] = {'memory_mb': 4096, 'vcpus': 4, 'local_gb': 10}
|
||||
INSTANCE_TYPES['m1.xlarge'] = {'memory_mb': 8192, 'vcpus': 4, 'local_gb': 10}
|
||||
INSTANCE_TYPES['c1.medium'] = {'memory_mb': 2048, 'vcpus': 4, 'local_gb': 10}
|
@ -20,93 +20,48 @@
|
||||
Compute Node:
|
||||
|
||||
Runs on each compute node, managing the
|
||||
hypervisor using libvirt.
|
||||
hypervisor using the virt module.
|
||||
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from twisted.internet import defer
|
||||
from twisted.internet import task
|
||||
from twisted.application import service
|
||||
|
||||
|
||||
try:
|
||||
import libvirt
|
||||
except Exception, err:
|
||||
logging.warning('no libvirt found')
|
||||
|
||||
from nova import exception
|
||||
from nova import fakevirt
|
||||
from nova import flags
|
||||
from nova import process
|
||||
from nova import utils
|
||||
from nova.compute import disk
|
||||
from nova.compute import model
|
||||
from nova.compute import network
|
||||
from nova.objectstore import image # for image_path flag
|
||||
from nova.compute import power_state
|
||||
from nova.compute.instance_types import INSTANCE_TYPES
|
||||
from nova.virt import connection as virt_connection
|
||||
from nova.volume import storage
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
flags.DEFINE_string('libvirt_xml_template',
|
||||
utils.abspath('compute/libvirt.xml.template'),
|
||||
'Libvirt XML Template')
|
||||
flags.DEFINE_bool('use_s3', True,
|
||||
'whether to get images from s3 or use local copy')
|
||||
flags.DEFINE_string('instances_path', utils.abspath('../instances'),
|
||||
'where instances are stored on disk')
|
||||
|
||||
INSTANCE_TYPES = {}
|
||||
INSTANCE_TYPES['m1.tiny'] = {'memory_mb': 512, 'vcpus': 1, 'local_gb': 0}
|
||||
INSTANCE_TYPES['m1.small'] = {'memory_mb': 1024, 'vcpus': 1, 'local_gb': 10}
|
||||
INSTANCE_TYPES['m1.medium'] = {'memory_mb': 2048, 'vcpus': 2, 'local_gb': 10}
|
||||
INSTANCE_TYPES['m1.large'] = {'memory_mb': 4096, 'vcpus': 4, 'local_gb': 10}
|
||||
INSTANCE_TYPES['m1.xlarge'] = {'memory_mb': 8192, 'vcpus': 4, 'local_gb': 10}
|
||||
INSTANCE_TYPES['c1.medium'] = {'memory_mb': 2048, 'vcpus': 4, 'local_gb': 10}
|
||||
|
||||
|
||||
def _image_path(path=''):
|
||||
return os.path.join(FLAGS.images_path, path)
|
||||
|
||||
|
||||
def _image_url(path):
|
||||
return "%s:%s/_images/%s" % (FLAGS.s3_host, FLAGS.s3_port, path)
|
||||
|
||||
|
||||
class Node(object, service.Service):
|
||||
"""
|
||||
Manages the running instances.
|
||||
"""
|
||||
def __init__(self):
|
||||
""" load configuration options for this node and connect to libvirt """
|
||||
""" load configuration options for this node and connect to the hypervisor"""
|
||||
super(Node, self).__init__()
|
||||
self._instances = {}
|
||||
self._conn = self._get_connection()
|
||||
self._conn = virt_connection.get_connection()
|
||||
self._pool = process.ProcessPool()
|
||||
self.instdir = model.InstanceDirectory()
|
||||
# TODO(joshua): This needs to ensure system state, specifically: modprobe aoe
|
||||
|
||||
def _get_connection(self):
|
||||
""" returns a libvirt connection object """
|
||||
# TODO(termie): maybe lazy load after initial check for permissions
|
||||
# TODO(termie): check whether we can be disconnected
|
||||
if FLAGS.fake_libvirt:
|
||||
conn = fakevirt.FakeVirtConnection.instance()
|
||||
else:
|
||||
auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT],
|
||||
'root',
|
||||
None]
|
||||
conn = libvirt.openAuth('qemu:///system', auth, 0)
|
||||
if conn == None:
|
||||
logging.error('Failed to open connection to the hypervisor')
|
||||
sys.exit(1)
|
||||
return conn
|
||||
|
||||
def noop(self):
|
||||
""" simple test of an AMQP message call """
|
||||
return defer.succeed('PONG')
|
||||
@ -122,8 +77,7 @@ class Node(object, service.Service):
|
||||
def adopt_instances(self):
|
||||
""" if there are instances already running, adopt them """
|
||||
return defer.succeed(0)
|
||||
instance_names = [self._conn.lookupByID(x).name()
|
||||
for x in self._conn.listDomainsID()]
|
||||
instance_names = self._conn.list_instances()
|
||||
for name in instance_names:
|
||||
try:
|
||||
new_inst = Instance.fromName(self._conn, self._pool, name)
|
||||
@ -155,7 +109,7 @@ class Node(object, service.Service):
|
||||
logging.exception("model server went away")
|
||||
yield
|
||||
|
||||
# @exception.wrap_exception
|
||||
@exception.wrap_exception
|
||||
def run_instance(self, instance_id, **_kwargs):
|
||||
""" launch a new instance with specified options """
|
||||
logging.debug("Starting instance %s..." % (instance_id))
|
||||
@ -174,8 +128,7 @@ class Node(object, service.Service):
|
||||
logging.info("Instances current state is %s", new_inst.state)
|
||||
if new_inst.is_running():
|
||||
raise exception.Error("Instance is already running")
|
||||
d = new_inst.spawn()
|
||||
return d
|
||||
new_inst.spawn()
|
||||
|
||||
@exception.wrap_exception
|
||||
def terminate_instance(self, instance_id):
|
||||
@ -309,20 +262,6 @@ class Instance(object):
|
||||
self.datamodel.save()
|
||||
logging.debug("Finished init of Instance with id of %s" % name)
|
||||
|
||||
def toXml(self):
|
||||
# TODO(termie): cache?
|
||||
logging.debug("Starting the toXML method")
|
||||
libvirt_xml = open(FLAGS.libvirt_xml_template).read()
|
||||
xml_info = self.datamodel.copy()
|
||||
# TODO(joshua): Make this xml express the attached disks as well
|
||||
|
||||
# TODO(termie): lazy lazy hack because xml is annoying
|
||||
xml_info['nova'] = json.dumps(self.datamodel.copy())
|
||||
libvirt_xml = libvirt_xml % xml_info
|
||||
logging.debug("Finished the toXML method")
|
||||
|
||||
return libvirt_xml
|
||||
|
||||
@classmethod
|
||||
def fromName(cls, conn, pool, name):
|
||||
""" use the saved data for reloading the instance """
|
||||
@ -333,7 +272,7 @@ class Instance(object):
|
||||
def set_state(self, state_code, state_description=None):
|
||||
self.datamodel['state'] = state_code
|
||||
if not state_description:
|
||||
state_description = STATE_NAMES[state_code]
|
||||
state_description = power_state.name(state_code)
|
||||
self.datamodel['state_description'] = state_description
|
||||
self.datamodel.save()
|
||||
|
||||
@ -347,37 +286,29 @@ class Instance(object):
|
||||
return self.datamodel['name']
|
||||
|
||||
def is_pending(self):
|
||||
return (self.state == Instance.NOSTATE or self.state == 'pending')
|
||||
return (self.state == power_state.NOSTATE or self.state == 'pending')
|
||||
|
||||
def is_destroyed(self):
|
||||
return self.state == Instance.SHUTOFF
|
||||
return self.state == power_state.SHUTOFF
|
||||
|
||||
def is_running(self):
|
||||
logging.debug("Instance state is: %s" % self.state)
|
||||
return (self.state == Instance.RUNNING or self.state == 'running')
|
||||
return (self.state == power_state.RUNNING or self.state == 'running')
|
||||
|
||||
def describe(self):
|
||||
return self.datamodel
|
||||
|
||||
def info(self):
|
||||
logging.debug("Getting info for dom %s" % self.name)
|
||||
virt_dom = self._conn.lookupByName(self.name)
|
||||
(state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info()
|
||||
return {'state': state,
|
||||
'max_mem': max_mem,
|
||||
'mem': mem,
|
||||
'num_cpu': num_cpu,
|
||||
'cpu_time': cpu_time,
|
||||
'node_name': FLAGS.node_name}
|
||||
|
||||
def basepath(self, path=''):
|
||||
return os.path.abspath(os.path.join(self.datamodel['basepath'], path))
|
||||
result = self._conn.get_info(self.name)
|
||||
result['node_name'] = FLAGS.node_name
|
||||
return result
|
||||
|
||||
def update_state(self):
|
||||
self.datamodel.update(self.info())
|
||||
self.set_state(self.state)
|
||||
self.datamodel.save() # Extra, but harmless
|
||||
|
||||
@defer.inlineCallbacks
|
||||
@exception.wrap_exception
|
||||
def destroy(self):
|
||||
if self.is_destroyed():
|
||||
@ -385,38 +316,9 @@ class Instance(object):
|
||||
raise exception.Error('trying to destroy already destroyed'
|
||||
' instance: %s' % self.name)
|
||||
|
||||
self.set_state(Instance.NOSTATE, 'shutting_down')
|
||||
try:
|
||||
virt_dom = self._conn.lookupByName(self.name)
|
||||
virt_dom.destroy()
|
||||
except Exception, _err:
|
||||
pass
|
||||
# If the instance is already terminated, we're still happy
|
||||
d = defer.Deferred()
|
||||
d.addCallback(lambda x: self._cleanup())
|
||||
d.addCallback(lambda x: self.datamodel.destroy())
|
||||
# TODO(termie): short-circuit me for tests
|
||||
# WE'LL save this for when we do shutdown,
|
||||
# instead of destroy - but destroy returns immediately
|
||||
timer = task.LoopingCall(f=None)
|
||||
def _wait_for_shutdown():
|
||||
try:
|
||||
self.update_state()
|
||||
if self.state == Instance.SHUTDOWN:
|
||||
timer.stop()
|
||||
d.callback(None)
|
||||
except Exception:
|
||||
self.set_state(Instance.SHUTDOWN)
|
||||
timer.stop()
|
||||
d.callback(None)
|
||||
timer.f = _wait_for_shutdown
|
||||
timer.start(interval=0.5, now=True)
|
||||
return d
|
||||
|
||||
def _cleanup(self):
|
||||
target = os.path.abspath(self.datamodel['basepath'])
|
||||
logging.info("Deleting instance files at %s", target)
|
||||
shutil.rmtree(target)
|
||||
self.set_state(power_state.NOSTATE, 'shutting_down')
|
||||
yield self._conn.destroy(self)
|
||||
self.datamodel.destroy()
|
||||
|
||||
@defer.inlineCallbacks
|
||||
@exception.wrap_exception
|
||||
@ -427,136 +329,26 @@ class Instance(object):
|
||||
'instance: %s (state: %s)' % (self.name, self.state))
|
||||
|
||||
logging.debug('rebooting instance %s' % self.name)
|
||||
self.set_state(Instance.NOSTATE, 'rebooting')
|
||||
yield self._conn.lookupByName(self.name).destroy()
|
||||
self._conn.createXML(self.toXml(), 0)
|
||||
|
||||
d = defer.Deferred()
|
||||
timer = task.LoopingCall(f=None)
|
||||
def _wait_for_reboot():
|
||||
try:
|
||||
self.set_state(power_state.NOSTATE, 'rebooting')
|
||||
yield self._conn.reboot(self)
|
||||
self.update_state()
|
||||
if self.is_running():
|
||||
logging.debug('rebooted instance %s' % self.name)
|
||||
timer.stop()
|
||||
d.callback(None)
|
||||
except Exception:
|
||||
self.set_state(Instance.SHUTDOWN)
|
||||
timer.stop()
|
||||
d.callback(None)
|
||||
timer.f = _wait_for_reboot
|
||||
timer.start(interval=0.5, now=True)
|
||||
yield d
|
||||
|
||||
def _fetch_s3_image(self, image, path):
|
||||
url = _image_url('%s/image' % image)
|
||||
d = self._pool.simpleExecute('curl --silent %s -o %s' % (url, path))
|
||||
return d
|
||||
|
||||
def _fetch_local_image(self, image, path):
|
||||
source = _image_path('%s/image' % image)
|
||||
d = self._pool.simpleExecute('cp %s %s' % (source, path))
|
||||
return d
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _create_image(self, libvirt_xml):
|
||||
# syntactic nicety
|
||||
data = self.datamodel
|
||||
basepath = self.basepath
|
||||
|
||||
# ensure directories exist and are writable
|
||||
yield self._pool.simpleExecute('mkdir -p %s' % basepath())
|
||||
yield self._pool.simpleExecute('chmod 0777 %s' % basepath())
|
||||
|
||||
|
||||
# TODO(termie): these are blocking calls, it would be great
|
||||
# if they weren't.
|
||||
logging.info('Creating image for: %s', data['instance_id'])
|
||||
f = open(basepath('libvirt.xml'), 'w')
|
||||
f.write(libvirt_xml)
|
||||
f.close()
|
||||
|
||||
if FLAGS.fake_libvirt:
|
||||
logging.info('fake_libvirt, nothing to do for create_image')
|
||||
raise defer.returnValue(None);
|
||||
|
||||
if FLAGS.use_s3:
|
||||
_fetch_file = self._fetch_s3_image
|
||||
else:
|
||||
_fetch_file = self._fetch_local_image
|
||||
|
||||
if not os.path.exists(basepath('disk')):
|
||||
yield _fetch_file(data['image_id'], basepath('disk-raw'))
|
||||
if not os.path.exists(basepath('kernel')):
|
||||
yield _fetch_file(data['kernel_id'], basepath('kernel'))
|
||||
if not os.path.exists(basepath('ramdisk')):
|
||||
yield _fetch_file(data['ramdisk_id'], basepath('ramdisk'))
|
||||
|
||||
execute = lambda cmd, input=None: self._pool.simpleExecute(cmd=cmd,
|
||||
input=input,
|
||||
error_ok=1)
|
||||
|
||||
key = data['key_data']
|
||||
net = None
|
||||
if FLAGS.simple_network:
|
||||
with open(FLAGS.simple_network_template) as f:
|
||||
net = f.read() % {'address': data['private_dns_name'],
|
||||
'network': FLAGS.simple_network_network,
|
||||
'netmask': FLAGS.simple_network_netmask,
|
||||
'gateway': FLAGS.simple_network_gateway,
|
||||
'broadcast': FLAGS.simple_network_broadcast,
|
||||
'dns': FLAGS.simple_network_dns}
|
||||
if key or net:
|
||||
logging.info('Injecting data into image %s', data['image_id'])
|
||||
yield disk.inject_data(basepath('disk-raw'), key, net, execute=execute)
|
||||
|
||||
if os.path.exists(basepath('disk')):
|
||||
yield self._pool.simpleExecute('rm -f %s' % basepath('disk'))
|
||||
|
||||
bytes = (INSTANCE_TYPES[data['instance_type']]['local_gb']
|
||||
* 1024 * 1024 * 1024)
|
||||
yield disk.partition(
|
||||
basepath('disk-raw'), basepath('disk'), bytes, execute=execute)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
@exception.wrap_exception
|
||||
def spawn(self):
|
||||
self.set_state(Instance.NOSTATE, 'spawning')
|
||||
self.set_state(power_state.NOSTATE, 'spawning')
|
||||
logging.debug("Starting spawn in Instance")
|
||||
|
||||
xml = self.toXml()
|
||||
self.set_state(Instance.NOSTATE, 'launching')
|
||||
logging.info('self %s', self)
|
||||
try:
|
||||
yield self._create_image(xml)
|
||||
self._conn.createXML(xml, 0)
|
||||
# TODO(termie): this should actually register
|
||||
# a callback to check for successful boot
|
||||
logging.debug("Instance is running")
|
||||
|
||||
local_d = defer.Deferred()
|
||||
timer = task.LoopingCall(f=None)
|
||||
def _wait_for_boot():
|
||||
try:
|
||||
self.update_state()
|
||||
if self.is_running():
|
||||
logging.debug('booted instance %s' % self.name)
|
||||
timer.stop()
|
||||
local_d.callback(None)
|
||||
except Exception:
|
||||
self.set_state(Instance.SHUTDOWN)
|
||||
logging.error('Failed to boot instance %s' % self.name)
|
||||
timer.stop()
|
||||
local_d.callback(None)
|
||||
timer.f = _wait_for_boot
|
||||
timer.start(interval=0.5, now=True)
|
||||
yield self._conn.spawn(self)
|
||||
except Exception, ex:
|
||||
logging.debug(ex)
|
||||
self.set_state(Instance.SHUTDOWN)
|
||||
self.set_state(power_state.SHUTDOWN)
|
||||
self.update_state()
|
||||
|
||||
@exception.wrap_exception
|
||||
def console_output(self):
|
||||
if not FLAGS.fake_libvirt:
|
||||
# FIXME: Abstract this for Xen
|
||||
if FLAGS.connection_type == 'libvirt':
|
||||
fname = os.path.abspath(
|
||||
os.path.join(self.datamodel['basepath'], 'console.log'))
|
||||
with open(fname, 'r') as f:
|
||||
@ -564,13 +356,3 @@ class Instance(object):
|
||||
else:
|
||||
console = 'FAKE CONSOLE OUTPUT'
|
||||
return defer.succeed(console)
|
||||
|
||||
STATE_NAMES = {
|
||||
Instance.NOSTATE : 'pending',
|
||||
Instance.RUNNING : 'running',
|
||||
Instance.BLOCKED : 'blocked',
|
||||
Instance.PAUSED : 'paused',
|
||||
Instance.SHUTDOWN : 'shutdown',
|
||||
Instance.SHUTOFF : 'shutdown',
|
||||
Instance.CRASHED : 'crashed',
|
||||
}
|
||||
|
41
nova/compute/power_state.py
Normal file
41
nova/compute/power_state.py
Normal file
@ -0,0 +1,41 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
# Copyright (c) 2010 Citrix Systems, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""The various power states that a VM can be in."""
|
||||
|
||||
NOSTATE = 0x00
|
||||
RUNNING = 0x01
|
||||
BLOCKED = 0x02
|
||||
PAUSED = 0x03
|
||||
SHUTDOWN = 0x04
|
||||
SHUTOFF = 0x05
|
||||
CRASHED = 0x06
|
||||
|
||||
|
||||
def name(code):
|
||||
d = {
|
||||
NOSTATE : 'pending',
|
||||
RUNNING : 'running',
|
||||
BLOCKED : 'blocked',
|
||||
PAUSED : 'paused',
|
||||
SHUTDOWN: 'shutdown',
|
||||
SHUTOFF : 'shutdown',
|
||||
CRASHED : 'crashed',
|
||||
}
|
||||
return d[code]
|
@ -39,6 +39,7 @@ from nova.auth import users
|
||||
from nova.compute import model
|
||||
from nova.compute import network
|
||||
from nova.compute import node
|
||||
from nova.compute.instance_types import INSTANCE_TYPES
|
||||
from nova.endpoint import images
|
||||
from nova.volume import storage
|
||||
|
||||
@ -103,7 +104,7 @@ class CloudController(object):
|
||||
result = {}
|
||||
for instance in self.instdir.all:
|
||||
if instance['project_id'] == project_id:
|
||||
line = '%s slots=%d' % (instance['private_dns_name'], node.INSTANCE_TYPES[instance['instance_type']]['vcpus'])
|
||||
line = '%s slots=%d' % (instance['private_dns_name'], INSTANCE_TYPES[instance['instance_type']]['vcpus'])
|
||||
if instance['key_name'] in result:
|
||||
result[instance['key_name']].append(line)
|
||||
else:
|
||||
|
112
nova/fakevirt.py
112
nova/fakevirt.py
@ -1,112 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
A fake (in-memory) hypervisor+api. Allows nova testing w/o KVM and libvirt.
|
||||
"""
|
||||
|
||||
import StringIO
|
||||
from xml.etree import ElementTree
|
||||
|
||||
|
||||
class FakeVirtConnection(object):
|
||||
# FIXME: networkCreateXML, listNetworks don't do anything since
|
||||
# they aren't exercised in tests yet
|
||||
|
||||
def __init__(self):
|
||||
self.next_index = 0
|
||||
self.instances = {}
|
||||
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
if not hasattr(cls, '_instance'):
|
||||
cls._instance = cls()
|
||||
return cls._instance
|
||||
|
||||
def lookupByID(self, i):
|
||||
return self.instances[str(i)]
|
||||
|
||||
def listDomainsID(self):
|
||||
return self.instances.keys()
|
||||
|
||||
def listNetworks(self):
|
||||
return []
|
||||
|
||||
def lookupByName(self, instance_id):
|
||||
for x in self.instances.values():
|
||||
if x.name() == instance_id:
|
||||
return x
|
||||
raise Exception('no instance found for instance_id: %s' % instance_id)
|
||||
|
||||
def networkCreateXML(self, xml):
|
||||
pass
|
||||
|
||||
def createXML(self, xml, flags):
|
||||
# parse the xml :(
|
||||
xml_stringio = StringIO.StringIO(xml)
|
||||
|
||||
my_xml = ElementTree.parse(xml_stringio)
|
||||
name = my_xml.find('name').text
|
||||
|
||||
fake_instance = FakeVirtInstance(conn=self,
|
||||
index=str(self.next_index),
|
||||
name=name,
|
||||
xml=my_xml)
|
||||
self.instances[str(self.next_index)] = fake_instance
|
||||
self.next_index += 1
|
||||
|
||||
def _removeInstance(self, i):
|
||||
self.instances.pop(str(i))
|
||||
|
||||
|
||||
class FakeVirtInstance(object):
|
||||
NOSTATE = 0x00
|
||||
RUNNING = 0x01
|
||||
BLOCKED = 0x02
|
||||
PAUSED = 0x03
|
||||
SHUTDOWN = 0x04
|
||||
SHUTOFF = 0x05
|
||||
CRASHED = 0x06
|
||||
|
||||
def __init__(self, conn, index, name, xml):
|
||||
self._conn = conn
|
||||
self._destroyed = False
|
||||
self._name = name
|
||||
self._index = index
|
||||
self._state = self.RUNNING
|
||||
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
def destroy(self):
|
||||
if self._state == self.SHUTOFF:
|
||||
raise Exception('instance already destroyed: %s' % self.name())
|
||||
self._state = self.SHUTDOWN
|
||||
self._conn._removeInstance(self._index)
|
||||
|
||||
def info(self):
|
||||
return [self._state, 0, 2, 0, 0]
|
||||
|
||||
def XMLDesc(self, flags):
|
||||
return open('fakevirtinstance.xml', 'r').read()
|
||||
|
||||
def blockStats(self, disk):
|
||||
return [0L, 0L, 0L, 0L, null]
|
||||
|
||||
def interfaceStats(self, iface):
|
||||
return [0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L]
|
@ -36,14 +36,13 @@ DEFINE_bool = DEFINE_bool
|
||||
# Define any app-specific flags in their own files, docs at:
|
||||
# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39
|
||||
|
||||
DEFINE_string('connection_type', 'libvirt', 'libvirt or fake')
|
||||
DEFINE_integer('s3_port', 3333, 's3 port')
|
||||
DEFINE_integer('s3_internal_port', 3334, 's3 port')
|
||||
DEFINE_string('s3_host', '127.0.0.1', 's3 host')
|
||||
#DEFINE_string('cloud_topic', 'cloud', 'the topic clouds listen on')
|
||||
DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on')
|
||||
DEFINE_string('storage_topic', 'storage', 'the topic storage nodes listen on')
|
||||
DEFINE_bool('fake_libvirt', False,
|
||||
'whether to use a fake libvirt or not')
|
||||
DEFINE_bool('verbose', False, 'show debug output')
|
||||
DEFINE_boolean('fake_rabbit', False, 'use a fake rabbit')
|
||||
DEFINE_bool('fake_network', False, 'should we use fake network devices and addresses')
|
||||
|
@ -33,7 +33,7 @@ class Context(object):
|
||||
class AccessTestCase(test.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(AccessTestCase, self).setUp()
|
||||
FLAGS.fake_libvirt = True
|
||||
FLAGS.connection_type = 'fake'
|
||||
FLAGS.fake_storage = True
|
||||
um = UserManager.instance()
|
||||
# Make test users
|
||||
|
@ -39,7 +39,7 @@ FLAGS = flags.FLAGS
|
||||
class CloudTestCase(test.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(CloudTestCase, self).setUp()
|
||||
self.flags(fake_libvirt=True,
|
||||
self.flags(connection_type='fake',
|
||||
fake_storage=True,
|
||||
fake_users=True)
|
||||
|
||||
@ -72,7 +72,7 @@ class CloudTestCase(test.BaseTestCase):
|
||||
users.UserManager.instance().delete_user('admin')
|
||||
|
||||
def test_console_output(self):
|
||||
if FLAGS.fake_libvirt:
|
||||
if FLAGS.connection_type == 'fake':
|
||||
logging.debug("Can't test instances without a real virtual env.")
|
||||
return
|
||||
instance_id = 'foo'
|
||||
@ -83,7 +83,7 @@ class CloudTestCase(test.BaseTestCase):
|
||||
rv = yield self.node.terminate_instance(instance_id)
|
||||
|
||||
def test_run_instances(self):
|
||||
if FLAGS.fake_libvirt:
|
||||
if FLAGS.connection_type == 'fake':
|
||||
logging.debug("Can't test instances without a real virtual env.")
|
||||
return
|
||||
image_id = FLAGS.default_image
|
||||
@ -104,7 +104,7 @@ class CloudTestCase(test.BaseTestCase):
|
||||
break
|
||||
self.assert_(rv)
|
||||
|
||||
if not FLAGS.fake_libvirt:
|
||||
if connection_type != 'fake':
|
||||
time.sleep(45) # Should use boto for polling here
|
||||
for reservations in rv['reservationSet']:
|
||||
# for res_id in reservations.keys():
|
||||
|
@ -20,7 +20,7 @@ from nova import flags
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
FLAGS.fake_libvirt = True
|
||||
FLAGS.connection_type = 'fake'
|
||||
FLAGS.fake_storage = True
|
||||
FLAGS.fake_rabbit = True
|
||||
FLAGS.fake_network = True
|
||||
|
@ -39,7 +39,7 @@ FLAGS = flags.FLAGS
|
||||
class AdminTestCase(test.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(AdminTestCase, self).setUp()
|
||||
self.flags(fake_libvirt=True,
|
||||
self.flags(connection_type='fake',
|
||||
fake_rabbit=True)
|
||||
|
||||
self.conn = rpc.Connection.instance()
|
||||
|
@ -34,7 +34,7 @@ FLAGS = flags.FLAGS
|
||||
class ModelTestCase(test.TrialTestCase):
|
||||
def setUp(self):
|
||||
super(ModelTestCase, self).setUp()
|
||||
self.flags(fake_libvirt=True,
|
||||
self.flags(connection_type='fake',
|
||||
fake_storage=True,
|
||||
fake_users=True)
|
||||
|
||||
|
@ -33,7 +33,7 @@ from nova import utils
|
||||
class NetworkTestCase(test.TrialTestCase):
|
||||
def setUp(self):
|
||||
super(NetworkTestCase, self).setUp()
|
||||
self.flags(fake_libvirt=True,
|
||||
self.flags(connection_type='fake',
|
||||
fake_storage=True,
|
||||
fake_network=True,
|
||||
network_size=32)
|
||||
|
@ -57,7 +57,7 @@ class NodeConnectionTestCase(test.TrialTestCase):
|
||||
def setUp(self):
|
||||
logging.getLogger().setLevel(logging.DEBUG)
|
||||
super(NodeConnectionTestCase, self).setUp()
|
||||
self.flags(fake_libvirt=True,
|
||||
self.flags(connection_type='fake',
|
||||
fake_storage=True,
|
||||
fake_users=True)
|
||||
self.node = node.Node()
|
||||
|
@ -25,6 +25,8 @@ import tempfile
|
||||
|
||||
from nova import flags
|
||||
from nova import objectstore
|
||||
from nova.objectstore import bucket # for buckets_path flag
|
||||
from nova.objectstore import image # for images_path flag
|
||||
from nova import test
|
||||
from nova.auth import users
|
||||
|
||||
|
@ -20,7 +20,7 @@ from nova import flags
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
FLAGS.fake_libvirt = False
|
||||
FLAGS.connection_type = 'libvirt'
|
||||
FLAGS.fake_storage = False
|
||||
FLAGS.fake_rabbit = False
|
||||
FLAGS.fake_network = False
|
||||
|
@ -34,7 +34,7 @@ class StorageTestCase(test.TrialTestCase):
|
||||
super(StorageTestCase, self).setUp()
|
||||
self.mynode = node.Node()
|
||||
self.mystorage = None
|
||||
self.flags(fake_libvirt=True,
|
||||
self.flags(connection_type='fake',
|
||||
fake_storage=True)
|
||||
self.mystorage = storage.BlockStore()
|
||||
|
||||
|
@ -35,7 +35,7 @@ class UserTestCase(test.BaseTestCase):
|
||||
flush_db = False
|
||||
def setUp(self):
|
||||
super(UserTestCase, self).setUp()
|
||||
self.flags(fake_libvirt=True,
|
||||
self.flags(connection_type='fake',
|
||||
fake_storage=True)
|
||||
self.users = users.UserManager.instance()
|
||||
|
||||
|
15
nova/virt/__init__.py
Normal file
15
nova/virt/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright (c) 2010 Citrix Systems, Inc.
|
||||
#
|
||||
# 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.
|
42
nova/virt/connection.py
Normal file
42
nova/virt/connection.py
Normal file
@ -0,0 +1,42 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
# Copyright (c) 2010 Citrix Systems, Inc.
|
||||
#
|
||||
# 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 nova import flags
|
||||
from nova.virt import fake
|
||||
from nova.virt import libvirt_conn
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
def get_connection(read_only=False):
|
||||
# TODO(termie): maybe lazy load after initial check for permissions
|
||||
# TODO(termie): check whether we can be disconnected
|
||||
t = FLAGS.connection_type
|
||||
if t == 'fake':
|
||||
conn = fake.get_connection(read_only)
|
||||
elif t == 'libvirt':
|
||||
conn = libvirt_conn.get_connection(read_only)
|
||||
else:
|
||||
raise Exception('Unknown connection type "%s"' % t)
|
||||
|
||||
if conn is None:
|
||||
logging.error('Failed to open connection to the hypervisor')
|
||||
sys.exit(1)
|
||||
return conn
|
81
nova/virt/fake.py
Normal file
81
nova/virt/fake.py
Normal file
@ -0,0 +1,81 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
# Copyright (c) 2010 Citrix Systems, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
A fake (in-memory) hypervisor+api. Allows nova testing w/o a hypervisor.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from nova.compute import power_state
|
||||
|
||||
|
||||
def get_connection(_):
|
||||
# The read_only parameter is ignored.
|
||||
return FakeConnection.instance()
|
||||
|
||||
|
||||
class FakeConnection(object):
|
||||
def __init__(self):
|
||||
self.instances = {}
|
||||
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
if not hasattr(cls, '_instance'):
|
||||
cls._instance = cls()
|
||||
return cls._instance
|
||||
|
||||
def list_instances(self):
|
||||
return self.instances.keys()
|
||||
|
||||
def spawn(self, instance):
|
||||
fake_instance = FakeInstance()
|
||||
self.instances[instance.name] = fake_instance
|
||||
fake_instance._state = power_state.RUNNING
|
||||
|
||||
def reboot(self, instance):
|
||||
pass
|
||||
|
||||
def destroy(self, instance):
|
||||
del self.instances[instance.name]
|
||||
|
||||
def get_info(self, instance_id):
|
||||
i = self.instances[instance_id]
|
||||
return {'state': i._state,
|
||||
'max_mem': 0,
|
||||
'mem': 0,
|
||||
'num_cpu': 2,
|
||||
'cpu_time': 0}
|
||||
|
||||
def list_disks(self, instance_id):
|
||||
return ['A_DISK']
|
||||
|
||||
def list_interfaces(self, instance_id):
|
||||
return ['A_VIF']
|
||||
|
||||
def block_stats(self, instance_id, disk_id):
|
||||
return [0L, 0L, 0L, 0L, null]
|
||||
|
||||
def interface_stats(self, instance_id, iface_id):
|
||||
return [0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L]
|
||||
|
||||
|
||||
class FakeInstance(object):
|
||||
def __init__(self):
|
||||
self._state = power_state.NOSTATE
|
55
nova/virt/images.py
Normal file
55
nova/virt/images.py
Normal file
@ -0,0 +1,55 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
# Copyright (c) 2010 Citrix Systems, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Handling of VM disk images.
|
||||
"""
|
||||
|
||||
import os.path
|
||||
|
||||
from nova import flags
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
flags.DEFINE_bool('use_s3', True,
|
||||
'whether to get images from s3 or use local copy')
|
||||
|
||||
|
||||
def fetch(pool, image, path):
|
||||
if FLAGS.use_s3:
|
||||
f = _fetch_s3_image
|
||||
else:
|
||||
f = _fetch_local_image
|
||||
return f(pool, image, path)
|
||||
|
||||
def _fetch_s3_image(pool, image, path):
|
||||
url = _image_url('%s/image' % image)
|
||||
d = pool.simpleExecute('curl --silent %s -o %s' % (url, path))
|
||||
return d
|
||||
|
||||
def _fetch_local_image(pool, image, path):
|
||||
source = _image_path('%s/image' % image)
|
||||
d = pool.simpleExecute('cp %s %s' % (source, path))
|
||||
return d
|
||||
|
||||
def _image_path(path):
|
||||
return os.path.join(FLAGS.images_path, path)
|
||||
|
||||
def _image_url(path):
|
||||
return "%s:%s/_images/%s" % (FLAGS.s3_host, FLAGS.s3_port, path)
|
353
nova/virt/libvirt_conn.py
Normal file
353
nova/virt/libvirt_conn.py
Normal file
@ -0,0 +1,353 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
# Copyright (c) 2010 Citrix Systems, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
A connection to a hypervisor (e.g. KVM) through libvirt.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os.path
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.internet import task
|
||||
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova import process
|
||||
from nova import utils
|
||||
from nova.compute import disk
|
||||
from nova.compute import instance_types
|
||||
from nova.compute import power_state
|
||||
from nova.virt import images
|
||||
|
||||
libvirt = None
|
||||
libxml2 = None
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
flags.DEFINE_string('libvirt_xml_template',
|
||||
utils.abspath('compute/libvirt.xml.template'),
|
||||
'Libvirt XML Template')
|
||||
|
||||
def get_connection(read_only):
|
||||
# These are loaded late so that there's no need to install these
|
||||
# libraries when not using libvirt.
|
||||
global libvirt
|
||||
global libxml2
|
||||
if libvirt is None:
|
||||
libvirt = __import__('libvirt')
|
||||
if libxml2 is None:
|
||||
libxml2 = __import__('libxml2')
|
||||
return LibvirtConnection(read_only)
|
||||
|
||||
|
||||
class LibvirtConnection(object):
|
||||
def __init__(self, read_only):
|
||||
auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT],
|
||||
'root',
|
||||
None]
|
||||
if read_only:
|
||||
self._conn = libvirt.openReadOnly('qemu:///system')
|
||||
else:
|
||||
self._conn = libvirt.openAuth('qemu:///system', auth, 0)
|
||||
self._pool = process.ProcessPool()
|
||||
|
||||
|
||||
def list_instances(self):
|
||||
return [self._conn.lookupByID(x).name()
|
||||
for x in self._conn.listDomainsID()]
|
||||
|
||||
|
||||
def destroy(self, instance):
|
||||
try:
|
||||
virt_dom = self._conn.lookupByName(instance.name)
|
||||
virt_dom.destroy()
|
||||
except Exception, _err:
|
||||
pass
|
||||
# If the instance is already terminated, we're still happy
|
||||
d = defer.Deferred()
|
||||
d.addCallback(lambda x: self._cleanup())
|
||||
# FIXME: What does this comment mean?
|
||||
# TODO(termie): short-circuit me for tests
|
||||
# WE'LL save this for when we do shutdown,
|
||||
# instead of destroy - but destroy returns immediately
|
||||
timer = task.LoopingCall(f=None)
|
||||
def _wait_for_shutdown():
|
||||
try:
|
||||
instance.update_state()
|
||||
if instance.state == power_state.SHUTDOWN:
|
||||
timer.stop()
|
||||
d.callback(None)
|
||||
except Exception:
|
||||
instance.set_state(power_state.SHUTDOWN)
|
||||
timer.stop()
|
||||
d.callback(None)
|
||||
timer.f = _wait_for_shutdown
|
||||
timer.start(interval=0.5, now=True)
|
||||
return d
|
||||
|
||||
|
||||
def _cleanup(self, instance):
|
||||
target = os.path.abspath(instance.datamodel['basepath'])
|
||||
logging.info("Deleting instance files at %s", target)
|
||||
shutil.rmtree(target)
|
||||
|
||||
|
||||
@defer.inlineCallbacks
|
||||
@exception.wrap_exception
|
||||
def reboot(self, instance):
|
||||
xml = self.toXml(instance)
|
||||
yield self._conn.lookupByName(instance.name).destroy()
|
||||
yield self._conn.createXML(xml, 0)
|
||||
|
||||
d = defer.Deferred()
|
||||
timer = task.LoopingCall(f=None)
|
||||
def _wait_for_reboot():
|
||||
try:
|
||||
instance.update_state()
|
||||
if instance.is_running():
|
||||
logging.debug('rebooted instance %s' % instance.name)
|
||||
timer.stop()
|
||||
d.callback(None)
|
||||
except Exception, exn:
|
||||
logging.error('_wait_for_reboot failed: %s' % exn)
|
||||
instance.set_state(power_state.SHUTDOWN)
|
||||
timer.stop()
|
||||
d.callback(None)
|
||||
timer.f = _wait_for_reboot
|
||||
timer.start(interval=0.5, now=True)
|
||||
yield d
|
||||
|
||||
|
||||
@defer.inlineCallbacks
|
||||
@exception.wrap_exception
|
||||
def spawn(self, instance):
|
||||
xml = self.toXml(instance)
|
||||
instance.set_state(power_state.NOSTATE, 'launching')
|
||||
yield self._create_image(instance, xml)
|
||||
yield self._conn.createXML(xml, 0)
|
||||
# TODO(termie): this should actually register
|
||||
# a callback to check for successful boot
|
||||
logging.debug("Instance is running")
|
||||
|
||||
local_d = defer.Deferred()
|
||||
timer = task.LoopingCall(f=None)
|
||||
def _wait_for_boot():
|
||||
try:
|
||||
instance.update_state()
|
||||
if instance.is_running():
|
||||
logging.debug('booted instance %s' % instance.name)
|
||||
timer.stop()
|
||||
local_d.callback(None)
|
||||
except Exception, exn:
|
||||
logging.error("_wait_for_boot exception %s" % exn)
|
||||
self.set_state(power_state.SHUTDOWN)
|
||||
logging.error('Failed to boot instance %s' % instance.name)
|
||||
timer.stop()
|
||||
local_d.callback(None)
|
||||
timer.f = _wait_for_boot
|
||||
timer.start(interval=0.5, now=True)
|
||||
yield local_d
|
||||
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _create_image(self, instance, libvirt_xml):
|
||||
# syntactic nicety
|
||||
data = instance.datamodel
|
||||
basepath = lambda x='': self.basepath(instance, x)
|
||||
|
||||
# ensure directories exist and are writable
|
||||
yield self._pool.simpleExecute('mkdir -p %s' % basepath())
|
||||
yield self._pool.simpleExecute('chmod 0777 %s' % basepath())
|
||||
|
||||
|
||||
# TODO(termie): these are blocking calls, it would be great
|
||||
# if they weren't.
|
||||
logging.info('Creating image for: %s', data['instance_id'])
|
||||
f = open(basepath('libvirt.xml'), 'w')
|
||||
f.write(libvirt_xml)
|
||||
f.close()
|
||||
|
||||
if not os.path.exists(basepath('disk')):
|
||||
yield images.fetch(self._pool, data['image_id'], basepath('disk-raw'))
|
||||
if not os.path.exists(basepath('kernel')):
|
||||
yield images.fetch(self._pool, data['kernel_id'], basepath('kernel'))
|
||||
if not os.path.exists(basepath('ramdisk')):
|
||||
yield images.fetch(self._pool, data['ramdisk_id'], basepath('ramdisk'))
|
||||
|
||||
execute = lambda cmd, input=None: self._pool.simpleExecute(cmd=cmd,
|
||||
input=input,
|
||||
error_ok=1)
|
||||
|
||||
key = data['key_data']
|
||||
net = None
|
||||
if FLAGS.simple_network:
|
||||
with open(FLAGS.simple_network_template) as f:
|
||||
net = f.read() % {'address': data['private_dns_name'],
|
||||
'network': FLAGS.simple_network_network,
|
||||
'netmask': FLAGS.simple_network_netmask,
|
||||
'gateway': FLAGS.simple_network_gateway,
|
||||
'broadcast': FLAGS.simple_network_broadcast,
|
||||
'dns': FLAGS.simple_network_dns}
|
||||
if key or net:
|
||||
logging.info('Injecting data into image %s', data['image_id'])
|
||||
yield disk.inject_data(basepath('disk-raw'), key, net, execute=execute)
|
||||
|
||||
if os.path.exists(basepath('disk')):
|
||||
yield self._pool.simpleExecute('rm -f %s' % basepath('disk'))
|
||||
|
||||
bytes = (instance_types.INSTANCE_TYPES[data['instance_type']]['local_gb']
|
||||
* 1024 * 1024 * 1024)
|
||||
yield disk.partition(
|
||||
basepath('disk-raw'), basepath('disk'), bytes, execute=execute)
|
||||
|
||||
|
||||
def basepath(self, instance, path=''):
|
||||
return os.path.abspath(os.path.join(instance.datamodel['basepath'], path))
|
||||
|
||||
|
||||
def toXml(self, instance):
|
||||
# TODO(termie): cache?
|
||||
logging.debug("Starting the toXML method")
|
||||
libvirt_xml = open(FLAGS.libvirt_xml_template).read()
|
||||
xml_info = instance.datamodel.copy()
|
||||
# TODO(joshua): Make this xml express the attached disks as well
|
||||
|
||||
# TODO(termie): lazy lazy hack because xml is annoying
|
||||
xml_info['nova'] = json.dumps(instance.datamodel.copy())
|
||||
libvirt_xml = libvirt_xml % xml_info
|
||||
logging.debug("Finished the toXML method")
|
||||
|
||||
return libvirt_xml
|
||||
|
||||
|
||||
def get_info(self, instance_id):
|
||||
virt_dom = self._conn.lookupByName(instance_id)
|
||||
(state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info()
|
||||
return {'state': state,
|
||||
'max_mem': max_mem,
|
||||
'mem': mem,
|
||||
'num_cpu': num_cpu,
|
||||
'cpu_time': cpu_time}
|
||||
|
||||
|
||||
def get_disks(self, instance_id):
|
||||
"""
|
||||
Note that this function takes an instance ID, not an Instance, so
|
||||
that it can be called by monitor.
|
||||
|
||||
Returns a list of all block devices for this domain.
|
||||
"""
|
||||
domain = self._conn.lookupByName(instance_id)
|
||||
# TODO(devcamcar): Replace libxml2 with etree.
|
||||
xml = domain.XMLDesc(0)
|
||||
doc = None
|
||||
|
||||
try:
|
||||
doc = libxml2.parseDoc(xml)
|
||||
except:
|
||||
return []
|
||||
|
||||
ctx = doc.xpathNewContext()
|
||||
disks = []
|
||||
|
||||
try:
|
||||
ret = ctx.xpathEval('/domain/devices/disk')
|
||||
|
||||
for node in ret:
|
||||
devdst = None
|
||||
|
||||
for child in node.children:
|
||||
if child.name == 'target':
|
||||
devdst = child.prop('dev')
|
||||
|
||||
if devdst == None:
|
||||
continue
|
||||
|
||||
disks.append(devdst)
|
||||
finally:
|
||||
if ctx != None:
|
||||
ctx.xpathFreeContext()
|
||||
if doc != None:
|
||||
doc.freeDoc()
|
||||
|
||||
return disks
|
||||
|
||||
|
||||
def get_interfaces(self, instance_id):
|
||||
"""
|
||||
Note that this function takes an instance ID, not an Instance, so
|
||||
that it can be called by monitor.
|
||||
|
||||
Returns a list of all network interfaces for this instance.
|
||||
"""
|
||||
domain = self._conn.lookupByName(instance_id)
|
||||
# TODO(devcamcar): Replace libxml2 with etree.
|
||||
xml = domain.XMLDesc(0)
|
||||
doc = None
|
||||
|
||||
try:
|
||||
doc = libxml2.parseDoc(xml)
|
||||
except:
|
||||
return []
|
||||
|
||||
ctx = doc.xpathNewContext()
|
||||
interfaces = []
|
||||
|
||||
try:
|
||||
ret = ctx.xpathEval('/domain/devices/interface')
|
||||
|
||||
for node in ret:
|
||||
devdst = None
|
||||
|
||||
for child in node.children:
|
||||
if child.name == 'target':
|
||||
devdst = child.prop('dev')
|
||||
|
||||
if devdst == None:
|
||||
continue
|
||||
|
||||
interfaces.append(devdst)
|
||||
finally:
|
||||
if ctx != None:
|
||||
ctx.xpathFreeContext()
|
||||
if doc != None:
|
||||
doc.freeDoc()
|
||||
|
||||
return interfaces
|
||||
|
||||
|
||||
def block_stats(self, instance_id, disk):
|
||||
"""
|
||||
Note that this function takes an instance ID, not an Instance, so
|
||||
that it can be called by monitor.
|
||||
"""
|
||||
domain = self._conn.lookupByName(instance_id)
|
||||
return domain.blockStats(disk)
|
||||
|
||||
|
||||
def interface_stats(self, instance_id, interface):
|
||||
"""
|
||||
Note that this function takes an instance ID, not an Instance, so
|
||||
that it can be called by monitor.
|
||||
"""
|
||||
domain = self._conn.lookupByName(instance_id)
|
||||
return domain.interfaceStats(interface)
|
Loading…
Reference in New Issue
Block a user