summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSandy Walsh <sandy.walsh@rackspace.com>2011-09-15 16:52:58 -0700
committerSandy Walsh <sandy.walsh@rackspace.com>2011-09-15 16:52:58 -0700
commit06c4eb3d8570e4f07a6c3012d2dc2ab907c460d8 (patch)
treefc61a8f43a941586b973d46e458dcffcf0432b33
parent2fd55ddfdf065a8792fdd0e3f3e97b5e56e4a4a3 (diff)
parent8a91c953b286c9de4a715cd01ab82c571db4e3c9 (diff)
downloadnova-06c4eb3d8570e4f07a6c3012d2dc2ab907c460d8.tar.gz
nova-06c4eb3d8570e4f07a6c3012d2dc2ab907c460d8.tar.xz
nova-06c4eb3d8570e4f07a6c3012d2dc2ab907c460d8.zip
trunk merge
-rw-r--r--nova/api/ec2/cloud.py12
-rw-r--r--nova/api/openstack/common.py24
-rw-r--r--nova/api/openstack/contrib/flavorextradata.py46
-rw-r--r--nova/api/openstack/contrib/volumes.py3
-rw-r--r--nova/api/openstack/flavors.py4
-rw-r--r--nova/api/openstack/schemas/v1.1/flavor.rng4
-rw-r--r--nova/api/openstack/versions.py4
-rw-r--r--nova/api/openstack/views/flavors.py3
-rw-r--r--nova/api/openstack/views/images.py10
-rw-r--r--nova/api/openstack/wsgi.py45
-rw-r--r--nova/db/api.py8
-rw-r--r--nova/db/sqlalchemy/api.py5
-rw-r--r--nova/exception.py2
-rw-r--r--nova/network/manager.py3
-rw-r--r--nova/tests/api/openstack/contrib/test_volumes.py73
-rw-r--r--nova/tests/api/openstack/test_api.py25
-rw-r--r--nova/tests/api/openstack/test_common.py40
-rw-r--r--nova/tests/api/openstack/test_extensions.py1
-rw-r--r--nova/tests/api/openstack/test_flavors.py52
-rw-r--r--nova/tests/api/openstack/test_images.py66
-rw-r--r--nova/tests/api/openstack/test_limits.py14
-rw-r--r--nova/tests/api/openstack/test_wsgi.py57
-rw-r--r--nova/tests/db/fakes.py4
-rw-r--r--nova/tests/fake_network.py30
-rw-r--r--nova/tests/test_libvirt.py88
-rw-r--r--nova/tests/vmwareapi/stubs.py2
-rw-r--r--nova/virt/vmwareapi/fake.py2
-rw-r--r--nova/virt/vmwareapi/vif.py27
-rw-r--r--nova/virt/vmwareapi/vm_util.py26
-rw-r--r--nova/virt/vmwareapi/vmops.py106
-rw-r--r--tools/esx/guest_tool.py70
31 files changed, 576 insertions, 280 deletions
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 0efb90d6e..fb1afa43a 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -326,11 +326,6 @@ class CloudController(object):
instance_ref = db.instance_get(ctxt, instance_ref[0]['id'])
mpi = self._get_mpi_data(ctxt, instance_ref['project_id'])
- if instance_ref['key_name']:
- keys = {'0': {'_name': instance_ref['key_name'],
- 'openssh-key': instance_ref['key_data']}}
- else:
- keys = ''
hostname = instance_ref['hostname']
host = instance_ref['host']
availability_zone = self._get_availability_zone_by_host(ctxt, host)
@@ -358,11 +353,16 @@ class CloudController(object):
'placement': {'availability-zone': availability_zone},
'public-hostname': hostname,
'public-ipv4': floating_ip or '',
- 'public-keys': keys,
'reservation-id': instance_ref['reservation_id'],
'security-groups': security_groups,
'mpi': mpi}}
+ # public-keys should be in meta-data only if user specified one
+ if instance_ref['key_name']:
+ data['meta-data']['public-keys'] = {
+ '0': {'_name': instance_ref['key_name'],
+ 'openssh-key': instance_ref['key_data']}}
+
for image_type in ['kernel', 'ramdisk']:
if instance_ref.get('%s_id' % image_type):
ec2_id = self.image_ec2_id(instance_ref['%s_id' % image_type],
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index a836a584c..ca7848678 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -187,30 +187,16 @@ def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit):
def get_id_from_href(href):
- """Return the id portion of a url as an int.
+ """Return the id or uuid portion of a url.
Given: 'http://www.foo.com/bar/123?q=4'
- Returns: 123
+ Returns: '123'
- In order to support local hrefs, the href argument can be just an id:
- Given: '123'
- Returns: 123
+ Given: 'http://www.foo.com/bar/abc123?q=4'
+ Returns: 'abc123'
"""
- LOG.debug(_("Attempting to treat %(href)s as an integer ID.") % locals())
-
- try:
- return int(href)
- except ValueError:
- pass
-
- LOG.debug(_("Attempting to treat %(href)s as a URL.") % locals())
-
- try:
- return int(urlparse.urlsplit(href).path.split('/')[-1])
- except ValueError as error:
- LOG.debug(_("Failed to parse ID from %(href)s: %(error)s") % locals())
- raise
+ return urlparse.urlsplit("%s" % href).path.split('/')[-1]
def remove_version_from_href(href):
diff --git a/nova/api/openstack/contrib/flavorextradata.py b/nova/api/openstack/contrib/flavorextradata.py
new file mode 100644
index 000000000..d0554c7b4
--- /dev/null
+++ b/nova/api/openstack/contrib/flavorextradata.py
@@ -0,0 +1,46 @@
+# Copyright 2011 Canonical Ltd.
+# 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.
+
+"""
+The Flavor extra data extension
+Openstack API version 1.1 lists "name", "ram", "disk", "vcpus" as flavor
+attributes. This extension adds to that list:
+ rxtx_cap
+ rxtx_quota
+ swap
+"""
+
+from nova.api.openstack import extensions
+
+
+class Flavorextradata(extensions.ExtensionDescriptor):
+ """The Flavor extra data extension for the OpenStack API."""
+
+ def get_name(self):
+ return "FlavorExtraData"
+
+ def get_alias(self):
+ return "os-flavor-extra-data"
+
+ def get_description(self):
+ return "Provide additional data for flavors"
+
+ def get_namespace(self):
+ return "http://docs.openstack.org/ext/flavor_extra_data/api/v1.1"
+
+ def get_updated(self):
+ return "2011-09-14T00:00:00+00:00"
+
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py
index d62225e58..9d4254f1f 100644
--- a/nova/api/openstack/contrib/volumes.py
+++ b/nova/api/openstack/contrib/volumes.py
@@ -372,8 +372,7 @@ class BootFromVolumeController(servers.ControllerV11):
for key in ['instance_type', 'image_ref']:
inst[key] = extra_values[key]
- builder = self._get_view_builder(req)
- server = builder.build(inst, is_detail=True)
+ server = self._build_view(req, inst, is_detail=True)
server['server']['adminPass'] = extra_values['password']
return server
diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py
index 805aad772..8a310c900 100644
--- a/nova/api/openstack/flavors.py
+++ b/nova/api/openstack/flavors.py
@@ -92,6 +92,10 @@ class FlavorXMLSerializer(wsgi.XMLDictSerializer):
if detailed:
flavor_elem.set('ram', str(flavor_dict['ram']))
flavor_elem.set('disk', str(flavor_dict['disk']))
+
+ for attr in ("vcpus", "swap", "rxtx_quota", "rxtx_cap"):
+ flavor_elem.set(attr, str(flavor_dict.get(attr, "")))
+
for link in flavor_dict.get('links', []):
elem = etree.SubElement(flavor_elem,
'{%s}link' % xmlutil.XMLNS_ATOM)
diff --git a/nova/api/openstack/schemas/v1.1/flavor.rng b/nova/api/openstack/schemas/v1.1/flavor.rng
index a00e4e9ee..6d3adc8dc 100644
--- a/nova/api/openstack/schemas/v1.1/flavor.rng
+++ b/nova/api/openstack/schemas/v1.1/flavor.rng
@@ -4,6 +4,10 @@
<attribute name="id"> <text/> </attribute>
<attribute name="ram"> <text/> </attribute>
<attribute name="disk"> <text/> </attribute>
+ <attribute name="rxtx_cap"> <text/> </attribute>
+ <attribute name="rxtx_quota"> <text/> </attribute>
+ <attribute name="swap"> <text/> </attribute>
+ <attribute name="vcpus"> <text/> </attribute>
<zeroOrMore>
<externalRef href="../atom-link.rng"/>
</zeroOrMore>
diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py
index 31dd9dc11..75a1d0ba4 100644
--- a/nova/api/openstack/versions.py
+++ b/nova/api/openstack/versions.py
@@ -107,7 +107,9 @@ class Versions(wsgi.Resource):
headers_serializer=headers_serializer)
supported_content_types = ('application/json',
+ 'application/vnd.openstack.compute+json',
'application/xml',
+ 'application/vnd.openstack.compute+xml',
'application/atom+xml')
deserializer = VersionsRequestDeserializer(
supported_content_types=supported_content_types)
@@ -308,7 +310,9 @@ def create_resource(version='1.0'):
serializer = wsgi.ResponseSerializer(body_serializers)
supported_content_types = ('application/json',
+ 'application/vnd.openstack.compute+json',
'application/xml',
+ 'application/vnd.openstack.compute+xml',
'application/atom+xml')
deserializer = wsgi.RequestDeserializer(
supported_content_types=supported_content_types)
diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py
index aea34b424..def969a6c 100644
--- a/nova/api/openstack/views/flavors.py
+++ b/nova/api/openstack/views/flavors.py
@@ -50,6 +50,9 @@ class ViewBuilder(object):
"disk": flavor_obj["local_gb"],
}
+ for key in ("vcpus", "swap", "rxtx_quota", "rxtx_cap"):
+ detail[key] = flavor_obj.get(key, "")
+
detail.update(simple)
return detail
diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py
index 8983b2957..86e8d7f3a 100644
--- a/nova/api/openstack/views/images.py
+++ b/nova/api/openstack/views/images.py
@@ -71,6 +71,7 @@ class ViewBuilder(object):
}
self._build_server(image, image_obj)
+ self._build_image_id(image, image_obj)
if detail:
image.update({
@@ -96,6 +97,12 @@ class ViewBuilderV10(ViewBuilder):
except (KeyError, ValueError):
pass
+ def _build_image_id(self, image, image_obj):
+ try:
+ image['id'] = int(image_obj['id'])
+ except ValueError:
+ pass
+
class ViewBuilderV11(ViewBuilder):
"""OpenStack API v1.1 Image Builder"""
@@ -119,6 +126,9 @@ class ViewBuilderV11(ViewBuilder):
except KeyError:
return
+ def _build_image_id(self, image, image_obj):
+ image['id'] = "%s" % image_obj['id']
+
def generate_href(self, image_id):
"""Return an href string pointing to this object."""
return os.path.join(self.base_url, self.project_id,
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index bdcadcb99..180f328b9 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -1,3 +1,19 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+# 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 json
from lxml import etree
@@ -19,6 +35,21 @@ XMLNS_ATOM = 'http://www.w3.org/2005/Atom'
LOG = logging.getLogger('nova.api.openstack.wsgi')
+# The vendor content types should serialize identically to the non-vendor
+# content types. So to avoid littering the code with both options, we
+# map the vendor to the other when looking up the type
+_CONTENT_TYPE_MAP = {
+ 'application/vnd.openstack.compute+json': 'application/json',
+ 'application/vnd.openstack.compute+xml': 'application/xml',
+}
+
+_SUPPORTED_CONTENT_TYPES = (
+ 'application/json',
+ 'application/vnd.openstack.compute+json',
+ 'application/xml',
+ 'application/vnd.openstack.compute+xml',
+)
+
class Request(webob.Request):
"""Add some Openstack API-specific logic to the base webob.Request."""
@@ -30,7 +61,7 @@ class Request(webob.Request):
"""
supported_content_types = supported_content_types or \
- ('application/json', 'application/xml')
+ _SUPPORTED_CONTENT_TYPES
parts = self.path.rsplit('.', 1)
if len(parts) > 1:
@@ -52,7 +83,7 @@ class Request(webob.Request):
if not "Content-Type" in self.headers:
return None
- allowed_types = ("application/xml", "application/json")
+ allowed_types = _SUPPORTED_CONTENT_TYPES
content_type = self.content_type
if content_type not in allowed_types:
@@ -192,7 +223,7 @@ class RequestDeserializer(object):
supported_content_types=None):
self.supported_content_types = supported_content_types or \
- ('application/json', 'application/xml')
+ _SUPPORTED_CONTENT_TYPES
self.body_deserializers = {
'application/xml': XMLDeserializer(),
@@ -250,7 +281,8 @@ class RequestDeserializer(object):
def get_body_deserializer(self, content_type):
try:
- return self.body_deserializers[content_type]
+ ctype = _CONTENT_TYPE_MAP.get(content_type, content_type)
+ return self.body_deserializers[ctype]
except (KeyError, TypeError):
raise exception.InvalidContentType(content_type=content_type)
@@ -316,7 +348,7 @@ class XMLDictSerializer(DictSerializer):
def to_xml_string(self, node, has_atom=False):
self._add_xmlns(node, has_atom)
- return node.toprettyxml(indent=' ', encoding='UTF-8')
+ return node.toxml('UTF-8')
#NOTE (ameade): the has_atom should be removed after all of the
# xml serializers and view builders have been updated to the current
@@ -444,7 +476,8 @@ class ResponseSerializer(object):
def get_body_serializer(self, content_type):
try:
- return self.body_serializers[content_type]
+ ctype = _CONTENT_TYPE_MAP.get(content_type, content_type)
+ return self.body_serializers[ctype]
except (KeyError, TypeError):
raise exception.InvalidContentType(content_type=content_type)
diff --git a/nova/db/api.py b/nova/db/api.py
index a9d2dc065..05d81d8b2 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -261,11 +261,13 @@ def floating_ip_disassociate(context, address):
return IMPL.floating_ip_disassociate(context, address)
-def floating_ip_fixed_ip_associate(context, floating_address, fixed_address):
+def floating_ip_fixed_ip_associate(context, floating_address,
+ fixed_address, host):
"""Associate an floating ip to a fixed_ip by address."""
return IMPL.floating_ip_fixed_ip_associate(context,
floating_address,
- fixed_address)
+ fixed_address,
+ host)
def floating_ip_get_all(context):
@@ -367,7 +369,7 @@ def fixed_ip_get_all(context):
def fixed_ip_get_all_by_instance_host(context, host):
"""Get all allocated fixed ips filtered by instance host."""
- return IMPL.fixed_ip_get_all_instance_by_host(context, host)
+ return IMPL.fixed_ip_get_all_by_instance_host(context, host)
def fixed_ip_get_by_address(context, address):
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index e5a661c7f..8ea154490 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -529,7 +529,8 @@ def floating_ip_count_by_project(context, project_id):
@require_context
-def floating_ip_fixed_ip_associate(context, floating_address, fixed_address):
+def floating_ip_fixed_ip_associate(context, floating_address,
+ fixed_address, host):
session = get_session()
with session.begin():
# TODO(devcamcar): How to ensure floating_id belongs to user?
@@ -540,6 +541,7 @@ def floating_ip_fixed_ip_associate(context, floating_address, fixed_address):
fixed_address,
session=session)
floating_ip_ref.fixed_ip = fixed_ip_ref
+ floating_ip_ref.host = host
floating_ip_ref.save(session=session)
@@ -583,6 +585,7 @@ def floating_ip_disassociate(context, address):
else:
fixed_ip_address = None
floating_ip_ref.fixed_ip = None
+ floating_ip_ref.host = None
floating_ip_ref.save(session=session)
return fixed_ip_address
diff --git a/nova/exception.py b/nova/exception.py
index a3cbb98cf..4f25d3721 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -506,7 +506,7 @@ class FixedIpInvalid(Invalid):
message = _("Fixed IP address %(address)s is invalid.")
-class NoMoreFixedIps(Error):
+class NoMoreFixedIps(NovaException):
message = _("Zero fixed ips available.")
diff --git a/nova/network/manager.py b/nova/network/manager.py
index da360720b..70e51888f 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -289,7 +289,8 @@ class FloatingIP(object):
self.db.floating_ip_fixed_ip_associate(context,
floating_address,
- fixed_address)
+ fixed_address,
+ self.host)
self.driver.bind_floating_ip(floating_address)
self.driver.ensure_floating_forward(floating_address, fixed_address)
diff --git a/nova/tests/api/openstack/contrib/test_volumes.py b/nova/tests/api/openstack/contrib/test_volumes.py
new file mode 100644
index 000000000..443ec399f
--- /dev/null
+++ b/nova/tests/api/openstack/contrib/test_volumes.py
@@ -0,0 +1,73 @@
+# Copyright 2011 Josh Durgin
+# 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 datetime
+import json
+import webob
+
+import nova
+from nova import context
+from nova import test
+from nova.api.openstack.contrib.volumes import BootFromVolumeController
+from nova.compute import instance_types
+from nova.tests.api.openstack import fakes
+from nova.tests.api.openstack.test_servers import fake_gen_uuid
+
+
+def fake_compute_api_create(cls, context, instance_type, image_href, **kwargs):
+ inst_type = instance_types.get_instance_type_by_flavor_id(2)
+ return [{'id': 1,
+ 'display_name': 'test_server',
+ 'uuid': fake_gen_uuid(),
+ 'instance_type': dict(inst_type),
+ 'access_ip_v4': '1.2.3.4',
+ 'access_ip_v6': 'fead::1234',
+ 'image_ref': 3,
+ 'user_id': 'fake',
+ 'project_id': 'fake',
+ 'created_at': datetime.datetime(2010, 10, 10, 12, 0, 0),
+ 'updated_at': datetime.datetime(2010, 11, 11, 11, 0, 0),
+ }]
+
+
+class BootFromVolumeTest(test.TestCase):
+
+ def setUp(self):
+ super(BootFromVolumeTest, self).setUp()
+ self.stubs.Set(nova.compute.API, 'create', fake_compute_api_create)
+
+ def test_create_root_volume(self):
+ body = dict(server=dict(
+ name='test_server', imageRef=3,
+ flavorRef=2, min_count=1, max_count=1,
+ block_device_mapping=[dict(
+ volume_id=1,
+ device_name='/dev/vda',
+ virtual='root',
+ delete_on_termination=False,
+ )]
+ ))
+ req = webob.Request.blank('/v1.1/fake/os-volumes_boot')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers['content-type'] = 'application/json'
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 200)
+ server = json.loads(res.body)['server']
+ self.assertEqual(1, server['id'])
+ self.assertEqual(2, int(server['flavor']['id']))
+ self.assertEqual(u'test_server', server['name'])
+ self.assertEqual(3, int(server['image']['id']))
+ self.assertEqual(16, len(server['adminPass']))
diff --git a/nova/tests/api/openstack/test_api.py b/nova/tests/api/openstack/test_api.py
index 7321c329f..b7a0b01ef 100644
--- a/nova/tests/api/openstack/test_api.py
+++ b/nova/tests/api/openstack/test_api.py
@@ -20,6 +20,7 @@ import json
import webob.exc
import webob.dec
+from lxml import etree
from webob import Request
from nova import test
@@ -52,6 +53,30 @@ class APITest(test.TestCase):
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 400)
+ def test_vendor_content_type_json(self):
+ ctype = 'application/vnd.openstack.compute+json'
+
+ req = webob.Request.blank('/')
+ req.headers['Accept'] = ctype
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 200)
+ self.assertEqual(res.content_type, ctype)
+
+ body = json.loads(res.body)
+
+ def test_vendor_content_type_xml(self):
+ ctype = 'application/vnd.openstack.compute+xml'
+
+ req = webob.Request.blank('/')
+ req.headers['Accept'] = ctype
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 200)
+ self.assertEqual(res.content_type, ctype)
+
+ body = etree.XML(res.body)
+
def test_exceptions_are_converted_to_faults(self):
@webob.dec.wsgify
diff --git a/nova/tests/api/openstack/test_common.py b/nova/tests/api/openstack/test_common.py
index 867e9d446..1628ad1c8 100644
--- a/nova/tests/api/openstack/test_common.py
+++ b/nova/tests/api/openstack/test_common.py
@@ -243,21 +243,41 @@ class MiscFunctionsTest(test.TestCase):
common.remove_version_from_href,
fixture)
- def test_get_id_from_href(self):
+ def test_get_id_from_href_with_int_url(self):
fixture = 'http://www.testsite.com/dir/45'
actual = common.get_id_from_href(fixture)
- expected = 45
+ expected = '45'
self.assertEqual(actual, expected)
- def test_get_id_from_href_bad_request(self):
- fixture = 'http://45'
- self.assertRaises(ValueError,
- common.get_id_from_href,
- fixture)
+ def test_get_id_from_href_with_int(self):
+ fixture = '45'
+ actual = common.get_id_from_href(fixture)
+ expected = '45'
+ self.assertEqual(actual, expected)
- def test_get_id_from_href_int(self):
- fixture = 1
- self.assertEqual(fixture, common.get_id_from_href(fixture))
+ def test_get_id_from_href_with_int_url_query(self):
+ fixture = 'http://www.testsite.com/dir/45?asdf=jkl'
+ actual = common.get_id_from_href(fixture)
+ expected = '45'
+ self.assertEqual(actual, expected)
+
+ def test_get_id_from_href_with_uuid_url(self):
+ fixture = 'http://www.testsite.com/dir/abc123'
+ actual = common.get_id_from_href(fixture)
+ expected = "abc123"
+ self.assertEqual(actual, expected)
+
+ def test_get_id_from_href_with_uuid_url_query(self):
+ fixture = 'http://www.testsite.com/dir/abc123?asdf=jkl'
+ actual = common.get_id_from_href(fixture)
+ expected = "abc123"
+ self.assertEqual(actual, expected)
+
+ def test_get_id_from_href_with_uuid(self):
+ fixture = 'abc123'
+ actual = common.get_id_from_href(fixture)
+ expected = 'abc123'
+ self.assertEqual(actual, expected)
def test_get_version_from_href(self):
fixture = 'http://www.testsite.com/v1.1/images'
diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py
index 31443242b..44f4eb055 100644
--- a/nova/tests/api/openstack/test_extensions.py
+++ b/nova/tests/api/openstack/test_extensions.py
@@ -87,6 +87,7 @@ class ExtensionControllerTest(test.TestCase):
self.ext_list = [
"Createserverext",
"FlavorExtraSpecs",
+ "FlavorExtraData",
"Floating_ips",
"Fox In Socks",
"Hosts",
diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py
index a3c5bd107..348042bfe 100644
--- a/nova/tests/api/openstack/test_flavors.py
+++ b/nova/tests/api/openstack/test_flavors.py
@@ -112,12 +112,20 @@ class FlavorsTest(test.TestCase):
"name": "flavor 1",
"ram": "256",
"disk": "10",
+ "rxtx_cap": "",
+ "rxtx_quota": "",
+ "swap": "",
+ "vcpus": "",
},
{
"id": "2",
"name": "flavor 2",
"ram": "256",
"disk": "10",
+ "rxtx_cap": "",
+ "rxtx_quota": "",
+ "swap": "",
+ "vcpus": "",
},
]
self.assertEqual(flavors, expected)
@@ -132,6 +140,10 @@ class FlavorsTest(test.TestCase):
"name": "flavor 12",
"ram": "256",
"disk": "10",
+ "rxtx_cap": "",
+ "rxtx_quota": "",
+ "swap": "",
+ "vcpus": "",
}
self.assertEqual(flavor, expected)
@@ -154,6 +166,10 @@ class FlavorsTest(test.TestCase):
"name": "flavor 12",
"ram": "256",
"disk": "10",
+ "rxtx_cap": "",
+ "rxtx_quota": "",
+ "swap": "",
+ "vcpus": "",
"links": [
{
"rel": "self",
@@ -221,6 +237,10 @@ class FlavorsTest(test.TestCase):
"name": "flavor 1",
"ram": "256",
"disk": "10",
+ "rxtx_cap": "",
+ "rxtx_quota": "",
+ "swap": "",
+ "vcpus": "",
"links": [
{
"rel": "self",
@@ -237,6 +257,10 @@ class FlavorsTest(test.TestCase):
"name": "flavor 2",
"ram": "256",
"disk": "10",
+ "rxtx_cap": "",
+ "rxtx_quota": "",
+ "swap": "",
+ "vcpus": "",
"links": [
{
"rel": "self",
@@ -276,6 +300,10 @@ class FlavorsXMLSerializationTest(test.TestCase):
"name": "asdf",
"ram": "256",
"disk": "10",
+ "rxtx_cap": "",
+ "rxtx_quota": "",
+ "swap": "",
+ "vcpus": "",
"links": [
{
"rel": "self",
@@ -303,6 +331,10 @@ class FlavorsXMLSerializationTest(test.TestCase):
"name": "asdf",
"ram": "256",
"disk": "10",
+ "rxtx_cap": "",
+ "rxtx_quota": "",
+ "swap": "",
+ "vcpus": "",
"links": [
{
"rel": "self",
@@ -340,6 +372,10 @@ class FlavorsXMLSerializationTest(test.TestCase):
"name": "asdf",
"ram": 256,
"disk": 10,
+ "rxtx_cap": "",
+ "rxtx_quota": "",
+ "swap": "",
+ "vcpus": "",
"links": [
{
"rel": "self",
@@ -378,6 +414,10 @@ class FlavorsXMLSerializationTest(test.TestCase):
"name": "flavor 23",
"ram": "512",
"disk": "20",
+ "rxtx_cap": "",
+ "rxtx_quota": "",
+ "swap": "",
+ "vcpus": "",
"links": [
{
"rel": "self",
@@ -393,6 +433,10 @@ class FlavorsXMLSerializationTest(test.TestCase):
"name": "flavor 13",
"ram": "256",
"disk": "10",
+ "rxtx_cap": "",
+ "rxtx_quota": "",
+ "swap": "",
+ "vcpus": "",
"links": [
{
"rel": "self",
@@ -435,6 +479,10 @@ class FlavorsXMLSerializationTest(test.TestCase):
"name": "flavor 23",
"ram": "512",
"disk": "20",
+ "rxtx_cap": "",
+ "rxtx_quota": "",
+ "swap": "",
+ "vcpus": "",
"links": [
{
"rel": "self",
@@ -450,6 +498,10 @@ class FlavorsXMLSerializationTest(test.TestCase):
"name": "flavor 13",
"ram": "256",
"disk": "10",
+ "rxtx_cap": "",
+ "rxtx_quota": "",
+ "swap": "",
+ "vcpus": "",
"links": [
{
"rel": "self",
diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py
index 2aee1bc14..e5fd4764a 100644
--- a/nova/tests/api/openstack/test_images.py
+++ b/nova/tests/api/openstack/test_images.py
@@ -77,14 +77,14 @@ class ImagesTest(test.TestCase):
response_dict = json.loads(response.body)
response_list = response_dict["images"]
- expected = [{'id': '123', 'name': 'public image'},
- {'id': '124', 'name': 'queued snapshot'},
- {'id': '125', 'name': 'saving snapshot'},
- {'id': '126', 'name': 'active snapshot'},
- {'id': '127', 'name': 'killed snapshot'},
- {'id': '128', 'name': 'deleted snapshot'},
- {'id': '129', 'name': 'pending_delete snapshot'},
- {'id': '130', 'name': None}]
+ expected = [{'id': 123, 'name': 'public image'},
+ {'id': 124, 'name': 'queued snapshot'},
+ {'id': 125, 'name': 'saving snapshot'},
+ {'id': 126, 'name': 'active snapshot'},
+ {'id': 127, 'name': 'killed snapshot'},
+ {'id': 128, 'name': 'deleted snapshot'},
+ {'id': 129, 'name': 'pending_delete snapshot'},
+ {'id': 130, 'name': None}]
self.assertDictListMatch(response_list, expected)
@@ -99,7 +99,7 @@ class ImagesTest(test.TestCase):
expected_image = {
"image": {
- "id": "123",
+ "id": 123,
"name": "public image",
"updated": NOW_API_FORMAT,
"created": NOW_API_FORMAT,
@@ -131,7 +131,7 @@ class ImagesTest(test.TestCase):
"status": "SAVING",
"progress": 0,
'server': {
- 'id': 42,
+ 'id': '42',
"links": [{
"rel": "self",
"href": server_href,
@@ -224,12 +224,10 @@ class ImagesTest(test.TestCase):
expected = minidom.parseString("""
<itemNotFound code="404"
- xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
- <message>
- Image not found.
- </message>
+ xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+ <message>Image not found.</message>
</itemNotFound>
- """.replace(" ", ""))
+ """.replace(" ", "").replace("\n", ""))
actual = minidom.parseString(response.body.replace(" ", ""))
@@ -261,12 +259,10 @@ class ImagesTest(test.TestCase):
# because the element hasn't changed definition
expected = minidom.parseString("""
<itemNotFound code="404"
- xmlns="http://docs.openstack.org/compute/api/v1.1">
- <message>
- Image not found.
- </message>
+ xmlns="http://docs.openstack.org/compute/api/v1.1">
+ <message>Image not found.</message>
</itemNotFound>
- """.replace(" ", ""))
+ """.replace(" ", "").replace("\n", ""))
actual = minidom.parseString(response.body.replace(" ", ""))
@@ -406,7 +402,7 @@ class ImagesTest(test.TestCase):
response_list = response_dict["images"]
expected = [{
- 'id': '123',
+ 'id': 123,
'name': 'public image',
'updated': NOW_API_FORMAT,
'created': NOW_API_FORMAT,
@@ -414,7 +410,7 @@ class ImagesTest(test.TestCase):
'progress': 100,
},
{
- 'id': '124',
+ 'id': 124,
'name': 'queued snapshot',
'updated': NOW_API_FORMAT,
'created': NOW_API_FORMAT,
@@ -422,7 +418,7 @@ class ImagesTest(test.TestCase):
'progress': 0,
},
{
- 'id': '125',
+ 'id': 125,
'name': 'saving snapshot',
'updated': NOW_API_FORMAT,
'created': NOW_API_FORMAT,
@@ -430,7 +426,7 @@ class ImagesTest(test.TestCase):
'progress': 0,
},
{
- 'id': '126',
+ 'id': 126,
'name': 'active snapshot',
'updated': NOW_API_FORMAT,
'created': NOW_API_FORMAT,
@@ -438,7 +434,7 @@ class ImagesTest(test.TestCase):
'progress': 100,
},
{
- 'id': '127',
+ 'id': 127,
'name': 'killed snapshot',
'updated': NOW_API_FORMAT,
'created': NOW_API_FORMAT,
@@ -446,7 +442,7 @@ class ImagesTest(test.TestCase):
'progress': 0,
},
{
- 'id': '128',
+ 'id': 128,
'name': 'deleted snapshot',
'updated': NOW_API_FORMAT,
'created': NOW_API_FORMAT,
@@ -454,7 +450,7 @@ class ImagesTest(test.TestCase):
'progress': 0,
},
{
- 'id': '129',
+ 'id': 129,
'name': 'pending_delete snapshot',
'updated': NOW_API_FORMAT,
'created': NOW_API_FORMAT,
@@ -462,7 +458,7 @@ class ImagesTest(test.TestCase):
'progress': 0,
},
{
- 'id': '130',
+ 'id': 130,
'name': None,
'updated': NOW_API_FORMAT,
'created': NOW_API_FORMAT,
@@ -511,7 +507,7 @@ class ImagesTest(test.TestCase):
'status': 'SAVING',
'progress': 0,
'server': {
- 'id': 42,
+ 'id': '42',
"links": [{
"rel": "self",
"href": server_href,
@@ -542,7 +538,7 @@ class ImagesTest(test.TestCase):
'status': 'SAVING',
'progress': 0,
'server': {
- 'id': 42,
+ 'id': '42',
"links": [{
"rel": "self",
"href": server_href,
@@ -573,7 +569,7 @@ class ImagesTest(test.TestCase):
'status': 'ACTIVE',
'progress': 100,
'server': {
- 'id': 42,
+ 'id': '42',
"links": [{
"rel": "self",
"href": server_href,
@@ -604,7 +600,7 @@ class ImagesTest(test.TestCase):
'status': 'ERROR',
'progress': 0,
'server': {
- 'id': 42,
+ 'id': '42',
"links": [{
"rel": "self",
"href": server_href,
@@ -635,7 +631,7 @@ class ImagesTest(test.TestCase):
'status': 'DELETED',
'progress': 0,
'server': {
- 'id': 42,
+ 'id': '42',
"links": [{
"rel": "self",
"href": server_href,
@@ -666,7 +662,7 @@ class ImagesTest(test.TestCase):
'status': 'DELETED',
'progress': 0,
'server': {
- 'id': 42,
+ 'id': '42',
"links": [{
"rel": "self",
"href": server_href,
@@ -914,7 +910,7 @@ class ImagesTest(test.TestCase):
app = fakes.wsgi_app(fake_auth_context=self._get_fake_context())
res = req.get_response(app)
image_meta = json.loads(res.body)['image']
- expected = {'id': '123', 'name': 'public image',
+ expected = {'id': 123, 'name': 'public image',
'updated': NOW_API_FORMAT,
'created': NOW_API_FORMAT, 'status': 'ACTIVE',
'progress': 100}
diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py
index 3db57ee86..6f0210c27 100644
--- a/nova/tests/api/openstack/test_limits.py
+++ b/nova/tests/api/openstack/test_limits.py
@@ -174,12 +174,11 @@ class LimitsControllerV10Test(BaseLimitTestSuite):
response = request.get_response(self.controller)
expected = minidom.parseString("""
- <limits
- xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+ <limits xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
<rate/>
<absolute/>
</limits>
- """.replace(" ", ""))
+ """.replace(" ", "").replace("\n", ""))
body = minidom.parseString(response.body.replace(" ", ""))
@@ -192,17 +191,16 @@ class LimitsControllerV10Test(BaseLimitTestSuite):
response = request.get_response(self.controller)
expected = minidom.parseString("""
- <limits
- xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
+ <limits xmlns="http://docs.rackspacecloud.com/servers/api/v1.0">
<rate>
<limit URI="*" regex=".*" remaining="10" resetTime="0"
- unit="MINUTE" value="10" verb="GET"/>
+ unit="MINUTE" value="10" verb="GET"/>
<limit URI="*" regex=".*" remaining="5" resetTime="0"
- unit="HOUR" value="5" verb="POST"/>
+ unit="HOUR" value="5" verb="POST"/>
</rate>
<absolute/>
</limits>
- """.replace(" ", ""))
+ """.replace(" ", "").replace("\n", ""))
body = minidom.parseString(response.body.replace(" ", ""))
self.assertEqual(expected.toxml(), body.toxml())
diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py
index 6dea78d17..74b9ce853 100644
--- a/nova/tests/api/openstack/test_wsgi.py
+++ b/nova/tests/api/openstack/test_wsgi.py
@@ -27,17 +27,17 @@ class RequestTest(test.TestCase):
result = request.get_content_type()
self.assertEqual(result, "application/json")
- def test_content_type_from_accept_xml(self):
- request = wsgi.Request.blank('/tests/123')
- request.headers["Accept"] = "application/xml"
- result = request.best_match_content_type()
- self.assertEqual(result, "application/xml")
-
- request = wsgi.Request.blank('/tests/123')
- request.headers["Accept"] = "application/json"
- result = request.best_match_content_type()
- self.assertEqual(result, "application/json")
-
+ def test_content_type_from_accept(self):
+ for content_type in ('application/xml',
+ 'application/vnd.openstack.compute+xml',
+ 'application/json',
+ 'application/vnd.openstack.compute+json'):
+ request = wsgi.Request.blank('/tests/123')
+ request.headers["Accept"] = content_type
+ result = request.best_match_content_type()
+ self.assertEqual(result, content_type)
+
+ def test_content_type_from_accept_best(self):
request = wsgi.Request.blank('/tests/123')
request.headers["Accept"] = "application/xml, application/json"
result = request.best_match_content_type()
@@ -231,7 +231,7 @@ class ResponseSerializerTest(test.TestCase):
self.body_serializers = {
'application/json': JSONSerializer(),
- 'application/XML': XMLSerializer(),
+ 'application/xml': XMLSerializer(),
}
self.serializer = wsgi.ResponseSerializer(self.body_serializers,
@@ -250,15 +250,24 @@ class ResponseSerializerTest(test.TestCase):
self.serializer.get_body_serializer,
'application/unknown')
- def test_serialize_response(self):
- response = self.serializer.serialize({}, 'application/json')
- self.assertEqual(response.headers['Content-Type'], 'application/json')
- self.assertEqual(response.body, 'pew_json')
- self.assertEqual(response.status_int, 404)
+ def test_serialize_response_json(self):
+ for content_type in ('application/json',
+ 'application/vnd.openstack.compute+json'):
+ response = self.serializer.serialize({}, content_type)
+ self.assertEqual(response.headers['Content-Type'], content_type)
+ self.assertEqual(response.body, 'pew_json')
+ self.assertEqual(response.status_int, 404)
+
+ def test_serialize_response_xml(self):
+ for content_type in ('application/xml',
+ 'application/vnd.openstack.compute+xml'):
+ response = self.serializer.serialize({}, content_type)
+ self.assertEqual(response.headers['Content-Type'], content_type)
+ self.assertEqual(response.body, 'pew_xml')
+ self.assertEqual(response.status_int, 404)
def test_serialize_response_None(self):
response = self.serializer.serialize(None, 'application/json')
- print response
self.assertEqual(response.headers['Content-Type'], 'application/json')
self.assertEqual(response.body, '')
self.assertEqual(response.status_int, 404)
@@ -281,7 +290,7 @@ class RequestDeserializerTest(test.TestCase):
self.body_deserializers = {
'application/json': JSONDeserializer(),
- 'application/XML': XMLDeserializer(),
+ 'application/xml': XMLDeserializer(),
}
self.deserializer = wsgi.RequestDeserializer(self.body_deserializers)
@@ -290,8 +299,9 @@ class RequestDeserializerTest(test.TestCase):
pass
def test_get_deserializer(self):
- expected = self.deserializer.get_body_deserializer('application/json')
- self.assertEqual(expected, self.body_deserializers['application/json'])
+ ctype = 'application/json'
+ expected = self.deserializer.get_body_deserializer(ctype)
+ self.assertEqual(expected, self.body_deserializers[ctype])
def test_get_deserializer_unknown_content_type(self):
self.assertRaises(exception.InvalidContentType,
@@ -299,10 +309,11 @@ class RequestDeserializerTest(test.TestCase):
'application/unknown')
def test_get_expected_content_type(self):
+ ctype = 'application/json'
request = wsgi.Request.blank('/')
- request.headers['Accept'] = 'application/json'
+ request.headers['Accept'] = ctype
self.assertEqual(self.deserializer.get_expected_content_type(request),
- 'application/json')
+ ctype)
def test_get_action_args(self):
env = {
diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py
index 19028a451..cdbfba63a 100644
--- a/nova/tests/db/fakes.py
+++ b/nova/tests/db/fakes.py
@@ -125,10 +125,11 @@ def stub_out_db_network_api(stubs):
if ips[0]['fixed_ip']:
fixed_ip_address = ips[0]['fixed_ip']['address']
ips[0]['fixed_ip'] = None
+ ips[0]['host'] = None
return fixed_ip_address
def fake_floating_ip_fixed_ip_associate(context, floating_address,
- fixed_address):
+ fixed_address, host):
float = filter(lambda i: i['address'] == floating_address,
floating_ips)
fixed = filter(lambda i: i['address'] == fixed_address,
@@ -136,6 +137,7 @@ def stub_out_db_network_api(stubs):
if float and fixed:
float[0]['fixed_ip'] = fixed[0]
float[0]['fixed_ip_id'] = fixed[0]['id']
+ float[0]['host'] = host
def fake_floating_ip_get_all_by_host(context, host):
# TODO(jkoelker): Once we get the patches that remove host from
diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py
index 1ecb99b31..142206755 100644
--- a/nova/tests/fake_network.py
+++ b/nova/tests/fake_network.py
@@ -25,6 +25,36 @@ HOST = "testhost"
FLAGS = flags.FLAGS
+class FakeIptablesFirewallDriver(object):
+ def __init__(self, **kwargs):
+ pass
+
+ def setattr(self, key, val):
+ self.__setattr__(key, val)
+
+ def apply_instance_filter(self, instance, network_info):
+ pass
+
+
+class FakeVIFDriver(object):
+
+ def __init__(self, **kwargs):
+ pass
+
+ def setattr(self, key, val):
+ self.__setattr__(key, val)
+
+ def plug(self, instance, network, mapping):
+ return {
+ 'id': 'fake',
+ 'bridge_name': 'fake',
+ 'mac_address': 'fake',
+ 'ip_address': 'fake',
+ 'dhcp_server': 'fake',
+ 'extra_params': 'fake',
+ }
+
+
class FakeModel(dict):
"""Represent a model from the db"""
def __init__(self, *args, **kwargs):
diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py
index d4e7f6b6b..b7c1ef1ab 100644
--- a/nova/tests/test_libvirt.py
+++ b/nova/tests/test_libvirt.py
@@ -52,6 +52,32 @@ def _concurrency(wait, done, target):
done.send()
+class FakeVirtDomain(object):
+
+ def __init__(self, fake_xml=None):
+ if fake_xml:
+ self._fake_dom_xml = fake_xml
+ else:
+ self._fake_dom_xml = """
+ <domain type='kvm'>
+ <devices>
+ <disk type='file'>
+ <source file='filename'/>
+ </disk>
+ </devices>
+ </domain>
+ """
+
+ def snapshotCreateXML(self, *args):
+ return None
+
+ def createWithFlags(self, launch_flags):
+ pass
+
+ def XMLDesc(self, *args):
+ return self._fake_dom_xml
+
+
class CacheConcurrencyTestCase(test.TestCase):
def setUp(self):
super(CacheConcurrencyTestCase, self).setUp()
@@ -153,70 +179,24 @@ class LibvirtConnTestCase(test.TestCase):
# A fake libvirt.virConnect
class FakeLibvirtConnection(object):
- pass
-
- # A fake connection.IptablesFirewallDriver
- class FakeIptablesFirewallDriver(object):
-
- def __init__(self, **kwargs):
- pass
-
- def setattr(self, key, val):
- self.__setattr__(key, val)
-
- # A fake VIF driver
- class FakeVIFDriver(object):
-
- def __init__(self, **kwargs):
- pass
-
- def setattr(self, key, val):
- self.__setattr__(key, val)
-
- def plug(self, instance, network, mapping):
- return {
- 'id': 'fake',
- 'bridge_name': 'fake',
- 'mac_address': 'fake',
- 'ip_address': 'fake',
- 'dhcp_server': 'fake',
- 'extra_params': 'fake',
- }
+ def defineXML(self, xml):
+ return FakeVirtDomain()
# Creating mocks
fake = FakeLibvirtConnection()
- fakeip = FakeIptablesFirewallDriver
- fakevif = FakeVIFDriver()
# Customizing above fake if necessary
for key, val in kwargs.items():
fake.__setattr__(key, val)
- # Inevitable mocks for connection.LibvirtConnection
- self.mox.StubOutWithMock(connection.utils, 'import_class')
- connection.utils.import_class(mox.IgnoreArg()).AndReturn(fakeip)
- self.mox.StubOutWithMock(connection.utils, 'import_object')
- connection.utils.import_object(mox.IgnoreArg()).AndReturn(fakevif)
+ self.flags(image_service='nova.image.fake.FakeImageService')
+ fw_driver = "nova.tests.fake_network.FakeIptablesFirewallDriver"
+ self.flags(firewall_driver=fw_driver)
+ self.flags(libvirt_vif_driver="nova.tests.fake_network.FakeVIFDriver")
+
self.mox.StubOutWithMock(connection.LibvirtConnection, '_conn')
connection.LibvirtConnection._conn = fake
def fake_lookup(self, instance_name):
-
- class FakeVirtDomain(object):
-
- def snapshotCreateXML(self, *args):
- return None
-
- def XMLDesc(self, *args):
- return """
- <domain type='kvm'>
- <devices>
- <disk type='file'>
- <source file='filename'/>
- </disk>
- </devices>
- </domain>
- """
-
return FakeVirtDomain()
def fake_execute(self, *args):
@@ -798,8 +778,6 @@ class LibvirtConnTestCase(test.TestCase):
shutil.rmtree(os.path.join(FLAGS.instances_path, instance.name))
shutil.rmtree(os.path.join(FLAGS.instances_path, '_base'))
- self.assertTrue(count)
-
def test_get_host_ip_addr(self):
conn = connection.LibvirtConnection(False)
ip = conn.get_host_ip_addr()
diff --git a/nova/tests/vmwareapi/stubs.py b/nova/tests/vmwareapi/stubs.py
index 0ed5e9b68..7de10e612 100644
--- a/nova/tests/vmwareapi/stubs.py
+++ b/nova/tests/vmwareapi/stubs.py
@@ -47,7 +47,5 @@ def set_stubs(stubs):
stubs.Set(vmware_images, 'upload_image', fake.fake_upload_image)
stubs.Set(vmwareapi_conn.VMWareAPISession, "_get_vim_object",
fake_get_vim_object)
- stubs.Set(vmwareapi_conn.VMWareAPISession, "_get_vim_object",
- fake_get_vim_object)
stubs.Set(vmwareapi_conn.VMWareAPISession, "_is_vim_object",
fake_is_vim_object)
diff --git a/nova/virt/vmwareapi/fake.py b/nova/virt/vmwareapi/fake.py
index 0dea13aba..ae00bca0f 100644
--- a/nova/virt/vmwareapi/fake.py
+++ b/nova/virt/vmwareapi/fake.py
@@ -409,7 +409,7 @@ def fake_plug_vifs(*args, **kwargs):
def fake_get_network(*args, **kwargs):
"""Fake get network."""
- return [{'type': 'fake'}]
+ return {'type': 'fake'}
def fake_fetch_image(context, image, instance, **kwargs):
diff --git a/nova/virt/vmwareapi/vif.py b/nova/virt/vmwareapi/vif.py
index fb6548b34..9906b89e1 100644
--- a/nova/virt/vmwareapi/vif.py
+++ b/nova/virt/vmwareapi/vif.py
@@ -17,42 +17,35 @@
"""VIF drivers for VMWare."""
-from nova import db
from nova import exception
from nova import flags
from nova import log as logging
-from nova import utils
from nova.virt.vif import VIFDriver
-from nova.virt.vmwareapi_conn import VMWareAPISession
from nova.virt.vmwareapi import network_utils
LOG = logging.getLogger("nova.virt.vmwareapi.vif")
FLAGS = flags.FLAGS
+FLAGS['vmwareapi_vlan_interface'].SetDefault('vmnic0')
class VMWareVlanBridgeDriver(VIFDriver):
"""VIF Driver to setup bridge/VLAN networking using VMWare API."""
def plug(self, instance, network, mapping):
+ """Plug the VIF to specified instance using information passed.
+ Currently we are plugging the VIF(s) during instance creation itself.
+ We can use this method when we add support to add additional NIC to
+ an existing instance."""
+ pass
+
+ def ensure_vlan_bridge(self, session, network):
"""Create a vlan and bridge unless they already exist."""
vlan_num = network['vlan']
bridge = network['bridge']
- bridge_interface = network['bridge_interface']
+ vlan_interface = FLAGS.vmwareapi_vlan_interface
- # Open vmwareapi session
- host_ip = FLAGS.vmwareapi_host_ip
- host_username = FLAGS.vmwareapi_host_username
- host_password = FLAGS.vmwareapi_host_password
- if not host_ip or host_username is None or host_password is None:
- raise Exception(_('Must specify vmwareapi_host_ip, '
- 'vmwareapi_host_username '
- 'and vmwareapi_host_password to use '
- 'connection_type=vmwareapi'))
- session = VMWareAPISession(host_ip, host_username, host_password,
- FLAGS.vmwareapi_api_retry_count)
- vlan_interface = bridge_interface
# Check if the vlan_interface physical network adapter exists on the
# host.
if not network_utils.check_if_vlan_interface_exists(session,
@@ -92,4 +85,6 @@ class VMWareVlanBridgeDriver(VIFDriver):
pgroup=pg_vlanid)
def unplug(self, instance, network, mapping):
+ """Cleanup operations like deleting port group if no instance
+ is associated with it."""
pass
diff --git a/nova/virt/vmwareapi/vm_util.py b/nova/virt/vmwareapi/vm_util.py
index 82b5f7214..dd1c81196 100644
--- a/nova/virt/vmwareapi/vm_util.py
+++ b/nova/virt/vmwareapi/vm_util.py
@@ -39,8 +39,7 @@ def split_datastore_path(datastore_path):
def get_vm_create_spec(client_factory, instance, data_store_name,
- network_name="vmnet0",
- os_type="otherGuest", network_ref=None):
+ vif_infos, os_type="otherGuest"):
"""Builds the VM Create spec."""
config_spec = client_factory.create('ns0:VirtualMachineConfigSpec')
config_spec.name = instance.name
@@ -61,14 +60,12 @@ def get_vm_create_spec(client_factory, instance, data_store_name,
config_spec.numCPUs = int(instance.vcpus)
config_spec.memoryMB = int(instance.memory_mb)
- mac_address = None
- if instance['mac_addresses']:
- mac_address = instance['mac_addresses'][0]['address']
+ vif_spec_list = []
+ for vif_info in vif_infos:
+ vif_spec = create_network_spec(client_factory, vif_info)
+ vif_spec_list.append(vif_spec)
- nic_spec = create_network_spec(client_factory,
- network_name, mac_address)
-
- device_config_spec = [nic_spec]
+ device_config_spec = vif_spec_list
config_spec.deviceChange = device_config_spec
return config_spec
@@ -93,8 +90,7 @@ def create_controller_spec(client_factory, key):
return virtual_device_config
-def create_network_spec(client_factory, network_name, mac_address,
- network_ref=None):
+def create_network_spec(client_factory, vif_info):
"""
Builds a config spec for the addition of a new network
adapter to the VM.
@@ -109,6 +105,9 @@ def create_network_spec(client_factory, network_name, mac_address,
# NOTE(asomya): Only works on ESXi if the portgroup binding is set to
# ephemeral. Invalid configuration if set to static and the NIC does
# not come up on boot if set to dynamic.
+ network_ref = vif_info['network_ref']
+ network_name = vif_info['network_name']
+ mac_address = vif_info['mac_address']
backing = None
if (network_ref and
network_ref['type'] == "DistributedVirtualPortgroup"):
@@ -295,11 +294,8 @@ def get_dummy_vm_create_spec(client_factory, name, data_store_name):
return config_spec
-def get_machine_id_change_spec(client_factory, mac, ip_addr, netmask,
- gateway, broadcast, dns):
+def get_machine_id_change_spec(client_factory, machine_id_str):
"""Builds the machine id change config spec."""
- machine_id_str = "%s;%s;%s;%s;%s;%s" % (mac, ip_addr, netmask,
- gateway, broadcast, dns)
virtual_machine_config_spec = \
client_factory.create('ns0:VirtualMachineConfigSpec')
diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py
index 6bdc2f23a..063b84a62 100644
--- a/nova/virt/vmwareapi/vmops.py
+++ b/nova/virt/vmwareapi/vmops.py
@@ -27,7 +27,6 @@ import urllib2
import uuid
from nova import context as nova_context
-from nova import db
from nova import exception
from nova import flags
from nova import log as logging
@@ -111,22 +110,6 @@ class VMWareVMOps(object):
client_factory = self._session._get_vim().client.factory
service_content = self._session._get_vim().get_service_content()
- network = db.network_get_by_instance(nova_context.get_admin_context(),
- instance['id'])
-
- net_name = network['bridge']
-
- def _check_if_network_bridge_exists():
- network_ref = \
- network_utils.get_network_with_the_name(self._session,
- net_name)
- if network_ref is None:
- raise exception.NetworkNotFoundForBridge(bridge=net_name)
- return network_ref
-
- self.plug_vifs(instance, network_info)
- network_obj = _check_if_network_bridge_exists()
-
def _get_datastore_ref():
"""Get the datastore list and choose the first local storage."""
data_stores = self._session._call_method(vim_util, "get_objects",
@@ -182,11 +165,36 @@ class VMWareVMOps(object):
vm_folder_mor, res_pool_mor = _get_vmfolder_and_res_pool_mors()
+ def _check_if_network_bridge_exists(network_name):
+ network_ref = \
+ network_utils.get_network_with_the_name(self._session,
+ network_name)
+ if network_ref is None:
+ raise exception.NetworkNotFoundForBridge(bridge=network_name)
+ return network_ref
+
+ def _get_vif_infos():
+ vif_infos = []
+ for (network, mapping) in network_info:
+ mac_address = mapping['mac']
+ network_name = network['bridge']
+ if mapping.get('should_create_vlan'):
+ network_ref = self._vif_driver.ensure_vlan_bridge(
+ self._session, network)
+ else:
+ network_ref = _check_if_network_bridge_exists(network_name)
+ vif_infos.append({'network_name': network_name,
+ 'mac_address': mac_address,
+ 'network_ref': network_ref,
+ })
+ return vif_infos
+
+ vif_infos = _get_vif_infos()
+
# Get the create vm config spec
config_spec = vm_util.get_vm_create_spec(
client_factory, instance,
- data_store_name, net_name, os_type,
- network_obj)
+ data_store_name, vif_infos, os_type)
def _execute_create_vm():
"""Create VM on ESX host."""
@@ -204,8 +212,10 @@ class VMWareVMOps(object):
_execute_create_vm()
- # Set the machine id for the VM for setting the IP
- self._set_machine_id(client_factory, instance)
+ # Set the machine.id parameter of the instance to inject
+ # the NIC configuration inside the VM
+ if FLAGS.flat_injected:
+ self._set_machine_id(client_factory, instance, network_info)
# Naming the VM files in correspondence with the VM instance name
# The flat vmdk file name
@@ -718,39 +728,45 @@ class VMWareVMOps(object):
"""Return link to instance's ajax console."""
return 'http://fakeajaxconsole/fake_url'
- def _set_machine_id(self, client_factory, instance):
+ def _set_machine_id(self, client_factory, instance, network_info):
"""
- Set the machine id of the VM for guest tools to pick up and change
- the IP.
+ Set the machine id of the VM for guest tools to pick up and reconfigure
+ the network interfaces.
"""
- admin_context = nova_context.get_admin_context()
vm_ref = self._get_vm_ref_from_the_name(instance.name)
if vm_ref is None:
raise exception.InstanceNotFound(instance_id=instance.id)
- network = db.network_get_by_instance(nova_context.get_admin_context(),
- instance['id'])
- mac_address = None
- if instance['mac_addresses']:
- mac_address = instance['mac_addresses'][0]['address']
-
- net_mask = network["netmask"]
- gateway = network["gateway"]
- broadcast = network["broadcast"]
- # TODO(vish): add support for dns2
- dns = network["dns1"]
-
- addresses = db.instance_get_fixed_addresses(admin_context,
- instance['id'])
- ip_addr = addresses[0] if addresses else None
+
+ machine_id_str = ''
+ for (network, info) in network_info:
+ # TODO(vish): add support for dns2
+ # TODO(sateesh): add support for injection of ipv6 configuration
+ ip_v4 = ip_v6 = None
+ if 'ips' in info and len(info['ips']) > 0:
+ ip_v4 = info['ips'][0]
+ if 'ip6s' in info and len(info['ip6s']) > 0:
+ ip_v6 = info['ip6s'][0]
+ if len(info['dns']) > 0:
+ dns = info['dns'][0]
+ else:
+ dns = ''
+
+ interface_str = "%s;%s;%s;%s;%s;%s" % \
+ (info['mac'],
+ ip_v4 and ip_v4['ip'] or '',
+ ip_v4 and ip_v4['netmask'] or '',
+ info['gateway'],
+ info['broadcast'],
+ dns)
+ machine_id_str = machine_id_str + interface_str + '#'
machine_id_change_spec = \
- vm_util.get_machine_id_change_spec(client_factory, mac_address,
- ip_addr, net_mask, gateway,
- broadcast, dns)
+ vm_util.get_machine_id_change_spec(client_factory, machine_id_str)
+
LOG.debug(_("Reconfiguring VM instance %(name)s to set the machine id "
"with ip - %(ip_addr)s") %
({'name': instance.name,
- 'ip_addr': ip_addr}))
+ 'ip_addr': ip_v4['ip']}))
reconfig_task = self._session._call_method(self._session._get_vim(),
"ReconfigVM_Task", vm_ref,
spec=machine_id_change_spec)
@@ -758,7 +774,7 @@ class VMWareVMOps(object):
LOG.debug(_("Reconfigured VM instance %(name)s to set the machine id "
"with ip - %(ip_addr)s") %
({'name': instance.name,
- 'ip_addr': ip_addr}))
+ 'ip_addr': ip_v4['ip']}))
def _get_datacenter_name_and_ref(self):
"""Get the datacenter name and the reference."""
diff --git a/tools/esx/guest_tool.py b/tools/esx/guest_tool.py
index 97b5302ba..5158d883a 100644
--- a/tools/esx/guest_tool.py
+++ b/tools/esx/guest_tool.py
@@ -81,28 +81,34 @@ def _bytes2int(bytes):
def _parse_network_details(machine_id):
"""
- Parse the machine.id field to get MAC, IP, Netmask and Gateway fields
- machine.id is of the form MAC;IP;Netmask;Gateway;Broadcast;DNS1,DNS2
- where ';' is the separator.
+ Parse the machine_id to get MAC, IP, Netmask and Gateway fields per NIC.
+ machine_id is of the form ('NIC_record#NIC_record#', '')
+ Each of the NIC will have record NIC_record in the form
+ 'MAC;IP;Netmask;Gateway;Broadcast;DNS' where ';' is field separator.
+ Each record is separated by '#' from next record.
"""
+ logging.debug(_("Received machine_id from vmtools : %s") % machine_id[0])
network_details = []
if machine_id[1].strip() == "1":
pass
else:
- network_info_list = machine_id[0].split(';')
- assert len(network_info_list) % 6 == 0
- no_grps = len(network_info_list) / 6
- i = 0
- while i < no_grps:
- k = i * 6
- network_details.append((
- network_info_list[k].strip().lower(),
- network_info_list[k + 1].strip(),
- network_info_list[k + 2].strip(),
- network_info_list[k + 3].strip(),
- network_info_list[k + 4].strip(),
- network_info_list[k + 5].strip().split(',')))
- i += 1
+ for machine_id_str in machine_id[0].split('#'):
+ network_info_list = machine_id_str.split(';')
+ if len(network_info_list) % 6 != 0:
+ break
+ no_grps = len(network_info_list) / 6
+ i = 0
+ while i < no_grps:
+ k = i * 6
+ network_details.append((
+ network_info_list[k].strip().lower(),
+ network_info_list[k + 1].strip(),
+ network_info_list[k + 2].strip(),
+ network_info_list[k + 3].strip(),
+ network_info_list[k + 4].strip(),
+ network_info_list[k + 5].strip().split(',')))
+ i += 1
+ logging.debug(_("NIC information from vmtools : %s") % network_details)
return network_details
@@ -279,6 +285,7 @@ def _filter_duplicates(all_entries):
def _set_rhel_networking(network_details=None):
+ """Set IPv4 network settings for RHEL distros."""
network_details = network_details or []
all_dns_servers = []
for network_detail in network_details:
@@ -320,31 +327,33 @@ def _set_rhel_networking(network_details=None):
def _set_ubuntu_networking(network_details=None):
+ """Set IPv4 network settings for Ubuntu."""
network_details = network_details or []
- """ Set IPv4 network settings for Ubuntu """
all_dns_servers = []
- for network_detail in network_details:
+ interface_file_name = '/etc/network/interfaces'
+ # Remove file
+ os.remove(interface_file_name)
+ # Touch file
+ _execute(['touch', interface_file_name])
+ interface_file = open(interface_file_name, 'w')
+ for device, network_detail in enumerate(network_details):
mac_address, ip_address, subnet_mask, gateway, broadcast,\
dns_servers = network_detail
all_dns_servers.extend(dns_servers)
adapter_name, current_ip_address = \
_get_linux_adapter_name_and_ip_address(mac_address)
- if adapter_name and not ip_address == current_ip_address:
- interface_file_name = \
- '/etc/network/interfaces'
- # Remove file
- os.remove(interface_file_name)
- # Touch file
- _execute(['touch', interface_file_name])
- interface_file = open(interface_file_name, 'w')
+ if adapter_name:
interface_file.write('\nauto %s' % adapter_name)
interface_file.write('\niface %s inet static' % adapter_name)
interface_file.write('\nbroadcast %s' % broadcast)
interface_file.write('\ngateway %s' % gateway)
interface_file.write('\nnetmask %s' % subnet_mask)
- interface_file.write('\naddress %s' % ip_address)
- interface_file.close()
+ interface_file.write('\naddress %s\n' % ip_address)
+ logging.debug(_("Successfully configured NIC %d with "
+ "NIC info %s") % (device, network_detail))
+ interface_file.close()
+
if all_dns_servers:
dns_file_name = "/etc/resolv.conf"
os.remove(dns_file_name)
@@ -355,7 +364,8 @@ def _set_ubuntu_networking(network_details=None):
for dns_server in unique_entries:
dns_file.write("\nnameserver %s" % dns_server)
dns_file.close()
- print "\nRestarting networking....\n"
+
+ logging.debug(_("Restarting networking....\n"))
_execute(['/etc/init.d/networking', 'restart'])