From 9e05d5eab4e98d9c87add1263be7a052b56960a9 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Thu, 9 Dec 2010 16:42:52 +0000 Subject: [PATCH 01/39] raw instances can now be launched in xenapi (only as hvm at the moment) --- nova/flags.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nova/flags.py b/nova/flags.py index 1f94feb08..1cb846c15 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -234,6 +234,13 @@ DEFINE_string('default_ramdisk', 'ari-11111', 'default ramdisk to use, testing only') DEFINE_string('default_instance_type', 'm1.small', 'default instance type to use, testing only') +DEFINE_string('null_kernel', 'aki-00000000', + 'kernel image that indicates not to use a kernel, ' + ' but to use a raw disk image instead') +DEFINE_string('null_kernel_pv', 'aki-00000001', + 'kernel image that indicates not to use a kernel, ' + ' but to use a raw disk image for a paravirtualized guest') + DEFINE_string('vpn_image_id', 'ami-CLOUDPIPE', 'AMI for cloudpipe vpn server') DEFINE_string('vpn_key_suffix', From 7e27bd452eb7fe08cb687bdd0ac5908df34b8d31 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Mon, 13 Dec 2010 16:11:12 +0000 Subject: [PATCH 02/39] only needs work for distinguishing pv from hvm --- .project | 17 +++++++++++++++++ .pydevproject | 10 ++++++++++ cuba-ubuntu.16501 | 0 nova-api.pid | 1 + nova-api.pid.lock | 0 nova-manage.conf | 26 ++++++++++++++++++++++++++ nova-network.pid | 1 + nova-objectstore.pid | 1 + nova-scheduler.pid | 1 + 9 files changed, 57 insertions(+) create mode 100644 .project create mode 100644 .pydevproject create mode 100644 cuba-ubuntu.16501 create mode 100644 nova-api.pid create mode 100644 nova-api.pid.lock create mode 100644 nova-manage.conf create mode 100644 nova-network.pid create mode 100644 nova-objectstore.pid create mode 100644 nova-scheduler.pid diff --git a/.project b/.project new file mode 100644 index 000000000..1213c940a --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + Openstack-xs_raw_disk_images + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + + diff --git a/.pydevproject b/.pydevproject new file mode 100644 index 000000000..8030d8e44 --- /dev/null +++ b/.pydevproject @@ -0,0 +1,10 @@ + + + + +Default +python 2.6 + +/home/salvatore/xenapi + + diff --git a/cuba-ubuntu.16501 b/cuba-ubuntu.16501 new file mode 100644 index 000000000..e69de29bb diff --git a/nova-api.pid b/nova-api.pid new file mode 100644 index 000000000..9bcd7ef16 --- /dev/null +++ b/nova-api.pid @@ -0,0 +1 @@ +16501 diff --git a/nova-api.pid.lock b/nova-api.pid.lock new file mode 100644 index 000000000..e69de29bb diff --git a/nova-manage.conf b/nova-manage.conf new file mode 100644 index 000000000..6942b8cc6 --- /dev/null +++ b/nova-manage.conf @@ -0,0 +1,26 @@ +--verbose +--nodaemon +--FAKE_subdomain=ec2 +--ca_path=/home/salvatore/openstack/nova/CA +--keys_path=/home/salvatore/openstack/nova/keys +--networks_path=/home/salvatore/openstack/nova/networks +--images_path=/home/salvatore/openstack/nova/images +--buckets_path=/home/salvatore/openstack/nova/buckets +--logdir=/home/salvatore/openstack/nova +#--libvirt_type=qemu +--connection_type=xenapi +--xenapi_connection_url=http://camctxlabs-svr05.cam.eu.citrix.com +--xenapi_connection_password=_car0pet_ +--auth_driver=nova.auth.dbdriver.DbDriver +--sql_connection=mysql://root:nova@10.70.177.19/nova +--s3_host=10.70.177.19 +--network_manager=nova.network.manager.FlatManager +#--network_manager=nova.network.manager.VlanManager +--fixed_range=192.168.1.240/28 +--routing_source_ip=10.70.177.19 +--flat_network_bridge=xenbr0 +--flat_network_gateway=192.168.1.1 +--flat_network_netmask=255.255.255.0 +--flat_network_network=192.168.1.0 +--flat_network_ips=192.168.1.220,192.168.1.221,192.168.1.222 +--flat_network_broadcast=192.168.1.255 diff --git a/nova-network.pid b/nova-network.pid new file mode 100644 index 000000000..b81b10821 --- /dev/null +++ b/nova-network.pid @@ -0,0 +1 @@ +16479 \ No newline at end of file diff --git a/nova-objectstore.pid b/nova-objectstore.pid new file mode 100644 index 000000000..0967315fe --- /dev/null +++ b/nova-objectstore.pid @@ -0,0 +1 @@ +16469 \ No newline at end of file diff --git a/nova-scheduler.pid b/nova-scheduler.pid new file mode 100644 index 000000000..d6bb441a4 --- /dev/null +++ b/nova-scheduler.pid @@ -0,0 +1 @@ +16490 \ No newline at end of file From fa1168c554da9975757839e6a61313c71426b8f4 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Wed, 15 Dec 2010 14:27:07 +0000 Subject: [PATCH 03/39] removed temporary comment lines --- nova/flags.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index 1cb846c15..bd2fe4b99 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -237,10 +237,6 @@ DEFINE_string('default_instance_type', 'm1.small', DEFINE_string('null_kernel', 'aki-00000000', 'kernel image that indicates not to use a kernel, ' ' but to use a raw disk image instead') -DEFINE_string('null_kernel_pv', 'aki-00000001', - 'kernel image that indicates not to use a kernel, ' - ' but to use a raw disk image for a paravirtualized guest') - DEFINE_string('vpn_image_id', 'ami-CLOUDPIPE', 'AMI for cloudpipe vpn server') DEFINE_string('vpn_key_suffix', From f1e85ce77914f35f59f498daca38db617eabba66 Mon Sep 17 00:00:00 2001 From: David Pravec Date: Thu, 16 Dec 2010 12:35:46 +0100 Subject: [PATCH 04/39] Make nova work even when user has LANG or LC_ALL configured Some commands are having different results when used in another language environment. For example ifconfig output parsing fails in my language. Also unittest using cat failed, as it didnt expect czech language in the error message. This small patch makes it work. Also adding myself to 'Authors' file. --- Authors | 1 + nova/tests/process_unittest.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Authors b/Authors index 565444ee1..6e0a7735f 100644 --- a/Authors +++ b/Authors @@ -4,6 +4,7 @@ Anthony Young Armando Migliaccio Chris Behrens Chmouel Boudjnah +David Pravec Dean Troyer Devin Carlen Eldar Nugaev diff --git a/nova/tests/process_unittest.py b/nova/tests/process_unittest.py index 67245af03..516b2452f 100644 --- a/nova/tests/process_unittest.py +++ b/nova/tests/process_unittest.py @@ -49,7 +49,7 @@ class ProcessTestCase(test.TrialTestCase): def test_execute_stderr(self): pool = process.ProcessPool(2) - d = pool.simple_execute('cat BAD_FILE', check_exit_code=False) + d = pool.simple_execute('LC_ALL=C cat BAD_FILE', check_exit_code=False) def _check(rv): self.assertEqual(rv[0], '') From 17fb41547517a88eb18d435c1463e34213865f3c Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 17 Dec 2010 19:17:39 -0600 Subject: [PATCH 05/39] Cleaned up TODOs, using flags now --- nova/flags.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/flags.py b/nova/flags.py index 87444565a..3318af058 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -212,6 +212,9 @@ DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID') DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key') DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', '127.0.0.1', 's3 host') +DEFINE_integer('glance_port', 9292, 'glance port') +DEFINE_string('glance_host', '127.0.0.1', 'glance host') +DEFINE_string('glance_storage_location', 'swift://username:api_key@auth.api.rackspacecloud.com/v1.0/cloudservers', 'glance storage location') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') From a36b52664ade2293e4f63b7bd78aaa7a08051ebf Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Mon, 20 Dec 2010 15:41:43 +0000 Subject: [PATCH 06/39] Fixed pep8 errors --- nova/tests/virt_unittest.py | 43 +++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py index 9bbba4ba9..e5d5f00c8 100644 --- a/nova/tests/virt_unittest.py +++ b/nova/tests/virt_unittest.py @@ -129,44 +129,41 @@ class LibvirtConnTestCase(test.TestCase): check_list.append(check) else: if expect_kernel: - check = (lambda t: t.find('./os/kernel').text.split('/' - )[1], 'kernel') + check = (lambda t: t.find('./os/kernel').text.split('/') + [1], 'kernel') else: check = (lambda t: t.find('./os/kernel'), None) check_list.append(check) if expect_ramdisk: - check = (lambda t: t.find('./os/initrd').text.split('/' - )[1], 'ramdisk') + check = (lambda t: t.find('./os/initrd').text.split('/') + [1], 'ramdisk') else: check = (lambda t: t.find('./os/initrd'), None) check_list.append(check) common_checks = [ (lambda t: t.find('.').tag, 'domain'), - (lambda t: t.find('./devices/interface/filterref/parameter' - ).get('name'), 'IP'), - (lambda t: t.find('./devices/interface/filterref/parameter' - ).get('value'), '10.11.12.13'), - (lambda t: t.findall('./devices/interface/filterref/parameter' - )[1].get('name'), 'DHCPSERVER'), - (lambda t: t.findall('./devices/interface/filterref/parameter' - )[1].get('value'), '10.0.0.1'), - (lambda t: t.find('./devices/serial/source').get('path' - ).split('/')[1], 'console.log'), + (lambda t: t.find('./devices/interface/filterref/parameter') + .get('name'), 'IP'), + (lambda t: t.find('./devices/interface/filterref/parameter') + .get('value'), '10.11.12.13'), + (lambda t: t.findall('./devices/interface/filterref/parameter') + [1].get('name'), 'DHCPSERVER'), + (lambda t: t.findall('./devices/interface/filterref/parameter') + [1].get('value'), '10.0.0.1'), + (lambda t: t.find('./devices/serial/source').get('path') + .split('/')[1], 'console.log'), (lambda t: t.find('./memory').text, '2097152')] if rescue: - common_checks += [(lambda t: t.findall('./devices/disk/source' - )[0].get('file').split('/')[1], - 'rescue-disk'), - (lambda t: t.findall('./devices/disk/source' - )[1].get('file').split('/')[1], - 'disk')] + common_checks += [(lambda t: t.findall('./devices/disk/source') + [0].get('file').split('/')[1], 'rescue-disk'), + (lambda t: t.findall('./devices/disk/source') + [1].get('file').split('/')[1], 'disk')] else: - common_checks += [(lambda t: t.findall('./devices/disk/source' - )[0].get('file').split('/')[1], - 'disk')] + common_checks += [(lambda t: t.findall('./devices/disk/source') + [0].get('file').split('/')[1], 'disk')] for (libvirt_type, (expected_uri, checks)) in type_uri_map.iteritems(): FLAGS.libvirt_type = libvirt_type From a14b10a52b77029ac84ac9784c127c74de9bb10e Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Mon, 20 Dec 2010 22:55:11 +0000 Subject: [PATCH 07/39] added suspend and resume --- nova/tests/compute_unittest.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index 187ca31de..111a43cdc 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -135,6 +135,14 @@ class ComputeTestCase(test.TestCase): self.compute.unpause_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id) + def test_suspend(self): + """ensure instance can be suspended""" + instance_id = self._create_instance() + self.compute.run_instance(self.context, instance_id) + self.compute.suspend_instance(self.context, instance_id) + self.compute.resume_instance(self.context, instance_id) + self.compute.terminate_instance(self.context, instance_id) + def test_reboot(self): """Ensure instance can be rebooted""" instance_id = self._create_instance() From 2abb417973574d73eefd5535f47eee03272492c6 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 21 Dec 2010 01:41:28 -0500 Subject: [PATCH 08/39] Use paste.deploy for running the api server. --- bin/nova-api-paste | 103 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100755 bin/nova-api-paste diff --git a/bin/nova-api-paste b/bin/nova-api-paste new file mode 100755 index 000000000..dcb76522f --- /dev/null +++ b/bin/nova-api-paste @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# pylint: disable-msg=C0103 +# 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. + +"""Starter script for Nova API.""" + +import gettext +import logging +import os +import sys + +from paste import deploy + +from nova import flags +from nova import wsgi + +# If ../nova/__init__.py exists, add ../ to Python search path, so that +# it will override what happens to be installed in /usr/(local/)lib/python... +possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), + os.pardir, + os.pardir)) +if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): + sys.path.insert(0, possible_topdir) + +gettext.install('nova', unicode=1) + +LOG = logging.getLogger('nova.api') +LOG.setLevel(logging.DEBUG) +LOG.addHandler(logging.StreamHandler()) + +FLAGS = flags.FLAGS + +API_ENDPOINTS = ['ec2', 'openstack'] + +def load_configuration(paste_config): + """Load the paste configuration from the config file and return it.""" + config = None + # Try each known name to get the global DEFAULTS, which will give ports + for name in API_ENDPOINTS: + try: + config = deploy.appconfig("config:%s" % paste_config, name=name) + except LookupError: + pass + if config: + verbose = config.get('verbose', None) + if verbose: + FLAGS.verbose = int(verbose) == 1 + if FLAGS.verbose: + logging.getLogger().setLevel(logging.DEBUG) + return config + LOG.debug("Paste config at %s has no secion for known apis", paste_config) + print "Paste config at %s has no secion for any known apis" % paste_config + os.exit(1) + +def launch_api(paste_config_file, section, server, port, host): + """Launch an api server from the specified port and IP.""" + LOG.debug("Launching api %s on %s:%s", section, host, port) + app = deploy.loadapp('config:%s' % paste_config_file, name=section) + server.start(app, int(port), host) + +def run_app(paste_config_file): + LOG.debug("Using paste.deploy config at: %s", configfile) + config = load_configuration(paste_config_file) + LOG.debug("Configuration: %r", config) + server = wsgi.Server() + ip = config.get('host', None) + for api in API_ENDPOINTS: + port = config.get("%s_port" % api, None) + if not port: + continue + host = config.get("%s_host" % api, None) or ip or '0.0.0.0' + launch_api(configfile, api, server, port, host) + LOG.debug("All api servers launched, now waiting") + server.wait() + +if __name__ == '__main__': + FLAGS(sys.argv) + configfiles = ['/etc/nova/nova-api.conf'] + if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): + configfiles.insert(0, + os.path.join(possible_topdir, 'etc', 'nova-api.conf')) + for configfile in configfiles: + if os.path.exists(configfile): + run_app(configfile) + break + else: + LOG.debug("Skipping missing configuration: %s", configfile) From 4c7492e5eff96f54f0956b77fe7c7fcd7d6e9ede Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 21 Dec 2010 19:20:28 -0500 Subject: [PATCH 09/39] Burnin support by specifying a specific host via availability_zone for running instances and volumes on. --- bin/nova-manage | 50 +++++++++++++- nova/scheduler/driver.py | 5 ++ nova/scheduler/simple.py | 26 +++++++ nova/tests/scheduler_unittest.py | 112 +++++++++++++++++++++++++++++-- 4 files changed, 188 insertions(+), 5 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 0c1b621ed..34bdd3df9 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -53,6 +53,7 @@ CLI interface for nova management. """ +import datetime import gettext import logging import os @@ -432,6 +433,52 @@ class NetworkCommands(object): int(network_size), int(vlan_start), int(vpn_start)) + +class ServiceCommands(object): + """Enable and disable running services""" + + def list(self, host=None, service=None): + """Show a list of all running services. Filter by host & service name. + args: [host] [service]""" + ctxt = context.get_admin_context() + now = datetime.datetime.utcnow() + services = db.service_get_all(ctxt) + if host: + services = [s for s in services if s['host'] == host] + if service: + services = [s for s in services if s['binary'] == service] + for svc in services: + delta = now - (svc['updated_at'] or svc['created_at']) + alive = (delta.seconds <= 15) + art = (alive and ":-)") or "XXX" + active = 'enabled' + if svc['disabled']: + active = 'disabled' + print "%-10s %-10s %-8s %s %s" % (svc['host'], svc['binary'], + active, art, + svc['updated_at']) + + def enable(self, host, service): + """Enable scheduling for a service + args: host service""" + ctxt = context.get_admin_context() + svc = db.service_get_by_args(ctxt, host, service) + if not svc: + print "Unable to find service" + return + db.service_update(ctxt, svc['id'], {'disabled': False}) + + def disable(self, host, service): + """Disable scheduling for a service + args: host service""" + ctxt = context.get_admin_context() + svc = db.service_get_by_args(ctxt, host, service) + if not svc: + print "Unable to find service" + return + db.service_update(ctxt, svc['id'], {'disabled': True}) + + CATEGORIES = [ ('user', UserCommands), ('project', ProjectCommands), @@ -439,7 +486,8 @@ CATEGORIES = [ ('shell', ShellCommands), ('vpn', VpnCommands), ('floating', FloatingIpCommands), - ('network', NetworkCommands)] + ('network', NetworkCommands), + ('service', ServiceCommands)] def lazy_match(name, key_value_tuples): diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py index f271d573f..340f40310 100644 --- a/nova/scheduler/driver.py +++ b/nova/scheduler/driver.py @@ -37,6 +37,11 @@ class NoValidHost(exception.Error): pass +class WillNotSchedule(exception.Error): + """The specified host is not up or doesn't exist.""" + pass + + class Scheduler(object): """The base class that all Scheduler clases should inherit from.""" diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index 7f5093656..9e85ba952 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -43,6 +43,19 @@ class SimpleScheduler(chance.ChanceScheduler): def schedule_run_instance(self, context, instance_id, *_args, **_kwargs): """Picks a host that is up and has the fewest running instances.""" instance_ref = db.instance_get(context, instance_id) + if instance_ref['availability_zone'] and context.is_admin: + zone, _x, host = instance_ref['availability_zone'].partition(':') + service = db.service_get_by_args(context.elevated(), host, + 'nova-compute') + if not self.service_is_up(service): + raise driver.WillNotSchedule("Host %s is not alive" % host) + + # TODO(vish): this probably belongs in the manager, if we + # can generalize this somehow + now = datetime.datetime.utcnow() + db.instance_update(context, instance_id, {'host': host, + 'scheduled_at': now}) + return host results = db.service_get_all_compute_sorted(context) for result in results: (service, instance_cores) = result @@ -62,6 +75,19 @@ class SimpleScheduler(chance.ChanceScheduler): def schedule_create_volume(self, context, volume_id, *_args, **_kwargs): """Picks a host that is up and has the fewest volumes.""" volume_ref = db.volume_get(context, volume_id) + if (':' in volume_ref['availability_zone']) and context.is_admin: + zone, _x, host = volume_ref['availability_zone'].partition(':') + service = db.service_get_by_args(context.elevated(), host, + 'nova-volume') + if not self.service_is_up(service): + raise driver.WillNotSchedule("Host %s not available" % host) + + # TODO(vish): this probably belongs in the manager, if we + # can generalize this somehow + now = datetime.datetime.utcnow() + db.volume_update(context, volume_id, {'host': host, + 'scheduled_at': now}) + return host results = db.service_get_all_volume_sorted(context) for result in results: (service, volume_gigabytes) = result diff --git a/nova/tests/scheduler_unittest.py b/nova/tests/scheduler_unittest.py index d1756b8fb..92262cc7d 100644 --- a/nova/tests/scheduler_unittest.py +++ b/nova/tests/scheduler_unittest.py @@ -19,6 +19,8 @@ Tests For Scheduler """ +import datetime + from nova import context from nova import db from nova import flags @@ -93,7 +95,7 @@ class SimpleDriverTestCase(test.TestCase): self.manager.delete_user(self.user) self.manager.delete_project(self.project) - def _create_instance(self): + def _create_instance(self, **kwargs): """Create a test instance""" inst = {} inst['image_id'] = 'ami-test' @@ -104,6 +106,7 @@ class SimpleDriverTestCase(test.TestCase): inst['mac_address'] = utils.generate_mac() inst['ami_launch_index'] = 0 inst['vcpus'] = 1 + inst['availability_zone'] = kwargs.get('availability_zone', None) return db.instance_create(self.context, inst)['id'] def _create_volume(self): @@ -112,10 +115,11 @@ class SimpleDriverTestCase(test.TestCase): vol['image_id'] = 'ami-test' vol['reservation_id'] = 'r-fakeres' vol['size'] = 1 + vol['availability_zone'] = 'test' return db.volume_create(self.context, vol)['id'] - def test_hosts_are_up(self): - """Ensures driver can find the hosts that are up""" + def test_doesnt_report_disabled_hosts_as_up(self): + """Ensures driver doesn't find hosts before they are enabled""" # NOTE(vish): constructing service without create method # because we are going to use it without queue compute1 = service.Service('host1', @@ -129,7 +133,30 @@ class SimpleDriverTestCase(test.TestCase): FLAGS.compute_manager) compute2.start() hosts = self.scheduler.driver.hosts_up(self.context, 'compute') - self.assertEqual(len(hosts), 2) + self.assertEqual(0, len(hosts)) + compute1.kill() + compute2.kill() + + def test_reports_enabled_hosts_as_up(self): + """Ensures driver can find the hosts that are up""" + # NOTE(vish): constructing service without create method + # because we are going to use it without queue + compute1 = service.Service('host1', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute1.start() + compute2 = service.Service('host2', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute2.start() + s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') + s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') + db.service_update(self.context, s1['id'], {'disabled': False}) + db.service_update(self.context, s2['id'], {'disabled': False}) + hosts = self.scheduler.driver.hosts_up(self.context, 'compute') + self.assertEqual(2, len(hosts)) compute1.kill() compute2.kill() @@ -145,6 +172,10 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute2.start() + s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') + s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') + db.service_update(self.context, s1['id'], {'disabled': False}) + db.service_update(self.context, s2['id'], {'disabled': False}) instance_id1 = self._create_instance() compute1.run_instance(self.context, instance_id1) instance_id2 = self._create_instance() @@ -156,6 +187,67 @@ class SimpleDriverTestCase(test.TestCase): compute1.kill() compute2.kill() + def test_specific_host_gets_instance(self): + """Ensures if you set availability_zone it launches on that zone""" + compute1 = service.Service('host1', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute1.start() + compute2 = service.Service('host2', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute2.start() + s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') + s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') + db.service_update(self.context, s1['id'], {'disabled': False}) + db.service_update(self.context, s2['id'], {'disabled': False}) + instance_id1 = self._create_instance() + compute1.run_instance(self.context, instance_id1) + instance_id2 = self._create_instance(availability_zone='nova:host1') + host = self.scheduler.driver.schedule_run_instance(self.context, + instance_id2) + self.assertEqual('host1', host) + compute1.terminate_instance(self.context, instance_id1) + db.instance_destroy(self.context, instance_id2) + compute1.kill() + compute2.kill() + + def test_wont_sechedule_if_specified_host_is_down(self): + compute1 = service.Service('host1', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute1.start() + s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') + now = datetime.datetime.utcnow() + delta = datetime.timedelta(seconds=FLAGS.service_down_time * 2) + past = now - delta + db.service_update(self.context, s1['id'], {'disabled': False, + 'updated_at': past}) + instance_id2 = self._create_instance(availability_zone='nova:host1') + self.assertRaises(driver.WillNotSchedule, + self.scheduler.driver.schedule_run_instance, + self.context, + instance_id2) + db.instance_destroy(self.context, instance_id2) + compute1.kill() + + def test_will_schedule_on_disabled_host_if_specified(self): + compute1 = service.Service('host1', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute1.start() + db.service_get_by_args(self.context, 'host1', 'nova-compute') + instance_id2 = self._create_instance(availability_zone='nova:host1') + host = self.scheduler.driver.schedule_run_instance(self.context, + instance_id2) + self.assertEqual('host1', host) + db.instance_destroy(self.context, instance_id2) + compute1.kill() + def test_too_many_cores(self): """Ensures we don't go over max cores""" compute1 = service.Service('host1', @@ -168,6 +260,10 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute2.start() + s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') + s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') + db.service_update(self.context, s1['id'], {'disabled': False}) + db.service_update(self.context, s2['id'], {'disabled': False}) instance_ids1 = [] instance_ids2 = [] for index in xrange(FLAGS.max_cores): @@ -201,6 +297,10 @@ class SimpleDriverTestCase(test.TestCase): 'volume', FLAGS.volume_manager) volume2.start() + s1 = db.service_get_by_args(self.context, 'host1', 'nova-volume') + s2 = db.service_get_by_args(self.context, 'host2', 'nova-volume') + db.service_update(self.context, s1['id'], {'disabled': False}) + db.service_update(self.context, s2['id'], {'disabled': False}) volume_id1 = self._create_volume() volume1.create_volume(self.context, volume_id1) volume_id2 = self._create_volume() @@ -224,6 +324,10 @@ class SimpleDriverTestCase(test.TestCase): 'volume', FLAGS.volume_manager) volume2.start() + s1 = db.service_get_by_args(self.context, 'host1', 'nova-volume') + s2 = db.service_get_by_args(self.context, 'host2', 'nova-volume') + db.service_update(self.context, s1['id'], {'disabled': False}) + db.service_update(self.context, s2['id'], {'disabled': False}) volume_ids1 = [] volume_ids2 = [] for index in xrange(FLAGS.max_gigabytes): From 737ddb99c01d9ae34abb7d29e0dbfa7952f01745 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Wed, 22 Dec 2010 13:27:51 -0500 Subject: [PATCH 10/39] Adding myself and Antony Messerli to the Authors file --- Authors | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Authors b/Authors index fa38ef0b1..6e873acf3 100644 --- a/Authors +++ b/Authors @@ -1,9 +1,11 @@ Andy Smith Anne Gentle Anthony Young +Antony Messerli Armando Migliaccio Chris Behrens Chmouel Boudjnah +Cory Wright Dean Troyer Devin Carlen Ed Leafe From 5a917a0492249a71cdd3378c464b95535781ab30 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 22 Dec 2010 13:01:33 -0600 Subject: [PATCH 11/39] Getting Snapshots to work with cloudservers command-line tool --- nova/flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/flags.py b/nova/flags.py index 51956d5cf..cf4614b8d 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -214,7 +214,6 @@ DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', '127.0.0.1', 's3 host') DEFINE_integer('glance_port', 9292, 'glance port') DEFINE_string('glance_host', '127.0.0.1', 'glance host') -DEFINE_string('glance_storage_location', 'swift://username:api_key@auth.api.rackspacecloud.com/v1.0/cloudservers', 'glance storage location') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') @@ -236,6 +235,7 @@ DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud', 'Url to ec2 api server') +DEFINE_string('default_project', 'openstack', 'default project for openstack') DEFINE_string('default_image', 'ami-11111', 'default image to use, testing only') DEFINE_string('default_instance_type', 'm1.small', From e69290247aaf512bef7b8dbeabb45c0e38ca9554 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 22 Dec 2010 14:00:31 -0600 Subject: [PATCH 12/39] i18n support for xs-snaps --- nova/flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/flags.py b/nova/flags.py index b61447c76..d773a7e4c 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -213,7 +213,7 @@ DEFINE_string('connection_type', 'libvirt', 'libvirt, xenapi or fake') DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID') DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key') DEFINE_integer('glance_port', 9292, 'glance port') -DEFINE_string('glance_host', utils.get_my_ip(),, 'glance host') +DEFINE_string('glance_host', utils.get_my_ip(), 'glance host') DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', utils.get_my_ip(), 's3 host (for infrastructure)') DEFINE_string('s3_dmz', utils.get_my_ip(), 's3 dmz ip (for instances)') From de621357871a6c3c5fd4528b4e0e46d48ea40d64 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 22 Dec 2010 14:54:02 -0600 Subject: [PATCH 13/39] Adding more comments regarding XS snapshots --- Authors | 1 + nova/tests/compute_unittest.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/Authors b/Authors index 0b048becb..d353b58ff 100644 --- a/Authors +++ b/Authors @@ -24,6 +24,7 @@ Michael Gundlach Monty Taylor Paul Voccio Rick Clark +Rick Harris Ryan Lucio Sandy Walsh Soren Hansen diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index 187ca31de..025291a92 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -142,6 +142,14 @@ class ComputeTestCase(test.TestCase): self.compute.reboot_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id) + def test_snapshot(self): + """Ensure instance can be snapshotted""" + instance_id = self._create_instance() + name = "myfakesnapshot" + self.compute.run_instance(self.context, instance_id) + self.compute.snapshot_instance(self.context, instance_id, name) + self.compute.terminate_instance(self.context, instance_id) + def test_console_output(self): """Make sure we can get console output from instance""" instance_id = self._create_instance() From b6d2e2a833f86af4293e4033ada15d697270988f Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 23 Dec 2010 20:35:16 +0000 Subject: [PATCH 14/39] added _() for gettext and a couple of pep8s --- nova/tests/compute_unittest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index 111a43cdc..14954c3a2 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -100,13 +100,13 @@ class ComputeTestCase(test.TestCase): self.compute.run_instance(self.context, instance_id) instances = db.instance_get_all(context.get_admin_context()) - logging.info("Running instances: %s", instances) + logging.info(_("Running instances: %s"), instances) self.assertEqual(len(instances), 1) self.compute.terminate_instance(self.context, instance_id) instances = db.instance_get_all(context.get_admin_context()) - logging.info("After terminating instances: %s", instances) + logging.info(_("After terminating instances: %s"), instances) self.assertEqual(len(instances), 0) def test_run_terminate_timestamps(self): From 24ad0544d9f6c5b729b8f7f123af69e3dc9b1a17 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Fri, 24 Dec 2010 19:17:02 +0000 Subject: [PATCH 15/39] remove requirement of sudo on tests --- nova/tests/test_xenapi.py | 1 + nova/tests/xenapi/stubs.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index b5d3ea395..ed2e4ffde 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -48,6 +48,7 @@ class XenAPIVolumeTestCase(test.TestCase): FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' fakes.stub_out_db_instance_api(self.stubs) + stubs.stub_out_get_target(self.stubs) fake.reset() self.values = {'name': 1, 'id': 1, 'project_id': 'fake', diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 1dacad6a3..a7e592fee 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -18,12 +18,13 @@ from nova.virt import xenapi_conn from nova.virt.xenapi import fake +from nova.virt.xenapi import volume_utils def stubout_session(stubs, cls): - """ Stubs out two methods from XenAPISession """ + """Stubs out two methods from XenAPISession""" def fake_import(self): - """ Stubs out get_imported_xenapi of XenAPISession """ + """Stubs out get_imported_xenapi of XenAPISession""" fake_module = 'nova.virt.xenapi.fake' from_list = ['fake'] return __import__(fake_module, globals(), locals(), from_list, -1) @@ -34,6 +35,14 @@ def stubout_session(stubs, cls): fake_import) +def stub_out_get_target(stubs): + """Stubs out _get_target in volume_utils""" + def fake_get_target(volume_id): + return (None, None) + + stubs.Set(volume_utils, '_get_target', fake_get_target) + + class FakeSessionForVMTests(fake.SessionBase): """ Stubs out a XenAPISession for VM tests """ def __init__(self, uri): From ca3ae003a490b1eed526b39ad7b317734ebe4d3b Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Sun, 26 Dec 2010 14:08:38 +0000 Subject: [PATCH 16/39] logs inner exception in nova/utils.py->import_class --- nova/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/utils.py b/nova/utils.py index b9045a50c..15112faa2 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -48,7 +48,8 @@ def import_class(import_str): try: __import__(mod_str) return getattr(sys.modules[mod_str], class_str) - except (ImportError, ValueError, AttributeError): + except (ImportError, ValueError, AttributeError), exc: + logging.debug(_('Inner Exception: %s'), exc) raise exception.NotFound(_('Class %s cannot be found') % class_str) From 2d5ceeb553a33d58d6997ee340a0dbd7b035e2d5 Mon Sep 17 00:00:00 2001 From: Ewan Mellor Date: Tue, 28 Dec 2010 01:09:42 +0000 Subject: [PATCH 17/39] Bug #694890: run_tests.sh sometimes doesn't pass arguments to nosetest Change the argument parsing in run_tests.sh so that we explicitly gather the arguments that aren't meant for run_tests.sh, and pass them on to nosetests. --- run_tests.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/run_tests.sh b/run_tests.sh index 67214996d..ffb0b6295 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -21,6 +21,7 @@ function process_option { -V|--virtual-env) let always_venv=1; let never_venv=0;; -N|--no-virtual-env) let always_venv=0; let never_venv=1;; -f|--force) let force=1;; + *) noseargs="$noseargs $1" esac } @@ -29,15 +30,18 @@ with_venv=tools/with_venv.sh always_venv=0 never_venv=0 force=0 +noseargs= for arg in "$@"; do process_option $arg done +NOSETESTS="nosetests -v $noseargs" + if [ $never_venv -eq 1 ]; then # Just run the test suites in current environment rm -f nova.sqlite - nosetests -v + $NOSETESTS exit fi @@ -49,7 +53,7 @@ fi if [ -e ${venv} ]; then ${with_venv} rm -f nova.sqlite - ${with_venv} nosetests -v $@ + ${with_venv} $NOSETESTS else if [ $always_venv -eq 1 ]; then # Automatically install the virtualenv @@ -62,10 +66,10 @@ else python tools/install_venv.py else rm -f nova.sqlite - nosetests -v + $NOSETESTS exit fi fi ${with_venv} rm -f nova.sqlite - ${with_venv} nosetests -v $@ + ${with_venv} $NOSETESTS fi From 210fc5f1f8527484e669e51ecba206dd951ba09f Mon Sep 17 00:00:00 2001 From: Ewan Mellor Date: Tue, 28 Dec 2010 01:37:04 +0000 Subject: [PATCH 18/39] Bug #694880: nova-compute now depends upon Cheetah even when not using libvirt Only import Cheetah when needed, as we do already with libvirt and libxml2. This ensures that users of other virt backends don't need Cheetah to run nova-compute. --- nova/tests/test_virt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 1c155abe4..4aa489d08 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -33,6 +33,7 @@ flags.DECLARE('instances_path', 'nova.compute.manager') class LibvirtConnTestCase(test.TestCase): def setUp(self): super(LibvirtConnTestCase, self).setUp() + libvirt_conn._late_load_cheetah() self.flags(fake_call=True) self.manager = manager.AuthManager() self.user = self.manager.create_user('fake', 'fake', 'fake', From ea3acc179ea5eec9166c21dca5b386cfdb7051b9 Mon Sep 17 00:00:00 2001 From: termie Date: Tue, 28 Dec 2010 01:40:24 -0800 Subject: [PATCH 19/39] Output of run_tests.sh to be closer to trial --- run_tests.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++ run_tests.sh | 10 ++++---- 2 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 run_tests.py diff --git a/run_tests.py b/run_tests.py new file mode 100644 index 000000000..56a8bffe7 --- /dev/null +++ b/run_tests.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# 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 os +import unittest +import sys + +from nose import config +from nose import result +from nose import core + + +class NovaTestResult(result.TextTestResult): + def __init__(self, *args, **kw): + result.TextTestResult.__init__(self, *args, **kw) + self._last_case = None + + def getDescription(self, test): + return str(test) + + def startTest(self, test): + unittest.TestResult.startTest(self, test) + current_case = test.test.__class__.__name__ + + if self.showAll: + if current_case != self._last_case: + self.stream.writeln(current_case) + self._last_case = current_case + + self.stream.write( + ' %s' % str(test.test._testMethodName).ljust(60)) + self.stream.flush() + + +class NovaTestRunner(core.TextTestRunner): + def _makeResult(self): + return NovaTestResult(self.stream, + self.descriptions, + self.verbosity, + self.config) + + +if __name__ == '__main__': + c = config.Config(stream=sys.stdout, + env=os.environ, + verbosity=3) + + runner = NovaTestRunner(stream=c.stream, + verbosity=c.verbosity, + config=c) + sys.exit(core.run(config=c, testRunner=runner)) diff --git a/run_tests.sh b/run_tests.sh index 67214996d..2e536a636 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -30,6 +30,7 @@ always_venv=0 never_venv=0 force=0 + for arg in "$@"; do process_option $arg done @@ -37,7 +38,7 @@ done if [ $never_venv -eq 1 ]; then # Just run the test suites in current environment rm -f nova.sqlite - nosetests -v + python run_tests.py $@ 2> run_tests.err.log exit fi @@ -49,7 +50,7 @@ fi if [ -e ${venv} ]; then ${with_venv} rm -f nova.sqlite - ${with_venv} nosetests -v $@ + ${with_venv} python run_tests.py $@ 2> run_tests.err.log else if [ $always_venv -eq 1 ]; then # Automatically install the virtualenv @@ -62,10 +63,11 @@ else python tools/install_venv.py else rm -f nova.sqlite - nosetests -v + #nosetests -v + python run_tests.py 2> run_tests.err.log exit fi fi ${with_venv} rm -f nova.sqlite - ${with_venv} nosetests -v $@ + ${with_venv} python run_tests.py $@ 2> run_tests.err.log fi From 690970f44b6143b7730c03b64e83fca29f580baa Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Tue, 28 Dec 2010 12:53:32 -0600 Subject: [PATCH 20/39] Add some basic snapshot tests --- nova/tests/test_xenapi.py | 108 +++++++++++++++++++++++++------------ nova/tests/xenapi/stubs.py | 66 +++++++++++++++++++++++ 2 files changed, 140 insertions(+), 34 deletions(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index ed2e4ffde..f0d84e9aa 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -29,9 +29,9 @@ from nova.auth import manager from nova.compute import instance_types from nova.compute import power_state from nova.virt import xenapi_conn -from nova.virt.xenapi import fake +from nova.virt.xenapi import fake as xenapi_fake from nova.virt.xenapi import volume_utils -from nova.tests.db import fakes +from nova.tests.db import fakes as db_fakes from nova.tests.xenapi import stubs FLAGS = flags.FLAGS @@ -47,9 +47,9 @@ class XenAPIVolumeTestCase(test.TestCase): FLAGS.target_host = '127.0.0.1' FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' - fakes.stub_out_db_instance_api(self.stubs) + db_fakes.stub_out_db_instance_api(self.stubs) stubs.stub_out_get_target(self.stubs) - fake.reset() + xenapi_fake.reset() self.values = {'name': 1, 'id': 1, 'project_id': 'fake', 'user_id': 'fake', @@ -83,7 +83,7 @@ class XenAPIVolumeTestCase(test.TestCase): label = 'SR-%s' % vol['ec2_id'] description = 'Test-SR' sr_ref = helper.create_iscsi_storage(session, info, label, description) - srs = fake.get_all('SR') + srs = xenapi_fake.get_all('SR') self.assertEqual(sr_ref, srs[0]) db.volume_destroy(context.get_admin_context(), vol['id']) @@ -107,17 +107,17 @@ class XenAPIVolumeTestCase(test.TestCase): conn = xenapi_conn.get_connection(False) volume = self._create_volume() instance = db.instance_create(self.values) - fake.create_vm(instance.name, 'Running') + xenapi_fake.create_vm(instance.name, 'Running') result = conn.attach_volume(instance.name, volume['ec2_id'], '/dev/sdc') def check(): # check that the VM has a VBD attached to it # Get XenAPI reference for the VM - vms = fake.get_all('VM') + vms = xenapi_fake.get_all('VM') # Get XenAPI record for VBD - vbds = fake.get_all('VBD') - vbd = fake.get_record('VBD', vbds[0]) + vbds = xenapi_fake.get_all('VBD') + vbd = xenapi_fake.get_record('VBD', vbds[0]) vm_ref = vbd['VM'] self.assertEqual(vm_ref, vms[0]) @@ -130,7 +130,7 @@ class XenAPIVolumeTestCase(test.TestCase): conn = xenapi_conn.get_connection(False) volume = self._create_volume() instance = db.instance_create(self.values) - fake.create_vm(instance.name, 'Running') + xenapi_fake.create_vm(instance.name, 'Running') self.assertRaises(Exception, conn.attach_volume, instance.name, @@ -156,41 +156,66 @@ class XenAPIVMTestCase(test.TestCase): self.stubs = stubout.StubOutForTesting() FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' - fake.reset() - fakes.stub_out_db_instance_api(self.stubs) - fake.create_network('fake', FLAGS.flat_network_bridge) + xenapi_fake.reset() + db_fakes.stub_out_db_instance_api(self.stubs) + xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) + stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) + self.conn = xenapi_conn.get_connection(False) def test_list_instances_0(self): - stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - conn = xenapi_conn.get_connection(False) - instances = conn.list_instances() + instances = self.conn.list_instances() self.assertEquals(instances, []) - def test_spawn(self): - stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - values = {'name': 1, 'id': 1, - 'project_id': self.project.id, - 'user_id': self.user.id, - 'image_id': 1, - 'kernel_id': 2, - 'ramdisk_id': 3, - 'instance_type': 'm1.large', - 'mac_address': 'aa:bb:cc:dd:ee:ff', - } - conn = xenapi_conn.get_connection(False) - instance = db.instance_create(values) - conn.spawn(instance) + def test_instance_snapshot(self): + stubs.stubout_instance_snapshot(self.stubs) + instance = self._create_instance() + + name = "MySnapshot" + template_vm_ref = self.conn.snapshot(instance, name) + + def ensure_vm_was_torn_down(): + vm_labels = [] + for vm_ref in xenapi_fake.get_all('VM'): + vm_rec = xenapi_fake.get_record('VM', vm_ref) + if not vm_rec["is_control_domain"]: + vm_labels.append(vm_rec["name_label"]) + + self.assertEquals(vm_labels, [1]) + + def ensure_vbd_was_torn_down(): + vbd_labels = [] + for vbd_ref in xenapi_fake.get_all('VBD'): + vbd_rec = xenapi_fake.get_record('VBD', vbd_ref) + vbd_labels.append(vbd_rec["vm_name_label"]) + + self.assertEquals(vbd_labels, [1]) + + def ensure_vdi_was_torn_down(): + for vdi_ref in xenapi_fake.get_all('VDI'): + vdi_rec = xenapi_fake.get_record('VDI', vdi_ref) + name_label = vdi_rec["name_label"] + self.assert_(not name_label.endswith('snapshot')) def check(): - instances = conn.list_instances() + ensure_vm_was_torn_down() + ensure_vbd_was_torn_down() + ensure_vdi_was_torn_down() + + check() + + def test_spawn(self): + instance = self._create_instance() + + def check(): + instances = self.conn.list_instances() self.assertEquals(instances, [1]) # Get Nova record for VM - vm_info = conn.get_info(1) + vm_info = self.conn.get_info(1) # Get XenAPI record for VM - vms = fake.get_all('VM') - vm = fake.get_record('VM', vms[0]) + vms = xenapi_fake.get_all('VM') + vm = xenapi_fake.get_record('VM', vms[0]) # Check that m1.large above turned into the right thing. instance_type = instance_types.INSTANCE_TYPES['m1.large'] @@ -218,3 +243,18 @@ class XenAPIVMTestCase(test.TestCase): self.manager.delete_project(self.project) self.manager.delete_user(self.user) self.stubs.UnsetAll() + + def _create_instance(self): + """Creates and spawns a test instance""" + values = {'name': 1, 'id': 1, + 'project_id': self.project.id, + 'user_id': self.user.id, + 'image_id': 1, + 'kernel_id': 2, + 'ramdisk_id': 3, + 'instance_type': 'm1.large', + 'mac_address': 'aa:bb:cc:dd:ee:ff', + } + instance = db.instance_create(values) + self.conn.spawn(instance) + return instance diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index a7e592fee..55f751f11 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -19,6 +19,54 @@ from nova.virt import xenapi_conn from nova.virt.xenapi import fake from nova.virt.xenapi import volume_utils +from nova.virt.xenapi import vm_utils + + +def stubout_instance_snapshot(stubs): + @classmethod + def fake_fetch_image(cls, session, instance_id, image, user, project, + type): + # Stubout wait_for_task + def fake_wait_for_task(self, id, task): + class FakeEvent: + + def send(self, value): + self.rv = value + + def wait(self): + return self.rv + + done = FakeEvent() + self._poll_task(id, task, done) + rv = done.wait() + return rv + + stubs.Set(xenapi_conn.XenAPISession, 'wait_for_task', + fake_wait_for_task) + + from nova.virt.xenapi.fake import create_vdi + name_label = "instance-%s" % instance_id + #TODO: create fake SR record + sr_ref = "fakesr" + vdi_ref = create_vdi(name_label=name_label, read_only=False, + sr_ref=sr_ref, sharable=False) + vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) + vdi_uuid = vdi_rec['uuid'] + return vdi_uuid + + stubs.Set(vm_utils.VMHelper, 'fetch_image', fake_fetch_image) + + def fake_parse_xmlrpc_value(val): + return val + + stubs.Set(xenapi_conn, '_parse_xmlrpc_value', fake_parse_xmlrpc_value) + + def fake_wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, + original_parent_uuid): + #TODO(sirp): Should we actually fake out the data here + return "fakeparent" + + stubs.Set(vm_utils, 'wait_for_vhd_coalesce', fake_wait_for_vhd_coalesce) def stubout_session(stubs, cls): @@ -63,6 +111,24 @@ class FakeSessionForVMTests(fake.SessionBase): vm['is_a_template'] = False vm['is_control_domain'] = False + def VM_snapshot(self, session_ref, vm_ref, label): + status = "Running" + template_vm_ref = fake.create_vm(label, status, is_a_template=True, + is_control_domain=False) + + sr_ref = "fakesr" + template_vdi_ref = fake.create_vdi(label, read_only=True, + sr_ref=sr_ref, sharable=False) + + template_vbd_ref = fake.create_vbd(template_vm_ref, template_vdi_ref) + return template_vm_ref + + def VDI_destroy(self, session_ref, vdi_ref): + fake.destroy_vdi(vdi_ref) + + def VM_destroy(self, session_ref, vm_ref): + fake.destroy_vm(vm_ref) + class FakeSessionForVolumeTests(fake.SessionBase): """ Stubs out a XenAPISession for Volume tests """ From 98885e6417aeda12f595ccb89e0a3d7fcf2a298f Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Tue, 28 Dec 2010 16:35:56 -0500 Subject: [PATCH 21/39] Update .mailmap with both email addresses for Ant and myself --- .mailmap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.mailmap b/.mailmap index 9ab7db743..9e7fb1ec0 100644 --- a/.mailmap +++ b/.mailmap @@ -27,3 +27,5 @@ + + From ee6a718c0e45b2a574ee3209e601ee55c7fb95d9 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 28 Dec 2010 23:25:50 +0100 Subject: [PATCH 22/39] Address bug #695157 by using a blank request class and setting an empty request path. --- nova/wsgi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/wsgi.py b/nova/wsgi.py index c7ee9ed14..b5d6b96c1 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -270,7 +270,7 @@ class Serializer(object): needed to serialize a dictionary to that type. """ self.metadata = metadata or {} - req = webob.Request(environ) + req = webob.Request.blank('', environ) suffix = req.path_info.split('.')[-1].lower() if suffix == 'json': self.handler = self._to_json From 51175dd47d4e3aa872ac3dd138af4637395712d5 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 28 Dec 2010 17:54:31 -0500 Subject: [PATCH 23/39] Defualt services to enabled. --- nova/tests/test_scheduler.py | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index 65430fb36..e8021ed5a 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -133,6 +133,10 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute2.start() + s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') + s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') + db.service_update(self.context, s1['id'], {'disabled': True}) + db.service_update(self.context, s2['id'], {'disabled': True}) hosts = self.scheduler.driver.hosts_up(self.context, 'compute') self.assertEqual(0, len(hosts)) compute1.kill() @@ -152,10 +156,6 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute2.start() - s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') - s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') - db.service_update(self.context, s1['id'], {'disabled': False}) - db.service_update(self.context, s2['id'], {'disabled': False}) hosts = self.scheduler.driver.hosts_up(self.context, 'compute') self.assertEqual(2, len(hosts)) compute1.kill() @@ -173,10 +173,6 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute2.start() - s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') - s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') - db.service_update(self.context, s1['id'], {'disabled': False}) - db.service_update(self.context, s2['id'], {'disabled': False}) instance_id1 = self._create_instance() compute1.run_instance(self.context, instance_id1) instance_id2 = self._create_instance() @@ -200,10 +196,6 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute2.start() - s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') - s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') - db.service_update(self.context, s1['id'], {'disabled': False}) - db.service_update(self.context, s2['id'], {'disabled': False}) instance_id1 = self._create_instance() compute1.run_instance(self.context, instance_id1) instance_id2 = self._create_instance(availability_zone='nova:host1') @@ -225,8 +217,7 @@ class SimpleDriverTestCase(test.TestCase): now = datetime.datetime.utcnow() delta = datetime.timedelta(seconds=FLAGS.service_down_time * 2) past = now - delta - db.service_update(self.context, s1['id'], {'disabled': False, - 'updated_at': past}) + db.service_update(self.context, s1['id'], {'updated_at': past}) instance_id2 = self._create_instance(availability_zone='nova:host1') self.assertRaises(driver.WillNotSchedule, self.scheduler.driver.schedule_run_instance, @@ -241,7 +232,8 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute1.start() - db.service_get_by_args(self.context, 'host1', 'nova-compute') + s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') + db.service_update(self.context, s1['id'], {'disabled': True}) instance_id2 = self._create_instance(availability_zone='nova:host1') host = self.scheduler.driver.schedule_run_instance(self.context, instance_id2) @@ -261,10 +253,6 @@ class SimpleDriverTestCase(test.TestCase): 'compute', FLAGS.compute_manager) compute2.start() - s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute') - s2 = db.service_get_by_args(self.context, 'host2', 'nova-compute') - db.service_update(self.context, s1['id'], {'disabled': False}) - db.service_update(self.context, s2['id'], {'disabled': False}) instance_ids1 = [] instance_ids2 = [] for index in xrange(FLAGS.max_cores): @@ -298,10 +286,6 @@ class SimpleDriverTestCase(test.TestCase): 'volume', FLAGS.volume_manager) volume2.start() - s1 = db.service_get_by_args(self.context, 'host1', 'nova-volume') - s2 = db.service_get_by_args(self.context, 'host2', 'nova-volume') - db.service_update(self.context, s1['id'], {'disabled': False}) - db.service_update(self.context, s2['id'], {'disabled': False}) volume_id1 = self._create_volume() volume1.create_volume(self.context, volume_id1) volume_id2 = self._create_volume() @@ -325,10 +309,6 @@ class SimpleDriverTestCase(test.TestCase): 'volume', FLAGS.volume_manager) volume2.start() - s1 = db.service_get_by_args(self.context, 'host1', 'nova-volume') - s2 = db.service_get_by_args(self.context, 'host2', 'nova-volume') - db.service_update(self.context, s1['id'], {'disabled': False}) - db.service_update(self.context, s2['id'], {'disabled': False}) volume_ids1 = [] volume_ids2 = [] for index in xrange(FLAGS.max_gigabytes): From c6e9b7c1ff8e3ba2e819bb9823ef3e4b9f532dc4 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 29 Dec 2010 10:35:29 -0600 Subject: [PATCH 24/39] Updating Authors --- .mailmap | 1 + Authors | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 9ab7db743..9ac8f2efd 100644 --- a/.mailmap +++ b/.mailmap @@ -27,3 +27,4 @@ + diff --git a/Authors b/Authors index 299114dad..407b407c8 100644 --- a/Authors +++ b/Authors @@ -25,7 +25,7 @@ Michael Gundlach Monty Taylor Paul Voccio Rick Clark -Rick Harris +Rick Harris Ryan Lane Ryan Lucio Salvatore Orlando From bae77c977faf323adc278d2658dbaa638e236902 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 29 Dec 2010 14:02:57 -0600 Subject: [PATCH 25/39] Added tests --- nova/tests/test_compute.py | 16 ++++++++++++++-- nova/tests/test_xenapi.py | 30 ++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index bcb8a1526..757b1f462 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -102,13 +102,15 @@ class ComputeTestCase(test.TestCase): instances = db.instance_get_all(context.get_admin_context()) logging.info(_("Running instances: %s"), instances) - self.assertEqual(len(instances), 1) + + instance_count = len(instances) + self.assertNotEqual(instance_count, 0) self.compute.terminate_instance(self.context, instance_id) instances = db.instance_get_all(context.get_admin_context()) logging.info(_("After terminating instances: %s"), instances) - self.assertEqual(len(instances), 0) + self.assertEqual(instance_count, len(instances) + 1) def test_run_terminate_timestamps(self): """Make sure timestamps are set for launched and destroyed""" @@ -151,6 +153,16 @@ class ComputeTestCase(test.TestCase): self.compute.reboot_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id) + def test_diagnostics(self): + """Ensure instance diagnostics are available""" + instance_id = self._create_instance() + self.compute.get_diagnostics(self.context, instance_id) + + def test_actions(self): + """Ensure instance actions are available""" + instance_id = self._create_instance() + self.compute.get_actions(self.context, instance_id) + def test_console_output(self): """Make sure we can get console output from instance""" instance_id = self._create_instance() diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index ed2e4ffde..11baead8b 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -159,6 +159,16 @@ class XenAPIVMTestCase(test.TestCase): fake.reset() fakes.stub_out_db_instance_api(self.stubs) fake.create_network('fake', FLAGS.flat_network_bridge) + self.values = { + "name": 1, + "id": 1, + "project_id": self.project.id, + "user_id": self.user.id, + "image_id": 1, + "kernel_id": 2, + "ramdisk_id": 3, + "instance_type": "m1.large", + "mac_address": "aa:bb:cc:dd:ee:ff"} def test_list_instances_0(self): stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) @@ -166,19 +176,19 @@ class XenAPIVMTestCase(test.TestCase): instances = conn.list_instances() self.assertEquals(instances, []) + def test_get_diagnostics(self): + stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) + conn = xenapi_conn.get_connection(False) + + instance = db.instance_create(self.values) + conn.spawn(instance) + + conn.get_diagnostics(instance) + def test_spawn(self): stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - values = {'name': 1, 'id': 1, - 'project_id': self.project.id, - 'user_id': self.user.id, - 'image_id': 1, - 'kernel_id': 2, - 'ramdisk_id': 3, - 'instance_type': 'm1.large', - 'mac_address': 'aa:bb:cc:dd:ee:ff', - } conn = xenapi_conn.get_connection(False) - instance = db.instance_create(values) + instance = db.instance_create(self.values) conn.spawn(instance) def check(): From d0a10fa7e2ee2aaec8d9f236c36582dddb1e1e00 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 29 Dec 2010 17:01:34 -0500 Subject: [PATCH 27/39] Fix scheduler testcase so it knows all flags and can run in isolation. --- nova/tests/test_scheduler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index 91517cc5d..78e4a1c77 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -33,6 +33,7 @@ from nova.scheduler import driver FLAGS = flags.FLAGS flags.DECLARE('max_cores', 'nova.scheduler.simple') +flags.DECLARE('stub_network', 'nova.compute.manager') class TestDriver(driver.Scheduler): From 1ec07fdf78f64cc88e1da80f8f39e5647b7ddb21 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 29 Dec 2010 17:08:42 -0500 Subject: [PATCH 28/39] Pep-8 cleanup. --- bin/nova-api-paste | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bin/nova-api-paste b/bin/nova-api-paste index dcb76522f..3d26fdb4f 100755 --- a/bin/nova-api-paste +++ b/bin/nova-api-paste @@ -48,6 +48,7 @@ FLAGS = flags.FLAGS API_ENDPOINTS = ['ec2', 'openstack'] + def load_configuration(paste_config): """Load the paste configuration from the config file and return it.""" config = None @@ -68,12 +69,14 @@ def load_configuration(paste_config): print "Paste config at %s has no secion for any known apis" % paste_config os.exit(1) + def launch_api(paste_config_file, section, server, port, host): """Launch an api server from the specified port and IP.""" LOG.debug("Launching api %s on %s:%s", section, host, port) app = deploy.loadapp('config:%s' % paste_config_file, name=section) server.start(app, int(port), host) + def run_app(paste_config_file): LOG.debug("Using paste.deploy config at: %s", configfile) config = load_configuration(paste_config_file) @@ -89,6 +92,7 @@ def run_app(paste_config_file): LOG.debug("All api servers launched, now waiting") server.wait() + if __name__ == '__main__': FLAGS(sys.argv) configfiles = ['/etc/nova/nova-api.conf'] From 8768a5e5902300ac15abed3763616c84727c7a0b Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 29 Dec 2010 17:15:50 -0500 Subject: [PATCH 29/39] i18n --- bin/nova-api-paste | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/bin/nova-api-paste b/bin/nova-api-paste index 3d26fdb4f..9bcb98372 100755 --- a/bin/nova-api-paste +++ b/bin/nova-api-paste @@ -27,9 +27,6 @@ import sys from paste import deploy -from nova import flags -from nova import wsgi - # If ../nova/__init__.py exists, add ../ to Python search path, so that # it will override what happens to be installed in /usr/(local/)lib/python... possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), @@ -40,6 +37,9 @@ if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): gettext.install('nova', unicode=1) +from nova import flags +from nova import wsgi + LOG = logging.getLogger('nova.api') LOG.setLevel(logging.DEBUG) LOG.addHandler(logging.StreamHandler()) @@ -65,22 +65,24 @@ def load_configuration(paste_config): if FLAGS.verbose: logging.getLogger().setLevel(logging.DEBUG) return config - LOG.debug("Paste config at %s has no secion for known apis", paste_config) - print "Paste config at %s has no secion for any known apis" % paste_config + LOG.debug(_("Paste config at %s has no secion for known apis"), + paste_config) + print _("Paste config at %s has no secion for any known apis") % \ + paste_config os.exit(1) def launch_api(paste_config_file, section, server, port, host): """Launch an api server from the specified port and IP.""" - LOG.debug("Launching api %s on %s:%s", section, host, port) + LOG.debug(_("Launching %s api on %s:%s"), section, host, port) app = deploy.loadapp('config:%s' % paste_config_file, name=section) server.start(app, int(port), host) def run_app(paste_config_file): - LOG.debug("Using paste.deploy config at: %s", configfile) + LOG.debug(_("Using paste.deploy config at: %s"), configfile) config = load_configuration(paste_config_file) - LOG.debug("Configuration: %r", config) + LOG.debug(_("Configuration: %r"), config) server = wsgi.Server() ip = config.get('host', None) for api in API_ENDPOINTS: @@ -89,7 +91,7 @@ def run_app(paste_config_file): continue host = config.get("%s_host" % api, None) or ip or '0.0.0.0' launch_api(configfile, api, server, port, host) - LOG.debug("All api servers launched, now waiting") + LOG.debug(_("All api servers launched, now waiting")) server.wait() @@ -104,4 +106,4 @@ if __name__ == '__main__': run_app(configfile) break else: - LOG.debug("Skipping missing configuration: %s", configfile) + LOG.debug(_("Skipping missing configuration: %s"), configfile) From dbb0e4c02586899483d2e6204232237ad44c113d Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 30 Dec 2010 01:19:38 -0500 Subject: [PATCH 30/39] Clean up how we determine IP to bind to. --- bin/nova-api-paste | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/nova-api-paste b/bin/nova-api-paste index 9bcb98372..6ee833a18 100755 --- a/bin/nova-api-paste +++ b/bin/nova-api-paste @@ -84,12 +84,12 @@ def run_app(paste_config_file): config = load_configuration(paste_config_file) LOG.debug(_("Configuration: %r"), config) server = wsgi.Server() - ip = config.get('host', None) + ip = config.get('host', '0.0.0.0') for api in API_ENDPOINTS: port = config.get("%s_port" % api, None) if not port: continue - host = config.get("%s_host" % api, None) or ip or '0.0.0.0' + host = config.get("%s_host" % api, ip) launch_api(configfile, api, server, port, host) LOG.debug(_("All api servers launched, now waiting")) server.wait() From 19243419639de06b20a1c272fcb0c68865787f86 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Thu, 30 Dec 2010 14:23:52 -0500 Subject: [PATCH 31/39] Several documentation corrections --- doc/source/adminguide/multi.node.install.rst | 23 +++++----- doc/source/devref/addmethod.openstackapi.rst | 8 ++-- doc/source/devref/rabbit.rst | 4 +- doc/source/nova.concepts.rst | 14 +++--- doc/source/quickstart.rst | 47 +++++++------------- 5 files changed, 39 insertions(+), 57 deletions(-) diff --git a/doc/source/adminguide/multi.node.install.rst b/doc/source/adminguide/multi.node.install.rst index fcb76c5e5..40490e71e 100644 --- a/doc/source/adminguide/multi.node.install.rst +++ b/doc/source/adminguide/multi.node.install.rst @@ -77,21 +77,20 @@ Nova development has consolidated all .conf files to nova.conf as of November 20 #. These need to be defined in the nova.conf configuration file:: - --sql_connection=mysql://root:nova@$CC_ADDR/nova # location of nova sql db - --s3_host=$CC_ADDR # This is where nova is hosting the objectstore service, which - # will contain the VM images and buckets - --rabbit_host=$CC_ADDR # This is where the rabbit AMQP messaging service is hosted - --cc_host=$CC_ADDR # This is where the the nova-api service lives - --verbose # Optional but very helpful during initial setup - --ec2_url=http://$CC_ADDR:8773/services/Cloud - --network_manager=nova.network.manager.FlatManager # simple, no-vlan networking type - - --fixed_range= # ip network to use for VM guests, ex 192.168.2.64/26 - --network_size=<# of addrs> # number of ip addrs to use for VM guests, ex 64 + --sql_connection=mysql://root:nova@$CC_ADDR/nova # location of nova sql db + --s3_host=$CC_ADDR # This is where Nova is hosting the objectstore service, which + # will contain the VM images and buckets + --rabbit_host=$CC_ADDR # This is where the rabbit AMQP messaging service is hosted + --cc_host=$CC_ADDR # This is where the the nova-api service lives + --verbose # Optional but very helpful during initial setup + --ec2_url=http://$CC_ADDR:8773/services/Cloud + --network_manager=nova.network.manager.FlatManager # simple, no-vlan networking type + --fixed_range= # ip network to use for VM guests, ex 192.168.2.64/26 + --network_size=<# of addrs> # number of ip addrs to use for VM guests, ex 64 #. Create a nova group:: - sudo addgroup nova + sudo addgroup nova The Nova config file should have its owner set to root:nova, and mode set to 0640, since they contain your MySQL server's root password. diff --git a/doc/source/devref/addmethod.openstackapi.rst b/doc/source/devref/addmethod.openstackapi.rst index 6484613df..4baa46e20 100644 --- a/doc/source/devref/addmethod.openstackapi.rst +++ b/doc/source/devref/addmethod.openstackapi.rst @@ -24,7 +24,7 @@ Routing To map URLs to controllers+actions, OpenStack uses the Routes package, a clone of Rails routes for Python implementations. See http://routes.groovie.org/ fore more information. -URLs are mapped to "action" methods on "controller" classes in nova/api/openstack/__init__/ApiRouter.__init__ . +URLs are mapped to "action" methods on "controller" classes in `nova/api/openstack/__init__/ApiRouter.__init__` . See http://routes.groovie.org/manual.html for all syntax, but you'll probably just need these two: - mapper.connect() lets you map a single URL to a single action on a controller. @@ -33,9 +33,9 @@ See http://routes.groovie.org/manual.html for all syntax, but you'll probably ju Controllers and actions ----------------------- -Controllers live in nova/api/openstack, and inherit from nova.wsgi.Controller. +Controllers live in `nova/api/openstack`, and inherit from nova.wsgi.Controller. -See nova/api/openstack/servers.py for an example. +See `nova/api/openstack/servers.py` for an example. Action methods take parameters that are sucked out of the URL by mapper.connect() or .resource(). The first two parameters are self and the WebOb request, from which you can get the req.environ, req.body, req.headers, etc. @@ -46,7 +46,7 @@ Actions return a dictionary, and wsgi.Controller serializes that to JSON or XML If you define a new controller, you'll need to define a _serialization_metadata attribute on the class, to tell wsgi.Controller how to convert your dictionary to XML. It needs to know the singular form of any list tag (e.g. list contains tags) and which dictionary keys are to be XML attributes as opposed to subtags (e.g. instead of 4). -See nova/api/openstack/servers.py for an example. +See `nova/api/openstack/servers.py` for an example. Faults ------ diff --git a/doc/source/devref/rabbit.rst b/doc/source/devref/rabbit.rst index 423284a55..ae0bac49d 100644 --- a/doc/source/devref/rabbit.rst +++ b/doc/source/devref/rabbit.rst @@ -71,8 +71,8 @@ RPC Casts The diagram below the message flow during an rp.cast operation: - 1. a Topic Publisher is instantiated to send the message request to the queuing system. - 2. once the message is dispatched by the exchange, it is fetched by the Topic Consumer dictated by the routing key (such as 'topic') and passed to the Worker in charge of the task. + 1. A Topic Publisher is instantiated to send the message request to the queuing system. + 2. Once the message is dispatched by the exchange, it is fetched by the Topic Consumer dictated by the routing key (such as 'topic') and passed to the Worker in charge of the task. .. image:: /images/rabbit/flow2.png :width: 60% diff --git a/doc/source/nova.concepts.rst b/doc/source/nova.concepts.rst index 18368546b..fb3969a43 100644 --- a/doc/source/nova.concepts.rst +++ b/doc/source/nova.concepts.rst @@ -75,7 +75,7 @@ Nova is built on a shared-nothing, messaging-based architecture. All of the majo To achieve the shared-nothing property with multiple copies of the same component, Nova keeps all the cloud system state in a distributed data store. Updates to system state are written into this store, using atomic transactions when required. Requests for system state are read out of this store. In limited cases, the read results are cached within controllers for short periods of time (for example, the current list of system users.) - .. note:: The database schema is available on the `OpenStack Wiki _`. + .. note:: The database schema is available on the `OpenStack Wiki `_. Concept: Storage ---------------- @@ -129,12 +129,12 @@ The simplest networking mode. Each instance receives a fixed ip from the pool. Flat DHCP Mode ~~~~~~~~~~~~~~ -This is similar to the flat mode, in that all instances are attached to the same bridge. In this mode nova does a bit more configuration, it will attempt to bridge into an ethernet device (eth0 by default). It will also run dnsmasq as a dhcpserver listening on this bridge. Instances receive their fixed IPs by doing a dhcpdiscover. +This is similar to the flat mode, in that all instances are attached to the same bridge. In this mode Nova does a bit more configuration, it will attempt to bridge into an ethernet device (eth0 by default). It will also run dnsmasq as a dhcpserver listening on this bridge. Instances receive their fixed IPs by doing a dhcpdiscover. VLAN DHCP Mode ~~~~~~~~~~~~~~ -This is the default networking mode and supports the most features. For multiple machine installation, it requires a switch that supports host-managed vlan tagging. In this mode, nova will create a vlan and bridge for each project. The project gets a range of private ips that are only accessible from inside the vlan. In order for a user to access the instances in their project, a special vpn instance (code named :ref:`cloudpipe `) needs to be created. Nova generates a certificate and key for the user to access the vpn and starts the vpn automatically. More information on cloudpipe can be found :ref:`here `. +This is the default networking mode and supports the most features. For multiple machine installation, it requires a switch that supports host-managed vlan tagging. In this mode, Nova will create a vlan and bridge for each project. The project gets a range of private ips that are only accessible from inside the vlan. In order for a user to access the instances in their project, a special vpn instance (code named :ref:`cloudpipe `) needs to be created. Nova generates a certificate and key for the user to access the vpn and starts the vpn automatically. More information on cloudpipe can be found :ref:`here `. The following diagram illustrates how the communication that occurs between the vlan (the dashed box) and the public internet (represented by the two clouds) @@ -154,16 +154,16 @@ Concept: nova-manage -------------------- The nova-manage command is used to perform many essential functions for -administration and ongoing maintenance of nova, such as user creation, +administration and ongoing maintenance of Nova, such as user creation, vpn management, and much more. -See doc:`nova.manage` in the Administration Guide for more details. +See :doc:`nova.manage` in the Administration Guide for more details. Concept: Flags -------------- -Nova uses python-gflags for a distributed command line system, and the flags can either be set when running a command at the command line or within flag files. When you install Nova packages, each nova service gets its own flag file. For example, nova-network.conf is used for configuring the nova-network service, and so forth. +Nova uses python-gflags for a distributed command line system, and the flags can either be set when running a command at the command line or within flag files. When you install Nova packages, each Nova service gets its own flag file. For example, nova-network.conf is used for configuring the nova-network service, and so forth. Concept: Plugins @@ -181,7 +181,7 @@ Concept: Plugins Concept: IPC/RPC ---------------- -Nova utilizes the RabbitMQ implementation of the AMQP messaging standard for performing communication between the various nova services. This message queuing service is used for both local and remote communication because Nova is designed so that there is no requirement that any of the services exist on the same physical machine. RabbitMQ in particular is very robust and provides the efficiency and reliability that Nova needs. More information about RabbitMQ can be found at http://www.rabbitmq.com/. +Nova utilizes the RabbitMQ implementation of the AMQP messaging standard for performing communication between the various Nova services. This message queuing service is used for both local and remote communication because Nova is designed so that there is no requirement that any of the services exist on the same physical machine. RabbitMQ in particular is very robust and provides the efficiency and reliability that Nova needs. More information about RabbitMQ can be found at http://www.rabbitmq.com/. Concept: Fakes -------------- diff --git a/doc/source/quickstart.rst b/doc/source/quickstart.rst index ae2b64d8a..fa5d96738 100644 --- a/doc/source/quickstart.rst +++ b/doc/source/quickstart.rst @@ -59,38 +59,21 @@ different configurations (though for more complex setups you should see * HOST_IP * Default: address of first interface from the ifconfig command * Values: 127.0.0.1, or any other valid address - -TEST -~~~~ - -**Default**: 0 -**Values**: 1, run tests after checkout and initial setup - -USE_MYSQL -~~~~~~~~~ - -**Default**: 0, use sqlite3 -**Values**: 1, use mysql instead of sqlite3 - -MYSQL_PASS -~~~~~~~~~~ - -Only useful if $USE_MYSQL=1. - -**Default**: nova -**Values**: value of root password for mysql - -USE_LDAP -~~~~~~~~ - -**Default**: 0, use :mod:`nova.auth.dbdriver` -**Values**: 1, use :mod:`nova.auth.ldapdriver` - -LIBVIRT_TYPE -~~~~~~~~~~~~ - -**Default**: qemu -**Values**: uml, kvm +* TEST + * Default: 0 + * Values: 1, run tests after checkout and initial setup +* USE_MYSQL + * Default: 0, use sqlite3 + * Values: 1, use mysql instead of sqlite3 +* MYSQL_PASS (Only useful if $USE_MYSQL=1) + * Default: nova + * Values: value of root password for mysql +* USE_LDAP + * Default: 0, use :mod:`nova.auth.dbdriver` + * Values: 1, use :mod:`nova.auth.ldapdriver` +* LIBVIRT_TYPE + * Default: qemu + * Values: uml, kvm Usage ----- From ec87f4ecaf1cb76e9e8ccf37ddb7e72e8f27515b Mon Sep 17 00:00:00 2001 From: termie Date: Thu, 30 Dec 2010 13:31:56 -0800 Subject: [PATCH 32/39] change exit code --- run_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_tests.py b/run_tests.py index 56a8bffe7..5b8617f63 100644 --- a/run_tests.py +++ b/run_tests.py @@ -65,4 +65,4 @@ if __name__ == '__main__': runner = NovaTestRunner(stream=c.stream, verbosity=c.verbosity, config=c) - sys.exit(core.run(config=c, testRunner=runner)) + sys.exit(not core.run(config=c, testRunner=runner)) From 8864b9ebfd0d77f25a409037b3c643de02e4d990 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 16:06:48 -0600 Subject: [PATCH 33/39] Review feedback --- nova/tests/test_compute.py | 11 ++--------- nova/tests/test_xenapi.py | 31 ++++++------------------------- 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index e3679b6b6..2664adc0d 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -102,15 +102,13 @@ class ComputeTestCase(test.TestCase): instances = db.instance_get_all(context.get_admin_context()) logging.info(_("Running instances: %s"), instances) - - instance_count = len(instances) - self.assertNotEqual(instance_count, 0) + self.assertEqual(len(instances), 1) self.compute.terminate_instance(self.context, instance_id) instances = db.instance_get_all(context.get_admin_context()) logging.info(_("After terminating instances: %s"), instances) - self.assertEqual(instance_count, len(instances) + 1) + self.assertEqual(len(instances), 0) def test_run_terminate_timestamps(self): """Make sure timestamps are set for launched and destroyed""" @@ -158,11 +156,6 @@ class ComputeTestCase(test.TestCase): instance_id = self._create_instance() self.compute.get_diagnostics(self.context, instance_id) - def test_actions(self): - """Ensure instance actions are available""" - instance_id = self._create_instance() - self.compute.get_actions(self.context, instance_id) - def test_snapshot(self): """Ensure instance can be snapshotted""" instance_id = self._create_instance() diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 3528d7bfe..a09750672 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -156,10 +156,11 @@ class XenAPIVMTestCase(test.TestCase): self.stubs = stubout.StubOutForTesting() FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' - - fake.reset() - fakes.stub_out_db_instance_api(self.stubs) - fake.create_network('fake', FLAGS.flat_network_bridge) + xenapi_fake.reset() + db_fakes.stub_out_db_instance_api(self.stubs) + xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) + stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) + self.conn = xenapi_conn.get_connection(False) self.values = { "name": 1, "id": 1, @@ -171,12 +172,6 @@ class XenAPIVMTestCase(test.TestCase): "instance_type": "m1.large", "mac_address": "aa:bb:cc:dd:ee:ff"} - xenapi_fake.reset() - db_fakes.stub_out_db_instance_api(self.stubs) - xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) - stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - self.conn = xenapi_conn.get_connection(False) - def test_list_instances_0(self): instances = self.conn.list_instances() self.assertEquals(instances, []) @@ -228,11 +223,6 @@ class XenAPIVMTestCase(test.TestCase): check() def test_spawn(self): - stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - conn = xenapi_conn.get_connection(False) - instance = db.instance_create(self.values) - conn.spawn(instance) - instance = self._create_instance() def check(): @@ -275,15 +265,6 @@ class XenAPIVMTestCase(test.TestCase): def _create_instance(self): """Creates and spawns a test instance""" - values = {'name': 1, 'id': 1, - 'project_id': self.project.id, - 'user_id': self.user.id, - 'image_id': 1, - 'kernel_id': 2, - 'ramdisk_id': 3, - 'instance_type': 'm1.large', - 'mac_address': 'aa:bb:cc:dd:ee:ff', - } - instance = db.instance_create(values) + instance = db.instance_create(self.values) self.conn.spawn(instance) return instance From 736cc20a8575f61426b6ee4fa683992e645f3ba6 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 18:12:10 -0600 Subject: [PATCH 34/39] Removed problematic test --- nova/tests/test_compute.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 2664adc0d..1fb9143f1 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -151,11 +151,6 @@ class ComputeTestCase(test.TestCase): self.compute.reboot_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id) - def test_diagnostics(self): - """Ensure instance diagnostics are available""" - instance_id = self._create_instance() - self.compute.get_diagnostics(self.context, instance_id) - def test_snapshot(self): """Ensure instance can be snapshotted""" instance_id = self._create_instance() From f7a0d3e13340d139892005ba24dfa5df0f1f4791 Mon Sep 17 00:00:00 2001 From: Ryan Lucio Date: Thu, 30 Dec 2010 16:21:11 -0800 Subject: [PATCH 35/39] Converted the pool_recycle setting to be a flag with a default of 3600 seconds --- nova/flags.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/flags.py b/nova/flags.py index 76a98d35a..338dcbf46 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -260,6 +260,9 @@ DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'), DEFINE_string('sql_connection', 'sqlite:///$state_path/nova.sqlite', 'connection string for sql database') +DEFINE_string('sql_idle_timeout', + '3600', + 'timeout for idle sql database connections') DEFINE_string('compute_manager', 'nova.compute.manager.ComputeManager', 'Manager for compute') From ccc07fefb4ba1bd1e9f9af97986e25d99c337276 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 18:56:12 -0600 Subject: [PATCH 36/39] Improved test --- nova/tests/test_xenapi.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index a09750672..539d132bd 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -180,9 +180,7 @@ class XenAPIVMTestCase(test.TestCase): stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) conn = xenapi_conn.get_connection(False) - instance = db.instance_create(self.values) - conn.spawn(instance) - + instance = self._create_instance() conn.get_diagnostics(instance) def test_instance_snapshot(self): From 8b7758185d5b27edc389e48240404ede9a24f7d1 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 19:07:20 -0600 Subject: [PATCH 37/39] Cleanup --- nova/tests/test_xenapi.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 539d132bd..c95a53af3 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -161,27 +161,14 @@ class XenAPIVMTestCase(test.TestCase): xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) self.conn = xenapi_conn.get_connection(False) - self.values = { - "name": 1, - "id": 1, - "project_id": self.project.id, - "user_id": self.user.id, - "image_id": 1, - "kernel_id": 2, - "ramdisk_id": 3, - "instance_type": "m1.large", - "mac_address": "aa:bb:cc:dd:ee:ff"} def test_list_instances_0(self): instances = self.conn.list_instances() self.assertEquals(instances, []) def test_get_diagnostics(self): - stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - conn = xenapi_conn.get_connection(False) - instance = self._create_instance() - conn.get_diagnostics(instance) + self.conn.get_diagnostics(instance) def test_instance_snapshot(self): stubs.stubout_instance_snapshot(self.stubs) @@ -263,6 +250,15 @@ class XenAPIVMTestCase(test.TestCase): def _create_instance(self): """Creates and spawns a test instance""" - instance = db.instance_create(self.values) + values = {'name': 1, 'id': 1, + 'project_id': self.project.id, + 'user_id': self.user.id, + 'image_id': 1, + 'kernel_id': 2, + 'ramdisk_id': 3, + 'instance_type': 'm1.large', + 'mac_address': 'aa:bb:cc:dd:ee:ff' + } + instance = db.instance_create(values) self.conn.spawn(instance) return instance From 37538c7414841453a365b908a008aca3bc744bfc Mon Sep 17 00:00:00 2001 From: Eric Day Date: Thu, 30 Dec 2010 20:03:21 -0800 Subject: [PATCH 38/39] Ignore CA/crl.pem --- .bzrignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.bzrignore b/.bzrignore index 82db46fa2..d81a7d829 100644 --- a/.bzrignore +++ b/.bzrignore @@ -6,6 +6,7 @@ keys networks nova.sqlite CA/cacert.pem +CA/crl.pem CA/index.txt* CA/openssl.cnf CA/serial* From 8ae8811ccb457e49d202ea5a37a0cb93d12dbb5b Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 3 Jan 2011 10:08:32 +0100 Subject: [PATCH 39/39] Remove references to nova-core/ppa and openstack/ppa PPA's. --- doc/source/adminguide/distros/ubuntu.10.04.rst | 13 ++++++------- doc/source/adminguide/getting.started.rst | 6 +++--- doc/source/adminguide/multi.node.install.rst | 4 ++-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/doc/source/adminguide/distros/ubuntu.10.04.rst b/doc/source/adminguide/distros/ubuntu.10.04.rst index ce368fab8..9d856458a 100644 --- a/doc/source/adminguide/distros/ubuntu.10.04.rst +++ b/doc/source/adminguide/distros/ubuntu.10.04.rst @@ -16,13 +16,13 @@ Here's a script you can use to install (and then run) Nova on Ubuntu or Debian ( Step 2: Install dependencies ---------------------------- -Nova requires rabbitmq for messaging and optionally you can use redis for storing state, so install these first. +Nova requires rabbitmq for messaging, so install that first. *Note:* You must have sudo installed to run these commands as shown here. :: - sudo apt-get install rabbitmq-server redis-server + sudo apt-get install rabbitmq-server You'll see messages starting with "Reading package lists... Done" and you must confirm by typing Y that you want to continue. @@ -31,11 +31,10 @@ If you're running on Ubuntu 10.04, you'll need to install Twisted and python-gfl :: - sudo apt-get install python-twisted - - sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 95C71FE2 - sudo sh -c 'echo "deb http://ppa.launchpad.net/openstack/openstack-ppa/ubuntu lucid main" > /etc/apt/sources.list.d/openstackppa.list' - sudo apt-get update && sudo apt-get install python-gflags + sudo add-get install python-software-properties + sudo add-apt-repository ppa:nova-core/trunk + sudo apt-get update + sudo apt-get install python-twisted python-gflags Once you've done this, continue at Step 3 here: :doc:`../single.node.install` diff --git a/doc/source/adminguide/getting.started.rst b/doc/source/adminguide/getting.started.rst index 3e8073606..0cadeb45e 100644 --- a/doc/source/adminguide/getting.started.rst +++ b/doc/source/adminguide/getting.started.rst @@ -76,11 +76,11 @@ External unix tools that are required: * aoetools and vblade-persist (if you use aoe-volumes) Nova uses cutting-edge versions of many packages. There are ubuntu packages in -the nova-core ppa. You can use add this ppa to your sources list on an ubuntu -machine with the following commands:: +the nova-core trunk ppa. You can use add this ppa to your sources list on an +ubuntu machine with the following commands:: sudo apt-get install -y python-software-properties - sudo add-apt-repository ppa:nova-core/ppa + sudo add-apt-repository ppa:nova-core/trunk Recommended ----------- diff --git a/doc/source/adminguide/multi.node.install.rst b/doc/source/adminguide/multi.node.install.rst index fcb76c5e5..7c4a69ccd 100644 --- a/doc/source/adminguide/multi.node.install.rst +++ b/doc/source/adminguide/multi.node.install.rst @@ -46,12 +46,12 @@ Assumptions Step 1 Use apt-get to get the latest code ----------------------------------------- -1. Setup Nova PPA with https://launchpad.net/~nova-core/+archive/ppa. +1. Setup Nova PPA with https://launchpad.net/~nova-core/+archive/trunk. :: sudo apt-get install python-software-properties - sudo add-apt-repository ppa:nova-core/ppa + sudo add-apt-repository ppa:nova-core/trunk 2. Run update.