diff options
| author | Brian Waldon <brian.waldon@rackspace.com> | 2011-05-03 12:29:11 -0400 |
|---|---|---|
| committer | Brian Waldon <brian.waldon@rackspace.com> | 2011-05-03 12:29:11 -0400 |
| commit | 4cfcafdd773495e7a1a78eb0a183d23b3527ecb6 (patch) | |
| tree | 78726e7650175f8750d21a86d55b3ac860fa1e88 | |
| parent | bf889f68e3efbf0ca388912b6c93ef61c5a8e7ad (diff) | |
| parent | ce019de9ca633218f031077f6317edb373f1ea88 (diff) | |
merging trunk
| -rw-r--r-- | Authors | 2 | ||||
| -rw-r--r-- | doc/source/_theme/layout.html | 2 | ||||
| -rw-r--r-- | nova/api/openstack/common.py | 9 | ||||
| -rw-r--r-- | nova/api/openstack/images.py | 2 | ||||
| -rw-r--r-- | nova/api/openstack/servers.py | 11 | ||||
| -rw-r--r-- | nova/api/openstack/views/images.py | 17 | ||||
| -rw-r--r-- | nova/api/openstack/views/servers.py | 4 | ||||
| -rw-r--r-- | nova/exception.py | 8 | ||||
| -rw-r--r-- | nova/network/linux_net.py | 7 | ||||
| -rw-r--r-- | nova/tests/api/openstack/test_images.py | 12 | ||||
| -rw-r--r-- | nova/tests/api/openstack/test_servers.py | 56 | ||||
| -rw-r--r-- | nova/tests/test_exception.py | 34 | ||||
| -rw-r--r-- | nova/tests/test_misc.py | 49 | ||||
| -rw-r--r-- | nova/tests/test_virt.py | 26 | ||||
| -rw-r--r-- | nova/virt/libvirt_conn.py | 11 | ||||
| -rwxr-xr-x | run_tests.sh | 1 |
16 files changed, 197 insertions, 54 deletions
@@ -1,3 +1,4 @@ +Alex Meade <alex.meade@rackspace.com> Andy Smith <code@term.ie> Andy Southgate <andy.southgate@citrix.com> Anne Gentle <anne@openstack.org> @@ -45,6 +46,7 @@ Joshua McKenty <jmckenty@gmail.com> Justin Santa Barbara <justin@fathomdb.com> Kei Masumoto <masumotok@nttdata.co.jp> Ken Pepple <ken.pepple@gmail.com> +Kevin Bringard <kbringard@attinteractive.com> Kevin L. Mitchell <kevin.mitchell@rackspace.com> Koji Iida <iida.koji@lab.ntt.co.jp> Lorin Hochstein <lorin@isi.edu> diff --git a/doc/source/_theme/layout.html b/doc/source/_theme/layout.html index 0a37a7943..b28edb364 100644 --- a/doc/source/_theme/layout.html +++ b/doc/source/_theme/layout.html @@ -73,7 +73,7 @@ <script type="text/javascript">$('#searchbox').show(0);</script> <p class="triangle-border right"> - Psst... hey. You're reading the latest content, but it might be out of sync with code. You can read <a href="http://nova.openstack.org/2011.1">Nova 2011.1 docs</a> or <a href="http://docs.openstack.org">all OpenStack docs</a> too. + Psst... hey. You're reading the latest content, but it might be out of sync with code. You can read <a href="http://nova.openstack.org/2011.2">Nova 2011.2 docs</a> or <a href="http://docs.openstack.org">all OpenStack docs</a> too. </p> {%- endif %} diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 65ed1e143..32cd689ca 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +import re from urlparse import urlparse import webob @@ -130,10 +131,16 @@ def get_image_id_from_image_hash(image_service, context, image_hash): def get_id_from_href(href): """Return the id portion of a url as an int. - Given: http://www.foo.com/bar/123?q=4 + Given: 'http://www.foo.com/bar/123?q=4' + Returns: 123 + + In order to support local hrefs, the href argument can be just an id: + Given: '123' Returns: 123 """ + if re.match(r'\d+$', str(href)): + return int(href) try: return int(urlparse(href).path.split('/')[-1]) except: diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 77baf5947..34d4c27fc 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -127,7 +127,7 @@ class Controller(common.OpenstackController): raise webob.exc.HTTPBadRequest() image = self._compute_service.snapshot(context, server_id, image_name) - return self.get_builder(req).build(image, detail=True) + return dict(image=self.get_builder(req).build(image, detail=True)) def get_builder(self, request): """Indicates that you must use a Controller subclass.""" diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 9b883f06b..8505c3f71 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -14,28 +14,25 @@ # under the License. import base64 -import hashlib import traceback from webob import exc from xml.dom import minidom from nova import compute -from nova import context from nova import exception from nova import flags from nova import log as logging from nova import quota from nova import utils -from nova import wsgi from nova.api.openstack import common from nova.api.openstack import faults import nova.api.openstack.views.addresses import nova.api.openstack.views.flavors +import nova.api.openstack.views.images import nova.api.openstack.views.servers from nova.auth import manager as auth_manager from nova.compute import instance_types -from nova.compute import power_state import nova.api.openstack from nova.scheduler import api as scheduler_api @@ -595,9 +592,6 @@ class ControllerV10(Controller): return nova.api.openstack.views.servers.ViewBuilderV10( addresses_builder) - def _get_addresses_view_builder(self, req): - return nova.api.openstack.views.addresses.ViewBuilderV10(req) - def _limit_items(self, items, req): return common.limited(items, req) @@ -629,9 +623,6 @@ class ControllerV11(Controller): return nova.api.openstack.views.servers.ViewBuilderV11( addresses_builder, flavor_builder, image_builder, base_url) - def _get_addresses_view_builder(self, req): - return nova.api.openstack.views.addresses.ViewBuilderV11(req) - def _action_change_password(self, input_dict, req, id): context = req.environ['nova.context'] if (not 'changePassword' in input_dict diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 9dec8a355..2773c9c13 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -46,6 +46,14 @@ class ViewBuilder(object): except KeyError: image['status'] = image['status'].upper() + def _build_server(self, image, instance_id): + """Indicates that you must use a ViewBuilder subclass.""" + raise NotImplementedError + + def generate_server_ref(self, server_id): + """Return an href string pointing to this server.""" + return os.path.join(self._url, "servers", str(server_id)) + def generate_href(self, image_id): """Return an href string pointing to this object.""" return os.path.join(self._url, "images", str(image_id)) @@ -66,7 +74,7 @@ class ViewBuilder(object): if "instance_id" in properties: try: - image["serverId"] = int(properties["instance_id"]) + self._build_server(image, int(properties["instance_id"])) except ValueError: pass @@ -85,12 +93,17 @@ class ViewBuilder(object): class ViewBuilderV10(ViewBuilder): """OpenStack API v1.0 Image Builder""" - pass + + def _build_server(self, image, instance_id): + image["serverId"] = instance_id class ViewBuilderV11(ViewBuilder): """OpenStack API v1.1 Image Builder""" + def _build_server(self, image, instance_id): + image["serverRef"] = self.generate_server_ref(instance_id) + def build(self, image_obj, detail=False): """Return a standardized image structure for display by the API.""" image = ViewBuilder.build(self, image_obj, detail) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index e52bfaea3..bdc85f4a1 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -63,8 +63,8 @@ class ViewBuilder(object): power_state.BLOCKED: 'ACTIVE', power_state.SUSPENDED: 'SUSPENDED', power_state.PAUSED: 'PAUSED', - power_state.SHUTDOWN: 'ACTIVE', - power_state.SHUTOFF: 'ACTIVE', + power_state.SHUTDOWN: 'SHUTDOWN', + power_state.SHUTOFF: 'SHUTOFF', power_state.CRASHED: 'ERROR', power_state.FAILED: 'ERROR'} diff --git a/nova/exception.py b/nova/exception.py index e8444cb14..8cdd0cb5a 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -49,10 +49,14 @@ class Error(Exception): class ApiError(Error): - def __init__(self, message='Unknown', code='ApiError'): + def __init__(self, message='Unknown', code=None): self.message = message self.code = code - super(ApiError, self).__init__('%s: %s' % (code, message)) + if code: + outstr = '%s: %s' % (code, message) + else: + outstr = '%s' % message + super(ApiError, self).__init__(outstr) class DBError(Error): diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index ec5579dee..ef997fd41 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -56,13 +56,12 @@ flags.DEFINE_string('input_chain', 'INPUT', 'chain to add nova_input to') flags.DEFINE_integer('dhcp_lease_time', 120, 'Lifetime of a DHCP lease') - flags.DEFINE_string('dns_server', None, 'if set, uses specific dns server for dnsmasq') flags.DEFINE_string('dmz_cidr', '10.128.0.0/24', 'dmz range that should be accepted') - - +flags.DEFINE_string('dnsmasq_config_file', "", + 'Override the default dnsmasq settings with this file') binary_name = os.path.basename(inspect.stack()[-1][1]) @@ -678,7 +677,7 @@ def _dnsmasq_cmd(net): cmd = ['sudo', '-E', 'dnsmasq', '--strict-order', '--bind-interfaces', - '--conf-file=', + '--conf-file=%s' % FLAGS.dnsmasq_config_file, '--domain=%s' % FLAGS.dhcp_domain, '--pid-file=%s' % _dhcp_file(net['bridge'], 'pid'), '--listen-address=%s' % net['gateway'], diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index ae86d0686..e5dd93c3f 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -538,7 +538,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }, { 'id': 127, - 'name': 'killed backup', 'serverId': 42, + 'name': 'killed backup', + 'serverId': 42, 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'FAILED', @@ -584,7 +585,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 124, 'name': 'queued backup', - 'serverId': 42, + 'serverRef': "http://localhost/v1.1/servers/42", 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'QUEUED', @@ -606,7 +607,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 125, 'name': 'saving backup', - 'serverId': 42, + 'serverRef': "http://localhost/v1.1/servers/42", 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'SAVING', @@ -629,7 +630,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): { 'id': 126, 'name': 'active backup', - 'serverId': 42, + 'serverRef': "http://localhost/v1.1/servers/42", 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'ACTIVE', @@ -650,7 +651,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }, { 'id': 127, - 'name': 'killed backup', 'serverId': 42, + 'name': 'killed backup', + 'serverRef': "http://localhost/v1.1/servers/42", 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'FAILED', diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 556046e9d..bbce77bf0 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -33,6 +33,7 @@ import nova.api.openstack from nova.api.openstack import servers import nova.compute.api from nova.compute import instance_types +from nova.compute import power_state import nova.db.api from nova.db.sqlalchemy.models import Instance from nova.db.sqlalchemy.models import InstanceMetadata @@ -56,6 +57,12 @@ def return_server_with_addresses(private, public): return _return_server +def return_server_with_power_state(power_state): + def _return_server(context, id): + return stub_instance(id, power_state=power_state) + return _return_server + + def return_servers(context, user_id=1): return [stub_instance(i, user_id) for i in xrange(5)] @@ -73,7 +80,7 @@ def instance_address(context, instance_id): def stub_instance(id, user_id=1, private_address=None, public_addresses=None, - host=None): + host=None, power_state=0): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) @@ -96,7 +103,7 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None, "launch_index": 0, "key_name": "", "key_data": "", - "state": 0, + "state": power_state, "state_description": "", "memory_mb": 0, "vcpus": 0, @@ -613,6 +620,33 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_create_instance_v11_local_href(self): + self._setup_for_create_instance() + + imageRef = 'http://localhost/v1.1/images/2' + imageRefLocal = '2' + flavorRef = 'http://localhost/v1.1/flavors/3' + body = { + 'server': { + 'name': 'server_test', + 'imageRef': imageRefLocal, + 'flavorRef': flavorRef, + }, + } + + req = webob.Request.blank('/v1.1/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app()) + + server = json.loads(res.body)['server'] + self.assertEqual(1, server['id']) + self.assertEqual(flavorRef, server['flavorRef']) + self.assertEqual(imageRef, server['imageRef']) + self.assertEqual(res.status_int, 200) + def test_create_instance_with_admin_pass_v10(self): self._setup_for_create_instance() @@ -1155,6 +1189,24 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_shutdown_status(self): + new_server = return_server_with_power_state(power_state.SHUTDOWN) + self.stubs.Set(nova.db.api, 'instance_get', new_server) + req = webob.Request.blank('/v1.0/servers/1') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + self.assertEqual(res_dict['server']['status'], 'SHUTDOWN') + + def test_shutoff_status(self): + new_server = return_server_with_power_state(power_state.SHUTOFF) + self.stubs.Set(nova.db.api, 'instance_get', new_server) + req = webob.Request.blank('/v1.0/servers/1') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + self.assertEqual(res_dict['server']['status'], 'SHUTOFF') + class TestServerCreateRequestXMLDeserializer(unittest.TestCase): diff --git a/nova/tests/test_exception.py b/nova/tests/test_exception.py new file mode 100644 index 000000000..1b0e41d9a --- /dev/null +++ b/nova/tests/test_exception.py @@ -0,0 +1,34 @@ +# 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. + +from nova import test +from nova import exception + + +class ApiErrorTestCase(test.TestCase): + def test_return_valid_error(self): + # without 'code' arg + err = exception.ApiError('fake error') + self.assertEqual(err.__str__(), 'fake error') + self.assertEqual(err.code, None) + self.assertEqual(err.message, 'fake error') + # with 'code' arg + err = exception.ApiError('fake error', 'blah code') + self.assertEqual(err.__str__(), 'blah code: fake error') + self.assertEqual(err.code, 'blah code') + self.assertEqual(err.message, 'fake error') diff --git a/nova/tests/test_misc.py b/nova/tests/test_misc.py index 4e17e1ce0..ad62b48bf 100644 --- a/nova/tests/test_misc.py +++ b/nova/tests/test_misc.py @@ -29,11 +29,12 @@ from nova.utils import parse_mailmap, str_dict_replace class ProjectTestCase(test.TestCase): def test_authors_up_to_date(self): topdir = os.path.normpath(os.path.dirname(__file__) + '/../../') - if os.path.exists(os.path.join(topdir, '.bzr')): - contributors = set() - - mailmap = parse_mailmap(os.path.join(topdir, '.mailmap')) + missing = set() + contributors = set() + mailmap = parse_mailmap(os.path.join(topdir, '.mailmap')) + authors_file = open(os.path.join(topdir, 'Authors'), 'r').read() + if os.path.exists(os.path.join(topdir, '.bzr')): import bzrlib.workingtree tree = bzrlib.workingtree.WorkingTree.open(topdir) tree.lock_read() @@ -47,22 +48,36 @@ class ProjectTestCase(test.TestCase): for r in revs: for author in r.get_apparent_authors(): email = author.split(' ')[-1] - contributors.add(str_dict_replace(email, mailmap)) + contributors.add(str_dict_replace(email, + mailmap)) + finally: + tree.unlock() - authors_file = open(os.path.join(topdir, 'Authors'), - 'r').read() + elif os.path.exists(os.path.join(topdir, '.git')): + import git + repo = git.Repo(topdir) + for commit in repo.head.commit.iter_parents(): + email = commit.author.email + if email is None: + email = commit.author.name + if 'nova-core' in email: + continue + if email.split(' ')[-1] == '<>': + email = email.split(' ')[-2] + email = '<' + email + '>' + contributors.add(str_dict_replace(email, mailmap)) - missing = set() - for contributor in contributors: - if contributor == 'nova-core': - continue - if not contributor in authors_file: - missing.add(contributor) + else: + self.assertTrue(False, 'Cannot read commit history') - self.assertTrue(len(missing) == 0, - '%r not listed in Authors' % missing) - finally: - tree.unlock() + for contributor in contributors: + if contributor == 'nova-core': + continue + if not contributor in authors_file: + missing.add(contributor) + + self.assertTrue(len(missing) == 0, + '%r not listed in Authors' % missing) class LockTestCase(test.TestCase): diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 462cf5aa0..1311ba361 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -44,7 +44,9 @@ def _concurrency(wait, done, target): done.send() -def _create_network_info(count=1): +def _create_network_info(count=1, ipv6=None): + if ipv6 is None: + ipv6 = FLAGS.use_ipv6 fake = 'fake' fake_ip = '0.0.0.0/0' fake_ip_2 = '0.0.0.1/0' @@ -55,8 +57,11 @@ def _create_network_info(count=1): 'cidr': fake_ip, 'cidr_v6': fake_ip} mapping = {'mac': fake, - 'ips': [{'ip': fake_ip}, {'ip': fake_ip}], - 'ip6s': [{'ip': fake_ip}, {'ip': fake_ip_2}, {'ip': fake_ip_3}]} + 'ips': [{'ip': fake_ip}, {'ip': fake_ip}]} + if ipv6: + mapping['ip6s'] = [{'ip': fake_ip}, + {'ip': fake_ip_2}, + {'ip': fake_ip_3}] return [(network, mapping) for x in xrange(0, count)] @@ -641,6 +646,11 @@ class LibvirtConnTestCase(test.TestCase): self.assertTrue(count) + def test_get_host_ip_addr(self): + conn = libvirt_conn.LibvirtConnection(False) + ip = conn.get_host_ip_addr() + self.assertEquals(ip, FLAGS.my_ip) + def tearDown(self): self.manager.delete_project(self.project) self.manager.delete_user(self.user) @@ -825,12 +835,20 @@ class IptablesFirewallTestCase(test.TestCase): "TCP port 80/81 acceptance rule wasn't added") db.instance_destroy(admin_ctxt, instance_ref['id']) - def test_filters_for_instance(self): + def test_filters_for_instance_with_ip_v6(self): + self.flags(use_ipv6=True) network_info = _create_network_info() rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info) self.assertEquals(len(rulesv4), 2) self.assertEquals(len(rulesv6), 3) + def test_filters_for_instance_without_ip_v6(self): + self.flags(use_ipv6=False) + network_info = _create_network_info() + rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info) + self.assertEquals(len(rulesv4), 2) + self.assertEquals(len(rulesv6), 0) + def multinic_iptables_test(self): ipv4_rules_per_network = 2 ipv6_rules_per_network = 3 diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 879534f59..9780c69a6 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -735,6 +735,9 @@ class LibvirtConnection(driver.ComputeDriver): subprocess.Popen(cmd, shell=True) return {'token': token, 'host': host, 'port': port} + def get_host_ip_addr(self): + return FLAGS.my_ip + @exception.wrap_exception def get_vnc_console(self, instance): def get_vnc_port_for_instance(instance_name): @@ -2008,10 +2011,12 @@ class IptablesFirewallDriver(FirewallDriver): for ip in mapping['ips']] ipv4_rules = self._create_filter(ips_v4, chain_name) - ips_v6 = [ip['ip'] for (_n, mapping) in network_info - for ip in mapping['ip6s']] + ipv6_rules = [] + if FLAGS.use_ipv6: + ips_v6 = [ip['ip'] for (_n, mapping) in network_info + for ip in mapping['ip6s']] + ipv6_rules = self._create_filter(ips_v6, chain_name) - ipv6_rules = self._create_filter(ips_v6, chain_name) return ipv4_rules, ipv6_rules def _add_filters(self, chain_name, ipv4_rules, ipv6_rules): diff --git a/run_tests.sh b/run_tests.sh index 610cf1f27..e3a0bd243 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -6,6 +6,7 @@ function usage { echo "" echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" + echo " -x, --stop Stop running tests after the first error or failure." echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." echo " -p, --pep8 Just run pep8" echo " -h, --help Print this usage message" |
