diff options
| author | Brian Lamar <brian.lamar@rackspace.com> | 2011-08-24 13:16:37 -0400 |
|---|---|---|
| committer | Brian Lamar <brian.lamar@rackspace.com> | 2011-08-24 13:16:37 -0400 |
| commit | c34c9a008f1314de6eeb41b1f57b6056a3f951f6 (patch) | |
| tree | 810454c78a3a917e7e29248db296b2f2f79e04eb | |
| parent | 64f946a6a0a6e08d7046ab98776928abe24f8d93 (diff) | |
| parent | bde2e876034219b614395ccd66cb45b0e23d348f (diff) | |
Merged trunk and fixed conflicts.
| -rw-r--r-- | nova/api/openstack/servers.py | 29 | ||||
| -rw-r--r-- | nova/compute/api.py | 11 | ||||
| -rw-r--r-- | nova/compute/manager.py | 8 | ||||
| -rw-r--r-- | nova/ipv6/account_identifier.py | 6 | ||||
| -rw-r--r-- | nova/ipv6/rfc2462.py | 4 | ||||
| -rw-r--r-- | nova/tests/api/openstack/test_server_actions.py | 137 | ||||
| -rw-r--r-- | nova/tests/api/openstack/test_servers.py | 135 | ||||
| -rw-r--r-- | nova/tests/test_compute.py | 5 | ||||
| -rw-r--r-- | nova/tests/test_ipv6.py | 38 | ||||
| -rw-r--r-- | nova/tests/test_versions.py | 61 |
10 files changed, 387 insertions, 47 deletions
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c5fdda1cf..e0dd9bdb1 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -602,8 +602,10 @@ class ControllerV10(Controller): LOG.debug(msg) raise exc.HTTPBadRequest(explanation=msg) + password = utils.generate_password(16) + try: - self.compute_api.rebuild(context, instance_id, image_id) + self.compute_api.rebuild(context, instance_id, image_id, password) except exception.RebuildRequiresActiveInstance: msg = _("Instance %s must be active to rebuild.") % instance_id raise exc.HTTPConflict(explanation=msg) @@ -738,14 +740,25 @@ class ControllerV11(Controller): self._validate_metadata(metadata) self._decode_personalities(personalities) + password = info["rebuild"].get("adminPass", + utils.generate_password(16)) + try: - self.compute_api.rebuild(context, instance_id, image_href, name, - metadata, personalities) + self.compute_api.rebuild(context, instance_id, image_href, + password, name=name, metadata=metadata, + files_to_inject=personalities) except exception.RebuildRequiresActiveInstance: msg = _("Instance %s must be active to rebuild.") % instance_id raise exc.HTTPConflict(explanation=msg) + except exception.InstanceNotFound: + msg = _("Instance %s could not be found") % instance_id + raise exc.HTTPNotFound(explanation=msg) - return webob.Response(status_int=202) + instance = self.compute_api.routing_get(context, instance_id) + view = self._build_view(request, instance, is_detail=True) + view['server']['adminPass'] = password + + return view @common.check_snapshots_enabled def _action_create_image(self, input_dict, req, instance_id): @@ -812,6 +825,9 @@ class HeadersSerializer(wsgi.ResponseHeadersSerializer): def delete(self, response, data): response.status_int = 204 + def action(self, response, data): + response.status_int = 202 + class ServerXMLSerializer(wsgi.XMLDictSerializer): @@ -933,6 +949,11 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): node.setAttribute('adminPass', server_dict['server']['adminPass']) return self.to_xml_string(node, True) + def action(self, server_dict): + #NOTE(bcwaldon): We need a way to serialize actions individually. This + # assumes all actions return a server entity + return self.create(server_dict) + def update(self, server_dict): xml_doc = minidom.Document() node = self._server_to_xml_detailed(xml_doc, diff --git a/nova/compute/api.py b/nova/compute/api.py index 7ed41fbbc..960d28bd0 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -57,15 +57,15 @@ def generate_default_hostname(instance): """Default function to generate a hostname given an instance reference.""" display_name = instance['display_name'] if display_name is None: - return 'server_%d' % (instance['id'],) + return 'server-%d' % (instance['id'],) table = '' deletions = '' for i in xrange(256): c = chr(i) if ('a' <= c <= 'z') or ('0' <= c <= '9') or (c == '-'): table += c - elif c == ' ': - table += '_' + elif c in " _": + table += '-' elif ('A' <= c <= 'Z'): table += c.lower() else: @@ -1041,8 +1041,8 @@ class API(base.Base): self._cast_compute_message('reboot_instance', context, instance_id) @scheduler_api.reroute_compute("rebuild") - def rebuild(self, context, instance_id, image_href, name=None, - metadata=None, files_to_inject=None): + def rebuild(self, context, instance_id, image_href, admin_password, + name=None, metadata=None, files_to_inject=None): """Rebuild the given instance with the provided metadata.""" instance = db.api.instance_get(context, instance_id) name = name or instance["display_name"] @@ -1065,6 +1065,7 @@ class API(base.Base): task_state=task_states.REBUILDING) rebuild_params = { + "new_pass": admin_password, "image_ref": image_href, "injected_files": files_to_inject, } diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 0a1dc13be..7697e8e6c 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -525,6 +525,7 @@ class ComputeManager(manager.SchedulerDependentManager): :param context: `nova.RequestContext` object :param instance_id: Instance identifier (integer) :param image_ref: Image identifier (href or integer) + :param new_pass: password to set on rebuilt instance """ context = context.elevated() @@ -552,13 +553,18 @@ class ComputeManager(manager.SchedulerDependentManager): instance_ref.image_ref = image_ref instance_ref.injected_files = kwargs.get('injected_files', []) network_info = self.network_api.get_instance_nw_info(context, - instance_ref) + instance_ref) + bd_mapping = self._setup_block_device_mapping(context, instance_id) self._instance_update(context, instance_id, vm_state=vm_states.REBUILDING, task_state=task_states.SPAWN) + # pull in new password here since the original password isn't in the db + instance_ref.admin_pass = kwargs.get('new_pass', + utils.generate_password(FLAGS.password_length)) + self.driver.spawn(context, instance_ref, network_info, bd_mapping) current_power_state = self._get_power_state(context, instance_ref) diff --git a/nova/ipv6/account_identifier.py b/nova/ipv6/account_identifier.py index 258678f0a..27bb01988 100644 --- a/nova/ipv6/account_identifier.py +++ b/nova/ipv6/account_identifier.py @@ -34,8 +34,12 @@ def to_global(prefix, mac, project_id): mac_addr = netaddr.IPAddress(int_addr) maskIP = netaddr.IPNetwork(prefix).ip return (project_hash ^ static_num ^ mac_addr | maskIP).format() - except TypeError: + except netaddr.AddrFormatError: raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac) + except TypeError: + raise TypeError(_('Bad prefix for to_global_ipv6: %s') % prefix) + except NameError: + raise TypeError(_('Bad project_id for to_global_ipv6: %s') % project_id) def to_mac(ipv6_address): diff --git a/nova/ipv6/rfc2462.py b/nova/ipv6/rfc2462.py index 0074efe98..acf42d201 100644 --- a/nova/ipv6/rfc2462.py +++ b/nova/ipv6/rfc2462.py @@ -30,8 +30,10 @@ def to_global(prefix, mac, project_id): maskIP = netaddr.IPNetwork(prefix).ip return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).\ format() - except TypeError: + except netaddr.AddrFormatError: raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac) + except TypeError: + raise TypeError(_('Bad prefix for to_global_ipv6: %s') % prefix) def to_mac(ipv6_address): diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index bfbc77c96..0cbbe271d 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -1,4 +1,5 @@ import base64 +import datetime import json import stubout @@ -6,9 +7,11 @@ import webob from nova import context from nova import utils +from nova import exception from nova import flags from nova.api.openstack import create_instance_helper from nova.compute import vm_states +from nova.compute import instance_types import nova.db.api from nova import test from nova.tests.api.openstack import common @@ -19,47 +22,46 @@ FLAGS = flags.FLAGS def return_server_by_id(context, id): - return _get_instance() + return stub_instance(id) def instance_update(context, instance_id, kwargs): - return _get_instance() + return stub_instance(instance_id) -def return_server_with_state(vm_state, task_state=None): +def return_server_with_attributes(**kwargs): def _return_server(context, id): - instance = _get_instance() - instance['vm_state'] = vm_state - instance['task_state'] = task_state - return instance + return stub_instance(id, **kwargs) return _return_server +def return_server_with_state(vm_state, task_state=None) + return return_server_with_attributes(vm_state=vm_state, + task_state=task_state) + + def return_server_with_uuid_and_state(vm_state, task_state=None): def _return_server(context, id): return return_server_with_state(vm_state, task_state) return _return_server -class MockSetAdminPassword(object): - def __init__(self): - self.instance_id = None - self.password = None - - def __call__(self, context, instance_id, password): - self.instance_id = instance_id - self.password = password +def stub_instance(id, metadata=None, image_ref="10", flavor_id="1", name=None): + if metadata is not None: + metadata_items = [{'key':k, 'value':v} for k, v in metadata.items()] + else: + metadata_items = [{'key':'seq', 'value':id}] + inst_type = instance_types.get_instance_type_by_flavor_id(int(flavor_id)) -def _get_instance(): instance = { - "id": 1, - "created_at": "2010-10-10 12:00:00", - "updated_at": "2010-11-11 11:00:00", + "id": int(id), + "created_at": datetime.datetime(2010, 10, 10, 12, 0, 0), + "updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0), "admin_pass": "", - "user_id": "", - "project_id": "", - "image_ref": "5", + "user_id": "fake", + "project_id": "fake", + "image_ref": image_ref, "kernel_id": "", "ramdisk_id": "", "launch_index": 0, @@ -72,9 +74,7 @@ def _get_instance(): "local_gb": 0, "hostname": "", "host": "", - "instance_type": { - "flavorid": 1, - }, + "instance_type": dict(inst_type), "user_data": "", "reservation_id": "", "mac_address": "", @@ -82,17 +82,34 @@ def _get_instance(): "launched_at": utils.utcnow(), "terminated_at": utils.utcnow(), "availability_zone": "", - "display_name": "test_server", + "display_name": name or "server%s" % id, "display_description": "", "locked": False, - "metadata": [], - #"address": , - #"floating_ips": [{"address":ip} for ip in public_addresses]} - "uuid": "deadbeef-feed-edee-beef-d0ea7beefedd"} + "metadata": metadata_items, + "access_ip_v4": "", + "access_ip_v6": "", + "uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + "virtual_interfaces": [], + } + + instance["fixed_ips"] = { + "address": '192.168.0.1', + "floating_ips": [], + } return instance +class MockSetAdminPassword(object): + def __init__(self): + self.instance_id = None + self.password = None + + def __call__(self, context, instance_id, password): + self.instance_id = instance_id + self.password = password + + class ServerActionsTest(test.TestCase): def setUp(self): @@ -599,6 +616,9 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(res.status_int, 400) def test_server_rebuild_accepted_minimum(self): + new_return_server = return_server_with_attributes(image_ref='2') + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + body = { "rebuild": { "imageRef": "http://localhost/images/2", @@ -612,6 +632,9 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) + body = json.loads(res.body) + self.assertEqual(body['server']['image']['id'], '2') + self.assertEqual(len(body['server']['adminPass']), 16) def test_server_rebuild_rejected_when_building(self): body = { @@ -635,12 +658,15 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(res.status_int, 409) def test_server_rebuild_accepted_with_metadata(self): + metadata = {'new': 'metadata'} + + new_return_server = return_server_with_attributes(metadata=metadata) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + body = { "rebuild": { "imageRef": "http://localhost/images/2", - "metadata": { - "new": "metadata", - }, + "metadata": metadata, }, } @@ -651,6 +677,8 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) + body = json.loads(res.body) + self.assertEqual(body['server']['metadata'], metadata) def test_server_rebuild_accepted_with_bad_metadata(self): body = { @@ -720,6 +748,49 @@ class ServerActionsTestV11(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) + body = json.loads(res.body) + self.assertTrue('personality' not in body['server']) + + def test_server_rebuild_admin_pass(self): + new_return_server = return_server_with_attributes(image_ref='2') + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + "adminPass": "asdf", + }, + } + + req = webob.Request.blank('/v1.1/fake/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + body = json.loads(res.body) + self.assertEqual(body['server']['image']['id'], '2') + self.assertEqual(body['server']['adminPass'], 'asdf') + + def test_server_rebuild_server_not_found(self): + def server_not_found(self, instance_id): + raise exception.InstanceNotFound(instance_id=instance_id) + self.stubs.Set(nova.db.api, 'instance_get', server_not_found) + + body = { + "rebuild": { + "imageRef": "http://localhost/images/2", + }, + } + + req = webob.Request.blank('/v1.1/fake/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 404) def test_resize_server(self): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index dc4e73508..3905e4f7a 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -4594,3 +4594,138 @@ class ServerXMLSerializationTest(test.TestCase): str(ip['version'])) self.assertEqual(str(ip_elem.get('addr')), str(ip['addr'])) + + def test_action(self): + serializer = servers.ServerXMLSerializer() + + fixture = { + "server": { + "id": 1, + "uuid": FAKE_UUID, + 'created': self.TIMESTAMP, + 'updated': self.TIMESTAMP, + "progress": 0, + "name": "test_server", + "status": "BUILD", + "accessIPv4": "1.2.3.4", + "accessIPv6": "fead::1234", + "hostId": "e4d909c290d0fb1ca068ffaddf22cbd0", + "adminPass": "test_password", + "image": { + "id": "5", + "links": [ + { + "rel": "bookmark", + "href": self.IMAGE_BOOKMARK, + }, + ], + }, + "flavor": { + "id": "1", + "links": [ + { + "rel": "bookmark", + "href": self.FLAVOR_BOOKMARK, + }, + ], + }, + "addresses": { + "network_one": [ + { + "version": 4, + "addr": "67.23.10.138", + }, + { + "version": 6, + "addr": "::babe:67.23.10.138", + }, + ], + "network_two": [ + { + "version": 4, + "addr": "67.23.10.139", + }, + { + "version": 6, + "addr": "::babe:67.23.10.139", + }, + ], + }, + "metadata": { + "Open": "Stack", + "Number": "1", + }, + 'links': [ + { + 'href': self.SERVER_HREF, + 'rel': 'self', + }, + { + 'href': self.SERVER_BOOKMARK, + 'rel': 'bookmark', + }, + ], + } + } + + output = serializer.serialize(fixture, 'action') + root = etree.XML(output) + xmlutil.validate_schema(root, 'server') + + expected_server_href = self.SERVER_HREF + expected_server_bookmark = self.SERVER_BOOKMARK + expected_image_bookmark = self.IMAGE_BOOKMARK + expected_flavor_bookmark = self.FLAVOR_BOOKMARK + expected_now = self.TIMESTAMP + expected_uuid = FAKE_UUID + server_dict = fixture['server'] + + for key in ['name', 'id', 'uuid', 'created', 'accessIPv4', + 'updated', 'progress', 'status', 'hostId', + 'accessIPv6', 'adminPass']: + self.assertEqual(root.get(key), str(server_dict[key])) + + link_nodes = root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 2) + for i, link in enumerate(server_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + metadata_root = root.find('{0}metadata'.format(NS)) + metadata_elems = metadata_root.findall('{0}meta'.format(NS)) + self.assertEqual(len(metadata_elems), 2) + for i, metadata_elem in enumerate(metadata_elems): + (meta_key, meta_value) = server_dict['metadata'].items()[i] + self.assertEqual(str(metadata_elem.get('key')), str(meta_key)) + self.assertEqual(str(metadata_elem.text).strip(), str(meta_value)) + + image_root = root.find('{0}image'.format(NS)) + self.assertEqual(image_root.get('id'), server_dict['image']['id']) + link_nodes = image_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['image']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + flavor_root = root.find('{0}flavor'.format(NS)) + self.assertEqual(flavor_root.get('id'), server_dict['flavor']['id']) + link_nodes = flavor_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['flavor']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + addresses_root = root.find('{0}addresses'.format(NS)) + addresses_dict = server_dict['addresses'] + network_elems = addresses_root.findall('{0}network'.format(NS)) + self.assertEqual(len(network_elems), 2) + for i, network_elem in enumerate(network_elems): + network = addresses_dict.items()[i] + self.assertEqual(str(network_elem.get('id')), str(network[0])) + ip_elems = network_elem.findall('{0}ip'.format(NS)) + for z, ip_elem in enumerate(ip_elems): + ip = network[1][z] + self.assertEqual(str(ip_elem.get('version')), + str(ip['version'])) + self.assertEqual(str(ip_elem.get('addr')), + str(ip['addr'])) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index cb6a6f54a..766a7da9b 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -176,8 +176,9 @@ class ComputeTestCase(test.TestCase): db.instance_destroy(self.context, instance_id) def test_default_hostname_generator(self): - cases = [(None, 'server_1'), ('Hello, Server!', 'hello_server'), - ('<}\x1fh\x10e\x08l\x02l\x05o\x12!{>', 'hello')] + cases = [(None, 'server-1'), ('Hello, Server!', 'hello-server'), + ('<}\x1fh\x10e\x08l\x02l\x05o\x12!{>', 'hello'), + ('hello_server', 'hello-server')] for display_name, hostname in cases: ref = self.compute_api.create(self.context, instance_types.get_default_instance_type(), None, diff --git a/nova/tests/test_ipv6.py b/nova/tests/test_ipv6.py index d123df6f1..04c1b5598 100644 --- a/nova/tests/test_ipv6.py +++ b/nova/tests/test_ipv6.py @@ -40,6 +40,25 @@ class IPv6RFC2462TestCase(test.TestCase): mac = ipv6.to_mac('2001:db8::216:3eff:fe33:4455') self.assertEquals(mac, '00:16:3e:33:44:55') + def test_to_global_with_bad_mac(self): + bad_mac = '02:16:3e:33:44:5Z' + self.assertRaises(TypeError, ipv6.to_global, + '2001:db8::', bad_mac, 'test') + + def test_to_global_with_bad_prefix(self): + bad_prefix = '82' + self.assertRaises(TypeError, ipv6.to_global, + bad_prefix, + '2001:db8::216:3eff:fe33:4455', + 'test') + + def test_to_global_with_bad_project(self): + bad_project = 'non-existent-project-name' + self.assertRaises(TypeError, ipv6.to_global, + '2001:db8::', + '2001:db8::a94a:8fe5:ff33:4455', + bad_project) + class IPv6AccountIdentiferTestCase(test.TestCase): """Unit tests for IPv6 account_identifier backend operations.""" @@ -55,3 +74,22 @@ class IPv6AccountIdentiferTestCase(test.TestCase): def test_to_mac(self): mac = ipv6.to_mac('2001:db8::a94a:8fe5:ff33:4455') self.assertEquals(mac, '02:16:3e:33:44:55') + + def test_to_global_with_bad_mac(self): + bad_mac = '02:16:3e:33:44:5X' + self.assertRaises(TypeError, ipv6.to_global, + '2001:db8::', bad_mac, 'test') + + def test_to_global_with_bad_prefix(self): + bad_prefix = '78' + self.assertRaises(TypeError, ipv6.to_global, + bad_prefix, + '2001:db8::a94a:8fe5:ff33:4455', + 'test') + + def test_to_global_with_bad_project(self): + bad_project = 'non-existent-project-name' + self.assertRaises(TypeError, ipv6.to_global, + '2001:db8::', + '2001:db8::a94a:8fe5:ff33:4455', + bad_project) diff --git a/nova/tests/test_versions.py b/nova/tests/test_versions.py new file mode 100644 index 000000000..4621b042b --- /dev/null +++ b/nova/tests/test_versions.py @@ -0,0 +1,61 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Ken Pepple +# +# 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 exception +from nova import test +from nova import utils +from nova import version + + +class VersionTestCase(test.TestCase): + """Test cases for Versions code""" + def setUp(self): + """setup test with unchanging values""" + super(VersionTestCase, self).setUp() + self.version = version + self.version.FINAL = False + self.version.NOVA_VERSION = ['2012', '10'] + self.version.YEAR, self.version.COUNT = self.version.NOVA_VERSION + self.version.version_info = {'branch_nick': u'LOCALBRANCH', + 'revision_id': 'LOCALREVISION', + 'revno': 0} + + def test_version_string_is_good(self): + """Ensure version string works""" + self.assertEqual("2012.10-dev", self.version.version_string()) + + def test_canonical_version_string_is_good(self): + """Ensure canonical version works""" + self.assertEqual("2012.10", self.version.canonical_version_string()) + + def test_final_version_strings_are_identical(self): + """Ensure final version strings match only at release""" + self.assertNotEqual(self.version.canonical_version_string(), + self.version.version_string()) + self.version.FINAL = True + self.assertEqual(self.version.canonical_version_string(), + self.version.version_string()) + + def test_vcs_version_string_is_good(self): + """Ensure uninstalled code generates local """ + self.assertEqual("LOCALBRANCH:LOCALREVISION", + self.version.vcs_version_string()) + + def test_version_string_with_vcs_is_good(self): + """Ensure uninstalled code get version string""" + self.assertEqual("2012.10-LOCALBRANCH:LOCALREVISION", + self.version.version_string_with_vcs()) |
