summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEwan Mellor <ewan.mellor@citrix.com>2010-07-29 00:07:43 +0000
committerTarmac <>2010-07-29 00:07:43 +0000
commite45b95aa993e577323893406b97e4de55d1ad330 (patch)
treec283214a46ec381ebfdfe4b5423cb737bd2df106
parent4e5737815bd62e0d78add8932ceb220b1ac3787d (diff)
parent04a6a0267e7dc0f4e587e43f23b4acf0dcef52fc (diff)
downloadnova-e45b95aa993e577323893406b97e4de55d1ad330.tar.gz
nova-e45b95aa993e577323893406b97e4de55d1ad330.tar.xz
nova-e45b95aa993e577323893406b97e4de55d1ad330.zip
Adds initial support for XenAPI (not yet finished)
-rwxr-xr-xbin/nova-dhcpbridge2
-rw-r--r--doc/source/fakes.rst4
-rw-r--r--nova/compute/instance_types.py30
-rw-r--r--nova/compute/monitor.py127
-rw-r--r--nova/compute/power_state.py41
-rw-r--r--nova/compute/service.py297
-rw-r--r--nova/endpoint/cloud.py3
-rw-r--r--nova/fakevirt.py112
-rw-r--r--nova/flags.py3
-rw-r--r--nova/objectstore/handler.py22
-rw-r--r--nova/tests/access_unittest.py2
-rw-r--r--nova/tests/auth_unittest.py2
-rw-r--r--nova/tests/cloud_unittest.py8
-rw-r--r--nova/tests/compute_unittest.py2
-rw-r--r--nova/tests/fake_flags.py2
-rw-r--r--nova/tests/model_unittest.py2
-rw-r--r--nova/tests/network_unittest.py2
-rw-r--r--nova/tests/objectstore_unittest.py2
-rw-r--r--nova/tests/real_flags.py2
-rw-r--r--nova/tests/storage_unittest.py115
-rw-r--r--nova/tests/volume_unittest.py2
-rw-r--r--nova/virt/__init__.py15
-rw-r--r--nova/virt/connection.py45
-rw-r--r--nova/virt/fake.py81
-rw-r--r--nova/virt/images.py72
-rw-r--r--nova/virt/libvirt_conn.py355
-rw-r--r--nova/virt/xenapi.py152
27 files changed, 996 insertions, 506 deletions
diff --git a/bin/nova-dhcpbridge b/bin/nova-dhcpbridge
index c519c6ccb..0db241b5e 100755
--- a/bin/nova-dhcpbridge
+++ b/bin/nova-dhcpbridge
@@ -76,7 +76,7 @@ def main():
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.auth_driver='nova.auth.ldapdriver.FakeLdapDriver'
action = argv[1]
diff --git a/doc/source/fakes.rst b/doc/source/fakes.rst
index bea8bc4e9..a993fb4c8 100644
--- a/doc/source/fakes.rst
+++ b/doc/source/fakes.rst
@@ -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:
diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py
new file mode 100644
index 000000000..439be3c7d
--- /dev/null
+++ b/nova/compute/instance_types.py
@@ -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}
diff --git a/nova/compute/monitor.py b/nova/compute/monitor.py
index fdc86b031..19e1a483d 100644
--- a/nova/compute/monitor.py
+++ b/nova/compute/monitor.py
@@ -27,7 +27,6 @@ Instance Monitoring:
import boto
import boto.s3
import datetime
-import libxml2
import logging
import os
import rrdtool
@@ -37,12 +36,8 @@ 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 flags
+from nova.virt import connection as virt_connection
FLAGS = flags.FLAGS
@@ -130,83 +125,6 @@ def init_rrd(instance, name):
*RRD_VALUES[name]
)
-def get_disks(domain):
- """
- Returns a list of all block devices for this domain.
- """
- # 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(domain):
- """
- Returns a list of all network interfaces for this instance.
- """
- # 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 graph_cpu(instance, duration):
"""
Creates a graph of cpu usage for the specified instance and duration.
@@ -317,10 +235,9 @@ def store_graph(instance_id, filename):
class Instance(object):
- def __init__(self, conn, domain):
+ def __init__(self, conn, instance_id):
self.conn = conn
- self.domain = domain
- self.instance_id = domain.name()
+ self.instance_id = instance_id
self.last_updated = datetime.datetime.min
self.cputime = 0
self.cputime_last_updated = None
@@ -385,14 +302,14 @@ class Instance(object):
"""
Returns cpu usage statistics for this instance.
"""
- info = self.domain.info()
+ info = self.conn.get_info(self.instance_id)
# Get the previous values.
cputime_last = self.cputime
cputime_last_updated = self.cputime_last_updated
# Get the raw CPU time used in nanoseconds.
- self.cputime = float(info[4])
+ self.cputime = float(info['cpu_time'])
self.cputime_last_updated = utcnow()
logging.debug('CPU: %d', self.cputime)
@@ -413,8 +330,8 @@ class Instance(object):
logging.debug('cputime_delta = %s', cputime_delta)
# Get the number of virtual cpus in this domain.
- vcpus = int(info[3])
-
+ vcpus = int(info['num_cpu'])
+
logging.debug('vcpus = %d', vcpus)
# Calculate CPU % used and cap at 100.
@@ -427,14 +344,13 @@ class Instance(object):
rd = 0
wr = 0
- # Get a list of block devices for this instance.
- disks = get_disks(self.domain)
+ disks = self.conn.get_disks(self.instance_id)
# Aggregate the read and write totals.
for disk in disks:
try:
rd_req, rd_bytes, wr_req, wr_bytes, errs = \
- self.domain.blockStats(disk)
+ self.conn.block_stats(self.instance_id, disk)
rd += rd_bytes
wr += wr_bytes
except TypeError:
@@ -451,13 +367,12 @@ class Instance(object):
rx = 0
tx = 0
- # Get a list of all network interfaces for this instance.
- interfaces = get_interfaces(self.domain)
+ interfaces = self.conn.get_interfaces(self.instance_id)
# Aggregate the in and out totals.
for interface in interfaces:
try:
- stats = self.domain.interfaceStats(interface)
+ stats = self.conn.interface_stats(self.instance_id, interface)
rx += stats[0]
tx += stats[4]
except TypeError:
@@ -493,20 +408,24 @@ class InstanceMonitor(object, service.Service):
Update resource usage for all running instances.
"""
try:
- conn = libvirt.openReadOnly(None)
- except libvirt.libvirtError:
- logging.exception('unexpected libvirt error')
+ conn = virt_connection.get_connection(read_only=True)
+ except Exception, exn:
+ logging.exception('unexpected exception getting connection')
time.sleep(FLAGS.monitoring_instances_delay)
return
- domain_ids = conn.listDomainsID()
-
+ domain_ids = conn.list_instances()
+ try:
+ self.updateInstances_(conn, domain_ids)
+ except Exception, exn:
+ logging.exception('updateInstances_')
+
+ def updateInstances_(self, conn, domain_ids):
for domain_id in domain_ids:
if not domain_id in self._instances:
- domain = conn.lookupByID(domain_id)
- instance = Instance(conn, domain)
+ instance = Instance(conn, domain_id)
self._instances[domain_id] = instance
- logging.debug('Found instance: %s', instance.instance_id)
+ logging.debug('Found instance: %s', domain_id)
for key in self._instances.keys():
instance = self._instances[key]
diff --git a/nova/compute/power_state.py b/nova/compute/power_state.py
new file mode 100644
index 000000000..b27aa4677
--- /dev/null
+++ b/nova/compute/power_state.py
@@ -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]
diff --git a/nova/compute/service.py b/nova/compute/service.py
index 9998dc6c3..9b162edc7 100644
--- a/nova/compute/service.py
+++ b/nova/compute/service.py
@@ -20,95 +20,50 @@
Compute Service:
Runs on each compute host, managing the
- hypervisor using libvirt.
+ hypervisor using the virt module.
"""
import base64
-import boto.utils
import json
import logging
import os
-import shutil
import sys
-import time
from twisted.internet import defer
from twisted.internet import task
-
-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 service
from nova import utils
-from nova.auth import signer, manager
from nova.compute import disk
from nova.compute import model
from nova.compute import network
+from nova.compute import power_state
+from nova.compute.instance_types import INSTANCE_TYPES
from nova.objectstore import image # for image_path flag
+from nova.virt import connection as virt_connection
from nova.volume import service as volume_service
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 ComputeService(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(ComputeService, self).__init__()
self._instances = {}
- self._conn = self._get_connection()
+ self._conn = virt_connection.get_connection()
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')
@@ -124,8 +79,7 @@ class ComputeService(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, name)
@@ -158,7 +112,7 @@ class ComputeService(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))
@@ -176,8 +130,7 @@ class ComputeService(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):
@@ -312,20 +265,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, name):
""" use the saved data for reloading the instance """
@@ -336,7 +275,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()
@@ -350,37 +289,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():
@@ -388,38 +319,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
@@ -430,157 +332,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.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)
-
- # This should probably move somewhere else, like e.g. a download_as
- # method on User objects and at the same time get rewritten to use
- # twisted web client.
- headers = {}
- headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
-
- user_id = self.datamodel['user_id']
- user = manager.AuthManager().get_user(user_id)
- uri = '/' + url.partition('/')[2]
- auth = signer.Signer(user.secret.encode()).s3_authorization(headers, 'GET', uri)
- headers['Authorization'] = 'AWS %s:%s' % (user.access, auth)
-
- cmd = ['/usr/bin/curl', '--silent', url]
- for (k,v) in headers.iteritems():
- cmd += ['-H', '%s: %s' % (k,v)]
-
- cmd += ['-o', path]
- return process.SharedPool().execute(executable=cmd[0], args=cmd[1:])
-
- def _fetch_local_image(self, image, path):
- source = _image_path('%s/image' % image)
- d = process.simple_execute('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 process.simple_execute(
- 'mkdir -p %s' % basepath())
- yield process.simple_execute(
- '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: \
- process.simple_execute(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 process.simple_execute(
- '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)
+ self.set_state(power_state.NOSTATE, 'rebooting')
+ yield self._conn.reboot(self)
+ self.update_state()
@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:
@@ -588,13 +359,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',
-}
diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py
index acba50b95..8a4edbc0b 100644
--- a/nova/endpoint/cloud.py
+++ b/nova/endpoint/cloud.py
@@ -37,6 +37,7 @@ from nova.auth import rbac
from nova.auth import manager
from nova.compute import model
from nova.compute import network
+from nova.compute.instance_types import INSTANCE_TYPES
from nova.compute import service as compute_service
from nova.endpoint import images
from nova.volume import service as volume_service
@@ -102,7 +103,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'], compute_service.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:
diff --git a/nova/fakevirt.py b/nova/fakevirt.py
deleted file mode 100644
index bcbeae548..000000000
--- a/nova/fakevirt.py
+++ /dev/null
@@ -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]
diff --git a/nova/flags.py b/nova/flags.py
index f63f82c3a..f35f5fa10 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -36,6 +36,7 @@ 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, xenapi or fake')
DEFINE_integer('s3_port', 3333, 's3 port')
DEFINE_string('s3_host', '127.0.0.1', 's3 host')
#DEFINE_string('cloud_topic', 'cloud', 'the topic clouds listen on')
@@ -43,8 +44,6 @@ DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on')
DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on')
DEFINE_string('network_topic', 'network', 'the topic network 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')
diff --git a/nova/objectstore/handler.py b/nova/objectstore/handler.py
index 999581c65..b4d7e6179 100644
--- a/nova/objectstore/handler.py
+++ b/nova/objectstore/handler.py
@@ -120,7 +120,21 @@ def get_context(request):
logging.debug("Authentication Failure: %s" % ex)
raise exception.NotAuthorized
-class S3(Resource):
+class ErrorHandlingResource(Resource):
+ """Maps exceptions to 404 / 401 codes. Won't work for exceptions thrown after NOT_DONE_YET is returned."""
+ # TODO(unassigned) (calling-all-twisted-experts): This needs to be plugged in to the right place in twisted...
+ # This doesn't look like it's the right place (consider exceptions in getChild; or after NOT_DONE_YET is returned
+ def render(self, request):
+ try:
+ return Resource.render(self, request)
+ except exception.NotFound:
+ request.setResponseCode(404)
+ return ''
+ except exception.NotAuthorized:
+ request.setResponseCode(403)
+ return ''
+
+class S3(ErrorHandlingResource):
"""Implementation of an S3-like storage server based on local files."""
def getChild(self, name, request):
request.context = get_context(request)
@@ -140,7 +154,7 @@ class S3(Resource):
}})
return server.NOT_DONE_YET
-class BucketResource(Resource):
+class BucketResource(ErrorHandlingResource):
def __init__(self, name):
Resource.__init__(self)
self.name = name
@@ -190,7 +204,7 @@ class BucketResource(Resource):
return ''
-class ObjectResource(Resource):
+class ObjectResource(ErrorHandlingResource):
def __init__(self, bucket, name):
Resource.__init__(self)
self.bucket = bucket
@@ -231,7 +245,7 @@ class ObjectResource(Resource):
request.setResponseCode(204)
return ''
-class ImageResource(Resource):
+class ImageResource(ErrorHandlingResource):
isLeaf = True
def __init__(self, name):
diff --git a/nova/tests/access_unittest.py b/nova/tests/access_unittest.py
index 832a4b279..fa0a090a0 100644
--- a/nova/tests/access_unittest.py
+++ b/nova/tests/access_unittest.py
@@ -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 = manager.AuthManager()
# Make test users
diff --git a/nova/tests/auth_unittest.py b/nova/tests/auth_unittest.py
index 6f35bab4e..2167c2385 100644
--- a/nova/tests/auth_unittest.py
+++ b/nova/tests/auth_unittest.py
@@ -35,7 +35,7 @@ class AuthTestCase(test.BaseTestCase):
flush_db = False
def setUp(self):
super(AuthTestCase, self).setUp()
- self.flags(fake_libvirt=True,
+ self.flags(connection_type='fake',
fake_storage=True)
self.manager = manager.AuthManager()
diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py
index 344085cc0..40837405c 100644
--- a/nova/tests/cloud_unittest.py
+++ b/nova/tests/cloud_unittest.py
@@ -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)
self.conn = rpc.Connection.instance()
@@ -71,7 +71,7 @@ class CloudTestCase(test.BaseTestCase):
manager.AuthManager().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'
@@ -82,7 +82,7 @@ class CloudTestCase(test.BaseTestCase):
rv = yield self.compute.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
@@ -103,7 +103,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():
diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py
index 5e909abc8..da0f82e3a 100644
--- a/nova/tests/compute_unittest.py
+++ b/nova/tests/compute_unittest.py
@@ -57,7 +57,7 @@ class ComputeConnectionTestCase(test.TrialTestCase):
def setUp(self):
logging.getLogger().setLevel(logging.DEBUG)
super(ComputeConnectionTestCase, self).setUp()
- self.flags(fake_libvirt=True,
+ self.flags(connection_type='fake',
fake_storage=True)
self.compute = service.ComputeService()
diff --git a/nova/tests/fake_flags.py b/nova/tests/fake_flags.py
index 304f24841..a7310fb26 100644
--- a/nova/tests/fake_flags.py
+++ b/nova/tests/fake_flags.py
@@ -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
diff --git a/nova/tests/model_unittest.py b/nova/tests/model_unittest.py
index 16e9cb0cc..6825cfe2a 100644
--- a/nova/tests/model_unittest.py
+++ b/nova/tests/model_unittest.py
@@ -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)
def tearDown(self):
diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py
index a8dba835a..f24eefb0d 100644
--- a/nova/tests/network_unittest.py
+++ b/nova/tests/network_unittest.py
@@ -34,7 +34,7 @@ class NetworkTestCase(test.TrialTestCase):
super(NetworkTestCase, self).setUp()
# NOTE(vish): if you change these flags, make sure to change the
# flags in the corresponding section in nova-dhcpbridge
- self.flags(fake_libvirt=True,
+ self.flags(connection_type='fake',
fake_storage=True,
fake_network=True,
auth_driver='nova.auth.ldapdriver.FakeLdapDriver',
diff --git a/nova/tests/objectstore_unittest.py b/nova/tests/objectstore_unittest.py
index c90120a6e..dd00377e7 100644
--- a/nova/tests/objectstore_unittest.py
+++ b/nova/tests/objectstore_unittest.py
@@ -26,6 +26,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 manager
from nova.objectstore.handler import S3
diff --git a/nova/tests/real_flags.py b/nova/tests/real_flags.py
index f054a8f19..121f4eb41 100644
--- a/nova/tests/real_flags.py
+++ b/nova/tests/real_flags.py
@@ -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
diff --git a/nova/tests/storage_unittest.py b/nova/tests/storage_unittest.py
new file mode 100644
index 000000000..f400cd2fd
--- /dev/null
+++ b/nova/tests/storage_unittest.py
@@ -0,0 +1,115 @@
+# 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.
+
+import logging
+
+from nova import exception
+from nova import flags
+from nova import test
+from nova.compute import node
+from nova.volume import storage
+
+
+FLAGS = flags.FLAGS
+
+
+class StorageTestCase(test.TrialTestCase):
+ def setUp(self):
+ logging.getLogger().setLevel(logging.DEBUG)
+ super(StorageTestCase, self).setUp()
+ self.mynode = node.Node()
+ self.mystorage = None
+ self.flags(connection_type='fake',
+ fake_storage=True)
+ self.mystorage = storage.BlockStore()
+
+ def test_run_create_volume(self):
+ vol_size = '0'
+ user_id = 'fake'
+ project_id = 'fake'
+ volume_id = self.mystorage.create_volume(vol_size, user_id, project_id)
+ # TODO(termie): get_volume returns differently than create_volume
+ self.assertEqual(volume_id,
+ storage.get_volume(volume_id)['volume_id'])
+
+ rv = self.mystorage.delete_volume(volume_id)
+ self.assertRaises(exception.Error,
+ storage.get_volume,
+ volume_id)
+
+ def test_too_big_volume(self):
+ vol_size = '1001'
+ user_id = 'fake'
+ project_id = 'fake'
+ self.assertRaises(TypeError,
+ self.mystorage.create_volume,
+ vol_size, user_id, project_id)
+
+ def test_too_many_volumes(self):
+ vol_size = '1'
+ user_id = 'fake'
+ project_id = 'fake'
+ num_shelves = FLAGS.last_shelf_id - FLAGS.first_shelf_id + 1
+ total_slots = FLAGS.slots_per_shelf * num_shelves
+ vols = []
+ for i in xrange(total_slots):
+ vid = self.mystorage.create_volume(vol_size, user_id, project_id)
+ vols.append(vid)
+ self.assertRaises(storage.NoMoreVolumes,
+ self.mystorage.create_volume,
+ vol_size, user_id, project_id)
+ for id in vols:
+ self.mystorage.delete_volume(id)
+
+ def test_run_attach_detach_volume(self):
+ # Create one volume and one node to test with
+ instance_id = "storage-test"
+ vol_size = "5"
+ user_id = "fake"
+ project_id = 'fake'
+ mountpoint = "/dev/sdf"
+ volume_id = self.mystorage.create_volume(vol_size, user_id, project_id)
+
+ volume_obj = storage.get_volume(volume_id)
+ volume_obj.start_attach(instance_id, mountpoint)
+ rv = yield self.mynode.attach_volume(volume_id,
+ instance_id,
+ mountpoint)
+ self.assertEqual(volume_obj['status'], "in-use")
+ self.assertEqual(volume_obj['attachStatus'], "attached")
+ self.assertEqual(volume_obj['instance_id'], instance_id)
+ self.assertEqual(volume_obj['mountpoint'], mountpoint)
+
+ self.assertRaises(exception.Error,
+ self.mystorage.delete_volume,
+ volume_id)
+
+ rv = yield self.mystorage.detach_volume(volume_id)
+ volume_obj = storage.get_volume(volume_id)
+ self.assertEqual(volume_obj['status'], "available")
+
+ rv = self.mystorage.delete_volume(volume_id)
+ self.assertRaises(exception.Error,
+ storage.get_volume,
+ volume_id)
+
+ def test_multi_node(self):
+ # TODO(termie): Figure out how to test with two nodes,
+ # each of them having a different FLAG for storage_node
+ # This will allow us to test cross-node interactions
+ pass
diff --git a/nova/tests/volume_unittest.py b/nova/tests/volume_unittest.py
index 62144269c..b536ac383 100644
--- a/nova/tests/volume_unittest.py
+++ b/nova/tests/volume_unittest.py
@@ -34,7 +34,7 @@ class VolumeTestCase(test.TrialTestCase):
super(VolumeTestCase, self).setUp()
self.compute = compute.service.ComputeService()
self.volume = None
- self.flags(fake_libvirt=True,
+ self.flags(connection_type='fake',
fake_storage=True)
self.volume = volume_service.VolumeService()
diff --git a/nova/virt/__init__.py b/nova/virt/__init__.py
new file mode 100644
index 000000000..3d598c463
--- /dev/null
+++ b/nova/virt/__init__.py
@@ -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.
diff --git a/nova/virt/connection.py b/nova/virt/connection.py
new file mode 100644
index 000000000..004adb19d
--- /dev/null
+++ b/nova/virt/connection.py
@@ -0,0 +1,45 @@
+# 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
+from nova.virt import xenapi
+
+
+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)
+ elif t == 'xenapi':
+ conn = xenapi.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
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
new file mode 100644
index 000000000..d9ae5ac96
--- /dev/null
+++ b/nova/virt/fake.py
@@ -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
diff --git a/nova/virt/images.py b/nova/virt/images.py
new file mode 100644
index 000000000..92210e242
--- /dev/null
+++ b/nova/virt/images.py
@@ -0,0 +1,72 @@
+# 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
+import time
+
+from nova import flags
+from nova import process
+from nova.auth import signer
+
+FLAGS = flags.FLAGS
+
+flags.DEFINE_bool('use_s3', True,
+ 'whether to get images from s3 or use local copy')
+
+
+def fetch(image, path, user):
+ if FLAGS.use_s3:
+ f = _fetch_s3_image
+ else:
+ f = _fetch_local_image
+ return f(image, path, user)
+
+def _fetch_s3_image(image, path, user):
+ url = _image_url('%s/image' % image)
+
+ # This should probably move somewhere else, like e.g. a download_as
+ # method on User objects and at the same time get rewritten to use
+ # twisted web client.
+ headers = {}
+ headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
+
+ uri = '/' + url.partition('/')[2]
+ auth = signer.Signer(user.secret.encode()).s3_authorization(headers, 'GET', uri)
+ headers['Authorization'] = 'AWS %s:%s' % (user.access, auth)
+
+ cmd = ['/usr/bin/curl', '--silent', url]
+ for (k,v) in headers.iteritems():
+ cmd += ['-H', '%s: %s' % (k,v)]
+
+ cmd += ['-o', path]
+ return process.SharedPool().execute(executable=cmd[0], args=cmd[1:])
+
+def _fetch_local_image(image, path, _):
+ source = _image_path('%s/image' % image)
+ return process.simple_execute('cp %s %s' % (source, path))
+
+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)
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
new file mode 100644
index 000000000..c545e4190
--- /dev/null
+++ b/nova/virt/libvirt_conn.py
@@ -0,0 +1,355 @@
+# 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.auth import manager
+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)
+
+
+ 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 _: self._cleanup(instance))
+ # 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 process.simple_execute('mkdir -p %s' % basepath())
+ yield process.simple_execute('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()
+
+ user = manager.AuthManager().get_user(data['user_id'])
+ if not os.path.exists(basepath('disk')):
+ yield images.fetch(data['image_id'], basepath('disk-raw'), user)
+ if not os.path.exists(basepath('kernel')):
+ yield images.fetch(data['kernel_id'], basepath('kernel'), user)
+ if not os.path.exists(basepath('ramdisk')):
+ yield images.fetch(data['ramdisk_id'], basepath('ramdisk'), user)
+
+ execute = lambda cmd, input=None: \
+ process.simple_execute(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 process.simple_execute('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)
diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py
new file mode 100644
index 000000000..dc372e3e3
--- /dev/null
+++ b/nova/virt/xenapi.py
@@ -0,0 +1,152 @@
+# 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.
+
+"""
+A connection to XenServer or Xen Cloud Platform.
+"""
+
+import logging
+
+from twisted.internet import defer
+from twisted.internet import task
+
+from nova import exception
+from nova import flags
+from nova import process
+from nova.compute import power_state
+
+XenAPI = None
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('xenapi_connection_url',
+ None,
+ 'URL for connection to XenServer/Xen Cloud Platform. Required if connection_type=xenapi.')
+flags.DEFINE_string('xenapi_connection_username',
+ 'root',
+ 'Username for connection to XenServer/Xen Cloud Platform. Used only if connection_type=xenapi.')
+flags.DEFINE_string('xenapi_connection_password',
+ None,
+ 'Password for connection to XenServer/Xen Cloud Platform. Used only if connection_type=xenapi.')
+
+
+def get_connection(_):
+ """Note that XenAPI doesn't have a read-only connection mode, so
+ the read_only parameter is ignored."""
+ # This is loaded late so that there's no need to install this
+ # library when not using XenAPI.
+ global XenAPI
+ if XenAPI is None:
+ XenAPI = __import__('XenAPI')
+ url = FLAGS.xenapi_connection_url
+ username = FLAGS.xenapi_connection_username
+ password = FLAGS.xenapi_connection_password
+ if not url or password is None:
+ raise Exception('Must specify xenapi_connection_url, xenapi_connection_username (optionally), and xenapi_connection_password to use connection_type=xenapi')
+ return XenAPIConnection(url, username, password)
+
+
+class XenAPIConnection(object):
+
+ def __init__(self, url, user, pw):
+ self._conn = XenAPI.Session(url)
+ self._conn.login_with_password(user, pw)
+
+ def list_instances(self):
+ result = [self._conn.xenapi.VM.get_name_label(vm) \
+ for vm in self._conn.xenapi.VM.get_all()]
+
+ @defer.inlineCallbacks
+ @exception.wrap_exception
+ def spawn(self, instance):
+ vm = self.lookup(instance.name)
+ if vm is not None:
+ raise Exception('Attempted to create non-unique name %s' %
+ instance.name)
+ mem = str(long(instance.datamodel['memory_kb']) * 1024)
+ vcpus = str(instance.datamodel['vcpus'])
+ rec = {
+ 'name_label': instance.name,
+ 'name_description': '',
+ 'is_a_template': False,
+ 'memory_static_min': '0',
+ 'memory_static_max': mem,
+ 'memory_dynamic_min': mem,
+ 'memory_dynamic_max': mem,
+ 'VCPUs_at_startup': vcpus,
+ 'VCPUs_max': vcpus,
+ 'VCPUs_params': {},
+ 'actions_after_shutdown': 'destroy',
+ 'actions_after_reboot': 'restart',
+ 'actions_after_crash': 'destroy',
+ 'PV_bootloader': '',
+ 'PV_kernel': instance.datamodel['kernel_id'],
+ 'PV_ramdisk': instance.datamodel['ramdisk_id'],
+ 'PV_args': '',
+ 'PV_bootloader_args': '',
+ 'PV_legacy_args': '',
+ 'HVM_boot_policy': '',
+ 'HVM_boot_params': {},
+ 'platform': {},
+ 'PCI_bus': '',
+ 'recommendations': '',
+ 'affinity': '',
+ 'user_version': '0',
+ 'other_config': {},
+ }
+ vm = yield self._conn.xenapi.VM.create(rec)
+ #yield self._conn.xenapi.VM.start(vm, False, False)
+
+
+ def reboot(self, instance):
+ vm = self.lookup(instance.name)
+ if vm is None:
+ raise Exception('instance not present %s' % instance.name)
+ yield self._conn.xenapi.VM.clean_reboot(vm)
+
+ def destroy(self, instance):
+ vm = self.lookup(instance.name)
+ if vm is None:
+ raise Exception('instance not present %s' % instance.name)
+ yield self._conn.xenapi.VM.destroy(vm)
+
+ def get_info(self, instance_id):
+ vm = self.lookup(instance_id)
+ if vm is None:
+ raise Exception('instance not present %s' % instance.name)
+ rec = self._conn.xenapi.VM.get_record(vm)
+ return {'state': power_state_from_xenapi[rec['power_state']],
+ 'max_mem': long(rec['memory_static_max']) >> 10,
+ 'mem': long(rec['memory_dynamic_max']) >> 10,
+ 'num_cpu': rec['VCPUs_max'],
+ 'cpu_time': 0}
+
+ def lookup(self, i):
+ vms = self._conn.xenapi.VM.get_by_name_label(i)
+ n = len(vms)
+ if n == 0:
+ return None
+ elif n > 1:
+ raise Exception('duplicate name found: %s' % i)
+ else:
+ return vms[0]
+
+ power_state_from_xenapi = {
+ 'Halted' : power_state.RUNNING, #FIXME
+ 'Running' : power_state.RUNNING,
+ 'Paused' : power_state.PAUSED,
+ 'Suspended': power_state.SHUTDOWN, # FIXME
+ 'Crashed' : power_state.CRASHED
+ }