summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorDan Prince <dan.prince@rackspace.com>2011-09-13 15:59:45 -0400
committerDan Prince <dan.prince@rackspace.com>2011-09-13 15:59:45 -0400
commitc4e4911bc90b37afc498f05f88f1128cbeff80e0 (patch)
treeb9190e9597472b718d6b6bf4c6c35b1dfb4224ff /nova
parent9b12a6c5ec11fd6ef3e110e6f0574762060ac809 (diff)
parent9f39ff070b5500a0ccb9a6454995f97342254381 (diff)
downloadnova-c4e4911bc90b37afc498f05f88f1128cbeff80e0.tar.gz
nova-c4e4911bc90b37afc498f05f88f1128cbeff80e0.tar.xz
nova-c4e4911bc90b37afc498f05f88f1128cbeff80e0.zip
Merge with trunk. Still one test failure in test_images.
Diffstat (limited to 'nova')
-rw-r--r--nova/api/auth.py1
-rw-r--r--nova/api/ec2/cloud.py4
-rw-r--r--nova/api/openstack/contrib/floating_ips.py4
-rw-r--r--nova/api/openstack/create_instance_helper.py5
-rw-r--r--nova/api/openstack/schemas/v1.1/server.rng2
-rw-r--r--nova/api/openstack/servers.py9
-rw-r--r--nova/api/openstack/views/addresses.py1
-rw-r--r--nova/api/openstack/views/images.py15
-rw-r--r--nova/api/openstack/views/servers.py9
-rw-r--r--nova/compute/api.py9
-rw-r--r--nova/compute/manager.py7
-rw-r--r--nova/context.py9
-rw-r--r--nova/db/api.py16
-rw-r--r--nova/db/sqlalchemy/api.py38
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/045_add_network_priority.py44
-rw-r--r--nova/db/sqlalchemy/models.py1
-rw-r--r--nova/exception.py15
-rw-r--r--nova/image/__init__.py56
-rw-r--r--nova/image/glance.py91
-rwxr-xr-x[-rw-r--r--]nova/network/linux_net.py65
-rw-r--r--nova/network/manager.py51
-rw-r--r--nova/network/quantum/__init__.py16
-rw-r--r--nova/network/quantum/client.py307
-rw-r--r--nova/network/quantum/manager.py324
-rw-r--r--nova/network/quantum/melange_connection.py141
-rw-r--r--nova/network/quantum/melange_ipam_lib.py205
-rw-r--r--nova/network/quantum/nova_ipam_lib.py195
-rw-r--r--nova/network/quantum/quantum_connection.py118
-rw-r--r--nova/scheduler/abstract_scheduler.py19
-rw-r--r--nova/scheduler/api.py52
-rw-r--r--nova/scheduler/base_scheduler.py23
-rw-r--r--nova/tests/api/openstack/contrib/test_createserverext.py2
-rw-r--r--nova/tests/api/openstack/contrib/test_floating_ips.py78
-rw-r--r--nova/tests/api/openstack/fakes.py2
-rw-r--r--nova/tests/api/openstack/test_images.py122
-rw-r--r--nova/tests/api/openstack/test_servers.py73
-rw-r--r--nova/tests/fake_network.py164
-rw-r--r--nova/tests/glance/stubs.py6
-rw-r--r--nova/tests/integrated/integrated_helpers.py2
-rw-r--r--nova/tests/scheduler/test_abstract_scheduler.py55
-rw-r--r--nova/tests/scheduler/test_scheduler.py102
-rw-r--r--nova/tests/test_cloud.py4
-rw-r--r--nova/tests/test_compute.py15
-rw-r--r--nova/tests/test_libvirt.py153
-rwxr-xr-xnova/tests/test_linux_net.py347
-rw-r--r--nova/tests/test_network.py139
-rw-r--r--nova/tests/test_quantum.py323
-rw-r--r--nova/tests/test_virt_drivers.py3
-rw-r--r--nova/tests/test_vmwareapi.py3
-rw-r--r--nova/tests/test_xenapi.py5
-rw-r--r--nova/version.py2
-rw-r--r--nova/virt/disk.py20
-rw-r--r--nova/virt/driver.py3
-rw-r--r--nova/virt/fake.py2
-rw-r--r--nova/virt/hyperv.py2
-rw-r--r--nova/virt/images.py3
-rw-r--r--nova/virt/libvirt/connection.py10
-rw-r--r--nova/virt/libvirt/firewall.py4
-rw-r--r--nova/virt/libvirt/vif.py2
-rw-r--r--nova/virt/vmwareapi/fake.py6
-rw-r--r--nova/virt/vmwareapi/vmops.py4
-rw-r--r--nova/virt/vmwareapi/vmware_images.py69
-rw-r--r--nova/virt/vmwareapi_conn.py2
-rw-r--r--nova/virt/xenapi/vm_utils.py17
-rw-r--r--nova/virt/xenapi/vmops.py26
-rw-r--r--nova/virt/xenapi_conn.py4
66 files changed, 3164 insertions, 462 deletions
diff --git a/nova/api/auth.py b/nova/api/auth.py
index cd0d38b3f..f73cae01e 100644
--- a/nova/api/auth.py
+++ b/nova/api/auth.py
@@ -70,6 +70,7 @@ class KeystoneContext(wsgi.Middleware):
project_id,
roles=roles,
auth_token=auth_token,
+ strategy='keystone',
remote_address=remote_address)
req.environ['nova.context'] = ctx
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 049ca6f93..4f7030a5a 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -1200,8 +1200,10 @@ class CloudController(object):
instances.append(instance)
else:
try:
+ # always filter out deleted instances
+ search_opts['deleted'] = False
instances = self.compute_api.get_all(context,
- search_opts=search_opts)
+ search_opts=search_opts)
except exception.NotFound:
instances = []
for instance in instances:
diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py
index d1add8f83..d078b26c6 100644
--- a/nova/api/openstack/contrib/floating_ips.py
+++ b/nova/api/openstack/contrib/floating_ips.py
@@ -107,7 +107,7 @@ class FloatingIPController(object):
context = req.environ['nova.context']
floating_ip = self.network_api.get_floating_ip(context, id)
- if 'fixed_ip' in floating_ip:
+ if floating_ip.get('fixed_ip'):
self.network_api.disassociate_floating_ip(context,
floating_ip['address'])
@@ -161,7 +161,7 @@ class Floating_ips(extensions.ExtensionDescriptor):
raise webob.exc.HTTPBadRequest(explanation=msg)
floating_ip = self.network_api.get_floating_ip_by_ip(context, address)
- if 'fixed_ip' in floating_ip:
+ if floating_ip.get('fixed_ip'):
self.network_api.disassociate_floating_ip(context, address)
return webob.Response(status_int=202)
diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py
index 29e071609..e27ddf78b 100644
--- a/nova/api/openstack/create_instance_helper.py
+++ b/nova/api/openstack/create_instance_helper.py
@@ -92,7 +92,8 @@ class CreateInstanceHelper(object):
if str(image_href).startswith(req.application_url):
image_href = image_href.split('/').pop()
try:
- image_service, image_id = nova.image.get_image_service(image_href)
+ image_service, image_id = nova.image.get_image_service(context,
+ image_href)
kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
req, image_service, image_id)
images = set([str(x['id']) for x in image_service.index(context)])
@@ -282,7 +283,7 @@ class CreateInstanceHelper(object):
try:
ramdisk_id = image_meta['properties']['ramdisk_id']
except KeyError:
- raise exception.RamdiskNotFoundForImage(image_id=image_id)
+ ramdisk_id = None
return kernel_id, ramdisk_id
diff --git a/nova/api/openstack/schemas/v1.1/server.rng b/nova/api/openstack/schemas/v1.1/server.rng
index dbd169a83..ef835e408 100644
--- a/nova/api/openstack/schemas/v1.1/server.rng
+++ b/nova/api/openstack/schemas/v1.1/server.rng
@@ -1,6 +1,8 @@
<element name="server" ns="http://docs.openstack.org/compute/api/v1.1"
xmlns="http://relaxng.org/ns/structure/1.0">
<attribute name="name"> <text/> </attribute>
+ <attribute name="userId"> <text/> </attribute>
+ <attribute name="tenantId"> <text/> </attribute>
<attribute name="id"> <text/> </attribute>
<attribute name="uuid"> <text/> </attribute>
<attribute name="updated"> <text/> </attribute>
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index acd2209af..f5447edc5 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -334,9 +334,8 @@ class Controller(object):
LOG.exception(msg)
raise exc.HTTPBadRequest(explanation=msg)
try:
- # TODO(gundlach): pass reboot_type, support soft reboot in
- # virt driver
- self.compute_api.reboot(req.environ['nova.context'], id)
+ self.compute_api.reboot(req.environ['nova.context'], id,
+ reboot_type)
except Exception, e:
LOG.exception(_("Error in reboot %s"), e)
raise exc.HTTPUnprocessableEntity()
@@ -873,6 +872,8 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer):
def _add_server_attributes(self, node, server):
node.setAttribute('id', str(server['id']))
+ node.setAttribute('userId', str(server['user_id']))
+ node.setAttribute('tenantId', str(server['tenant_id']))
node.setAttribute('uuid', str(server['uuid']))
node.setAttribute('hostId', str(server['hostId']))
node.setAttribute('name', server['name'])
@@ -1009,7 +1010,7 @@ def create_resource(version='1.0'):
"attributes": {
"server": ["id", "imageId", "name", "flavorId", "hostId",
"status", "progress", "adminPass", "flavorRef",
- "imageRef"],
+ "imageRef", "userId", "tenantId"],
"link": ["rel", "type", "href"],
},
"dict_collections": {
diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py
index 8f07a2289..8d38bc9c3 100644
--- a/nova/api/openstack/views/addresses.py
+++ b/nova/api/openstack/views/addresses.py
@@ -88,7 +88,6 @@ class ViewBuilderV11(ViewBuilder):
try:
return interface['network']['label']
except (TypeError, KeyError) as exc:
- LOG.exception(exc)
raise TypeError
def _extract_ipv4_addresses(self, interface):
diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py
index 20c99124b..86e8d7f3a 100644
--- a/nova/api/openstack/views/images.py
+++ b/nova/api/openstack/views/images.py
@@ -37,17 +37,18 @@ class ViewBuilder(object):
def _format_status(self, image):
"""Update the status field to standardize format."""
status_mapping = {
- 'pending': 'QUEUED',
- 'decrypting': 'PREPARING',
- 'untarring': 'SAVING',
- 'available': 'ACTIVE',
- 'killed': 'FAILED',
+ 'active': 'ACTIVE',
+ 'queued': 'SAVING',
+ 'saving': 'SAVING',
+ 'deleted': 'DELETED',
+ 'pending_delete': 'DELETED',
+ 'killed': 'ERROR',
}
try:
- image['status'] = status_mapping[image['status']].upper()
+ image['status'] = status_mapping[image['status']]
except KeyError:
- image['status'] = image['status'].upper()
+ image['status'] = 'UNKNOWN'
def _build_server(self, image, image_obj):
"""Indicates that you must use a ViewBuilder subclass."""
diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py
index 3a13d15f1..473dc9e7e 100644
--- a/nova/api/openstack/views/servers.py
+++ b/nova/api/openstack/views/servers.py
@@ -66,6 +66,8 @@ class ViewBuilder(object):
inst_dict = {
'id': inst['id'],
'name': inst['display_name'],
+ 'user_id': inst.get('user_id', ''),
+ 'tenant_id': inst.get('project_id', ''),
'status': common.status_from_state(vm_state, task_state)}
# Return the metadata as a dictionary
@@ -143,6 +145,8 @@ class ViewBuilderV11(ViewBuilder):
response['server']['accessIPv4'] = inst.get('access_ip_v4') or ""
response['server']['accessIPv6'] = inst.get('access_ip_v6') or ""
+ response['server']['key_name'] = inst.get('key_name', '')
+ response['server']['config_drive'] = inst.get('config_drive')
return response
@@ -183,8 +187,6 @@ class ViewBuilderV11(ViewBuilder):
def _build_extra(self, response, inst):
self._build_links(response, inst)
response['uuid'] = inst['uuid']
- response['key_name'] = inst.get('key_name', '')
- self._build_config_drive(response, inst)
def _build_links(self, response, inst):
href = self.generate_href(inst["id"])
@@ -203,9 +205,6 @@ class ViewBuilderV11(ViewBuilder):
response["links"] = links
- def _build_config_drive(self, response, inst):
- response['config_drive'] = inst.get('config_drive')
-
def generate_href(self, server_id):
"""Create an url that refers to a specific server id."""
return os.path.join(self.base_url, self.project_id,
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 6806522f7..d674224e5 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -202,7 +202,8 @@ class API(base.Base):
self._check_injected_file_quota(context, injected_files)
self._check_requested_networks(context, requested_networks)
- (image_service, image_id) = nova.image.get_image_service(image_href)
+ (image_service, image_id) = nova.image.get_image_service(context,
+ image_href)
image = image_service.show(context, image_id)
config_drive_id = None
@@ -877,6 +878,7 @@ class API(base.Base):
'image': 'image_ref',
'name': 'display_name',
'instance_name': 'name',
+ 'tenant_id': 'project_id',
'recurse_zones': None,
'flavor': _remap_flavor_filter,
'fixed_ip': _remap_fixed_ip_filter}
@@ -1041,13 +1043,14 @@ class API(base.Base):
return recv_meta
@scheduler_api.reroute_compute("reboot")
- def reboot(self, context, instance_id):
+ def reboot(self, context, instance_id, reboot_type):
"""Reboot the given instance."""
self.update(context,
instance_id,
vm_state=vm_states.ACTIVE,
task_state=task_states.REBOOTING)
- self._cast_compute_message('reboot_instance', context, instance_id)
+ self._cast_compute_message('reboot_instance', context, instance_id,
+ reboot_type)
@scheduler_api.reroute_compute("rebuild")
def rebuild(self, context, instance_id, image_href, admin_password,
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 0477db745..7915830ec 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -322,7 +322,8 @@ class ComputeManager(manager.SchedulerDependentManager):
# used by the image service. This should be refactored to be
# consistent.
image_href = instance['image_ref']
- image_service, image_id = nova.image.get_image_service(image_href)
+ image_service, image_id = nova.image.get_image_service(context,
+ image_href)
image_meta = image_service.show(context, image_id)
try:
@@ -579,7 +580,7 @@ class ComputeManager(manager.SchedulerDependentManager):
@exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
@checks_instance_lock
- def reboot_instance(self, context, instance_id):
+ def reboot_instance(self, context, instance_id, reboot_type="SOFT"):
"""Reboot an instance on this host."""
LOG.audit(_("Rebooting instance %s"), instance_id, context=context)
context = context.elevated()
@@ -601,7 +602,7 @@ class ComputeManager(manager.SchedulerDependentManager):
context=context)
network_info = self._get_instance_nw_info(context, instance_ref)
- self.driver.reboot(instance_ref, network_info)
+ self.driver.reboot(instance_ref, network_info, reboot_type)
current_power_state = self._get_power_state(context, instance_ref)
self._instance_update(context,
diff --git a/nova/context.py b/nova/context.py
index 5c22641a0..de5b791c4 100644
--- a/nova/context.py
+++ b/nova/context.py
@@ -32,7 +32,7 @@ class RequestContext(object):
def __init__(self, user_id, project_id, is_admin=None, read_deleted=False,
roles=None, remote_address=None, timestamp=None,
- request_id=None, auth_token=None):
+ request_id=None, auth_token=None, strategy='noauth'):
self.user_id = user_id
self.project_id = project_id
self.roles = roles or []
@@ -50,6 +50,7 @@ class RequestContext(object):
request_id = unicode(uuid.uuid4())
self.request_id = request_id
self.auth_token = auth_token
+ self.strategy = strategy
def to_dict(self):
return {'user_id': self.user_id,
@@ -60,7 +61,8 @@ class RequestContext(object):
'remote_address': self.remote_address,
'timestamp': utils.strtime(self.timestamp),
'request_id': self.request_id,
- 'auth_token': self.auth_token}
+ 'auth_token': self.auth_token,
+ 'strategy': self.strategy}
@classmethod
def from_dict(cls, values):
@@ -77,7 +79,8 @@ class RequestContext(object):
remote_address=self.remote_address,
timestamp=self.timestamp,
request_id=self.request_id,
- auth_token=self.auth_token)
+ auth_token=self.auth_token,
+ strategy=self.strategy)
def get_admin_context(read_deleted=False):
diff --git a/nova/db/api.py b/nova/db/api.py
index 148887635..a9d2dc065 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -324,13 +324,15 @@ def migration_get_by_instance_and_status(context, instance_uuid, status):
####################
-def fixed_ip_associate(context, address, instance_id, network_id=None):
+def fixed_ip_associate(context, address, instance_id, network_id=None,
+ reserved=False):
"""Associate fixed ip to instance.
Raises if fixed ip is not available.
"""
- return IMPL.fixed_ip_associate(context, address, instance_id, network_id)
+ return IMPL.fixed_ip_associate(context, address, instance_id, network_id,
+ reserved)
def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None):
@@ -420,6 +422,11 @@ def virtual_interface_get_by_address(context, address):
return IMPL.virtual_interface_get_by_address(context, address)
+def virtual_interface_get_by_uuid(context, vif_uuid):
+ """Gets a virtual interface from the table filtering on vif uuid."""
+ return IMPL.virtual_interface_get_by_uuid(context, vif_uuid)
+
+
def virtual_interface_get_by_fixed_ip(context, fixed_ip_id):
"""Gets the virtual interface fixed_ip is associated with."""
return IMPL.virtual_interface_get_by_fixed_ip(context, fixed_ip_id)
@@ -715,6 +722,11 @@ def network_get_by_bridge(context, bridge):
return IMPL.network_get_by_bridge(context, bridge)
+def network_get_by_uuid(context, uuid):
+ """Get a network by uuid or raise if it does not exist."""
+ return IMPL.network_get_by_uuid(context, uuid)
+
+
def network_get_by_cidr(context, cidr):
"""Get a network by cidr or raise if it does not exist"""
return IMPL.network_get_by_cidr(context, cidr)
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index b99667afc..e5a661c7f 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -669,14 +669,19 @@ def floating_ip_update(context, address, values):
@require_admin_context
-def fixed_ip_associate(context, address, instance_id, network_id=None):
+def fixed_ip_associate(context, address, instance_id, network_id=None,
+ reserved=False):
+ """Keyword arguments:
+ reserved -- should be a boolean value(True or False), exact value will be
+ used to filter on the fixed ip address
+ """
session = get_session()
with session.begin():
network_or_none = or_(models.FixedIp.network_id == network_id,
models.FixedIp.network_id == None)
fixed_ip_ref = session.query(models.FixedIp).\
filter(network_or_none).\
- filter_by(reserved=False).\
+ filter_by(reserved=reserved).\
filter_by(deleted=False).\
filter_by(address=address).\
with_lockmode('update').\
@@ -945,6 +950,22 @@ def virtual_interface_get_by_address(context, address):
@require_context
+def virtual_interface_get_by_uuid(context, vif_uuid):
+ """Gets a virtual interface from the table.
+
+ :param vif_uuid: the uuid of the interface you're looking to get
+ """
+ session = get_session()
+ vif_ref = session.query(models.VirtualInterface).\
+ filter_by(uuid=vif_uuid).\
+ options(joinedload('network')).\
+ options(joinedload('instance')).\
+ options(joinedload('fixed_ips')).\
+ first()
+ return vif_ref
+
+
+@require_context
def virtual_interface_get_by_fixed_ip(context, fixed_ip_id):
"""Gets the virtual interface fixed_ip is associated with.
@@ -1858,6 +1879,19 @@ def network_get_by_bridge(context, bridge):
@require_admin_context
+def network_get_by_uuid(context, uuid):
+ session = get_session()
+ result = session.query(models.Network).\
+ filter_by(uuid=uuid).\
+ filter_by(deleted=False).\
+ first()
+
+ if not result:
+ raise exception.NetworkNotFoundForUUID(uuid=uuid)
+ return result
+
+
+@require_admin_context
def network_get_by_cidr(context, cidr):
session = get_session()
result = session.query(models.Network).\
diff --git a/nova/db/sqlalchemy/migrate_repo/versions/045_add_network_priority.py b/nova/db/sqlalchemy/migrate_repo/versions/045_add_network_priority.py
new file mode 100644
index 000000000..b9b0ea37c
--- /dev/null
+++ b/nova/db/sqlalchemy/migrate_repo/versions/045_add_network_priority.py
@@ -0,0 +1,44 @@
+# Copyright 2011 Nicira, Inc.
+# 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 sqlalchemy import *
+from migrate import *
+
+from nova import log as logging
+from nova import utils
+
+
+meta = MetaData()
+
+networks = Table('networks', meta,
+ Column("id", Integer(), primary_key=True, nullable=False))
+
+# Add priority column to networks table
+priority = Column('priority', Integer())
+
+
+def upgrade(migrate_engine):
+ meta.bind = migrate_engine
+
+ try:
+ networks.create_column(priority)
+ except Exception:
+ logging.error(_("priority column not added to networks table"))
+ raise
+
+
+def downgrade(migrate_engine):
+ meta.bind = migrate_engine
+ networks.drop_column(priority)
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 854034f12..211049112 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -628,6 +628,7 @@ class Network(BASE, NovaBase):
dhcp_start = Column(String(255))
project_id = Column(String(255))
+ priority = Column(Integer)
host = Column(String(255)) # , ForeignKey('hosts.id'))
uuid = Column(String(36))
diff --git a/nova/exception.py b/nova/exception.py
index df6ff25cd..a3cbb98cf 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -403,10 +403,6 @@ class KernelNotFoundForImage(ImageNotFound):
message = _("Kernel not found for image %(image_id)s.")
-class RamdiskNotFoundForImage(ImageNotFound):
- message = _("Ramdisk not found for image %(image_id)s.")
-
-
class UserNotFound(NotFound):
message = _("User %(user_id)s could not be found.")
@@ -439,6 +435,10 @@ class NetworkNotFoundForBridge(NetworkNotFound):
message = _("Network could not be found for bridge %(bridge)s")
+class NetworkNotFoundForUUID(NetworkNotFound):
+ message = _("Network could not be found for uuid %(uuid)s")
+
+
class NetworkNotFoundForCidr(NetworkNotFound):
message = _("Network could not be found with cidr %(cidr)s.")
@@ -810,3 +810,10 @@ class CannotResizeToSmallerSize(NovaException):
class ImageTooLarge(NovaException):
message = _("Image is larger than instance type allows")
+
+
+class ZoneRequestError(Error):
+ def __init__(self, message=None):
+ if message is None:
+ message = _("1 or more Zones could not complete the request")
+ super(ZoneRequestError, self).__init__(message=message)
diff --git a/nova/image/__init__.py b/nova/image/__init__.py
index 5447c8a3a..307b73f01 100644
--- a/nova/image/__init__.py
+++ b/nova/image/__init__.py
@@ -16,70 +16,20 @@
# under the License.
-from urlparse import urlparse
-
import nova
-from nova import exception
from nova import utils
from nova import flags
-from nova.image import glance as glance_image_service
+from nova.image import glance
FLAGS = flags.FLAGS
-GlanceClient = utils.import_class('glance.client.Client')
-
-
-def _parse_image_ref(image_href):
- """Parse an image href into composite parts.
-
- :param image_href: href of an image
- :returns: a tuple of the form (image_id, host, port)
- :raises ValueError
-
- """
- o = urlparse(image_href)
- port = o.port or 80
- host = o.netloc.split(':', 1)[0]
- image_id = int(o.path.split('/')[-1])
- return (image_id, host, port)
-
-
def get_default_image_service():
ImageService = utils.import_class(FLAGS.image_service)
return ImageService()
-# FIXME(sirp): perhaps this should be moved to nova/images/glance so that we
-# keep Glance specific code together for the most part
-def get_glance_client(image_href):
- """Get the correct glance client and id for the given image_href.
-
- The image_href param can be an href of the form
- http://myglanceserver:9292/images/42, or just an int such as 42. If the
- image_href is an int, then flags are used to create the default
- glance client.
-
- :param image_href: image ref/id for an image
- :returns: a tuple of the form (glance_client, image_id)
-
- """
- image_href = image_href or 0
- if str(image_href).isdigit():
- glance_host, glance_port = \
- glance_image_service.pick_glance_api_server()
- glance_client = GlanceClient(glance_host, glance_port)
- return (glance_client, int(image_href))
-
- try:
- (image_id, host, port) = _parse_image_ref(image_href)
- except ValueError:
- raise exception.InvalidImageRef(image_href=image_href)
- glance_client = GlanceClient(host, port)
- return (glance_client, image_id)
-
-
-def get_image_service(image_href):
+def get_image_service(context, image_href):
"""Get the proper image_service and id for the given image_href.
The image_href param can be an href of the form
@@ -94,6 +44,6 @@ def get_image_service(image_href):
if str(image_href).isdigit():
return (get_default_image_service(), int(image_href))
- (glance_client, image_id) = get_glance_client(image_href)
+ (glance_client, image_id) = glance.get_glance_client(context, image_href)
image_service = nova.image.glance.GlanceImageService(glance_client)
return (image_service, image_id)
diff --git a/nova/image/glance.py b/nova/image/glance.py
index 80abc7384..13c8ff843 100644
--- a/nova/image/glance.py
+++ b/nova/image/glance.py
@@ -23,6 +23,7 @@ import copy
import datetime
import json
import random
+from urlparse import urlparse
from glance.common import exception as glance_exception
@@ -42,6 +43,35 @@ FLAGS = flags.FLAGS
GlanceClient = utils.import_class('glance.client.Client')
+def _parse_image_ref(image_href):
+ """Parse an image href into composite parts.
+
+ :param image_href: href of an image
+ :returns: a tuple of the form (image_id, host, port)
+ :raises ValueError
+
+ """
+ o = urlparse(image_href)
+ port = o.port or 80
+ host = o.netloc.split(':', 1)[0]
+ image_id = int(o.path.split('/')[-1])
+ return (image_id, host, port)
+
+
+def _create_glance_client(context, host, port):
+ if context.strategy == 'keystone':
+ # NOTE(dprince): Glance client just needs auth_tok right? Should we
+ # add username and tenant to the creds below?
+ creds = {'strategy': 'keystone',
+ 'username': context.user_id,
+ 'tenant': context.project_id}
+ glance_client = GlanceClient(host, port, auth_tok=context.auth_token,
+ creds=creds)
+ else:
+ glance_client = GlanceClient(host, port)
+ return glance_client
+
+
def pick_glance_api_server():
"""Return which Glance API server to use for the request
@@ -57,6 +87,33 @@ def pick_glance_api_server():
return host, port
+def get_glance_client(context, image_href):
+ """Get the correct glance client and id for the given image_href.
+
+ The image_href param can be an href of the form
+ http://myglanceserver:9292/images/42, or just an int such as 42. If the
+ image_href is an int, then flags are used to create the default
+ glance client.
+
+ :param image_href: image ref/id for an image
+ :returns: a tuple of the form (glance_client, image_id)
+
+ """
+ image_href = image_href or 0
+ if str(image_href).isdigit():
+ glance_host, glance_port = pick_glance_api_server()
+ glance_client = _create_glance_client(context, glance_host,
+ glance_port)
+ return (glance_client, int(image_href))
+
+ try:
+ (image_id, host, port) = _parse_image_ref(image_href)
+ except ValueError:
+ raise exception.InvalidImageRef(image_href=image_href)
+ glance_client = _create_glance_client(context, glance_host, glance_port)
+ return (glance_client, image_id)
+
+
class GlanceImageService(service.BaseImageService):
"""Provides storage and retrieval of disk image objects within Glance."""
@@ -71,23 +128,14 @@ class GlanceImageService(service.BaseImageService):
def __init__(self, client=None):
self._client = client
- def _get_client(self):
+ def _get_client(self, context):
# NOTE(sirp): we want to load balance each request across glance
# servers. Since GlanceImageService is a long-lived object, `client`
# is made to choose a new server each time via this property.
if self._client is not None:
return self._client
glance_host, glance_port = pick_glance_api_server()
- return GlanceClient(glance_host, glance_port)
-
- def _set_client(self, client):
- self._client = client
-
- client = property(_get_client, _set_client)
-
- def _set_client_context(self, context):
- """Sets the client's auth token."""
- self.client.set_auth_token(context.auth_token)
+ return _create_glance_client(context, glance_host, glance_port)
def index(self, context, **kwargs):
"""Calls out to Glance for a list of images available."""
@@ -128,14 +176,14 @@ class GlanceImageService(service.BaseImageService):
def _get_images(self, context, **kwargs):
"""Get image entitites from images service"""
- self._set_client_context(context)
# ensure filters is a dict
kwargs['filters'] = kwargs.get('filters') or {}
# NOTE(vish): don't filter out private images
kwargs['filters'].setdefault('is_public', 'none')
- return self._fetch_images(self.client.get_images_detailed, **kwargs)
+ client = self._get_client(context)
+ return self._fetch_images(client.get_images_detailed, **kwargs)
def _fetch_images(self, fetch_func, **kwargs):
"""Paginate through results from glance server"""
@@ -168,9 +216,8 @@ class GlanceImageService(service.BaseImageService):
def show(self, context, image_id):
"""Returns a dict with image data for the given opaque image id."""
- self._set_client_context(context)
try:
- image_meta = self.client.get_image_meta(image_id)
+ image_meta = self._get_client(context).get_image_meta(image_id)
except glance_exception.NotFound:
raise exception.ImageNotFound(image_id=image_id)
@@ -192,9 +239,9 @@ class GlanceImageService(service.BaseImageService):
def get(self, context, image_id, data):
"""Calls out to Glance for metadata and data and writes data."""
- self._set_client_context(context)
try:
- image_meta, image_chunks = self.client.get_image(image_id)
+ client = self._get_client(context)
+ image_meta, image_chunks = client.get_image(image_id)
except glance_exception.NotFound:
raise exception.ImageNotFound(image_id=image_id)
@@ -210,7 +257,6 @@ class GlanceImageService(service.BaseImageService):
:raises: AlreadyExists if the image already exist.
"""
- self._set_client_context(context)
# Translate Base -> Service
LOG.debug(_('Creating image in Glance. Metadata passed in %s'),
image_meta)
@@ -218,7 +264,7 @@ class GlanceImageService(service.BaseImageService):
LOG.debug(_('Metadata after formatting for Glance %s'),
sent_service_image_meta)
- recv_service_image_meta = self.client.add_image(
+ recv_service_image_meta = self._get_client(context).add_image(
sent_service_image_meta, data)
# Translate Service -> Base
@@ -233,12 +279,12 @@ class GlanceImageService(service.BaseImageService):
:raises: ImageNotFound if the image does not exist.
"""
- self._set_client_context(context)
# NOTE(vish): show is to check if image is available
self.show(context, image_id)
image_meta = _convert_to_string(image_meta)
try:
- image_meta = self.client.update_image(image_id, image_meta, data)
+ client = self._get_client(context)
+ image_meta = client.update_image(image_id, image_meta, data)
except glance_exception.NotFound:
raise exception.ImageNotFound(image_id=image_id)
@@ -251,11 +297,10 @@ class GlanceImageService(service.BaseImageService):
:raises: ImageNotFound if the image does not exist.
"""
- self._set_client_context(context)
# NOTE(vish): show is to check if image is available
self.show(context, image_id)
try:
- result = self.client.delete_image(image_id)
+ result = self._get_client(context).delete_image(image_id)
except glance_exception.NotFound:
raise exception.ImageNotFound(image_id=image_id)
return result
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index 57c1d0c28..7d89b2bcc 100644..100755
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -68,6 +68,9 @@ flags.DEFINE_string('linuxnet_interface_driver',
'Driver used to create ethernet devices.')
flags.DEFINE_string('linuxnet_ovs_integration_bridge',
'br-int', 'Name of Open vSwitch bridge used with linuxnet')
+flags.DEFINE_bool('use_single_default_gateway',
+ False, 'Use single default gateway. Only first nic of vm'
+ ' will get default gateway from dhcp server')
binary_name = os.path.basename(inspect.stack()[-1][1])
@@ -511,6 +514,32 @@ def get_dhcp_hosts(context, network_ref):
return '\n'.join(hosts)
+def get_dhcp_opts(context, network_ref):
+ """Get network's hosts config in dhcp-opts format."""
+ hosts = []
+ ips_ref = db.network_get_associated_fixed_ips(context, network_ref['id'])
+
+ if ips_ref:
+ #set of instance ids
+ instance_set = set([fixed_ip_ref['instance_id']
+ for fixed_ip_ref in ips_ref])
+ default_gw_network_node = {}
+ for instance_id in instance_set:
+ vifs = db.virtual_interface_get_by_instance(context, instance_id)
+ if vifs:
+ #offer a default gateway to the first virtual interface
+ default_gw_network_node[instance_id] = vifs[0]['network_id']
+
+ for fixed_ip_ref in ips_ref:
+ instance_id = fixed_ip_ref['instance_id']
+ if instance_id in default_gw_network_node:
+ target_network_id = default_gw_network_node[instance_id]
+ # we don't want default gateway for this fixed ip
+ if target_network_id != fixed_ip_ref['network_id']:
+ hosts.append(_host_dhcp_opts(fixed_ip_ref))
+ return '\n'.join(hosts)
+
+
# NOTE(ja): Sending a HUP only reloads the hostfile, so any
# configuration options (like dchp-range, vlan, ...)
# aren't reloaded.
@@ -526,6 +555,12 @@ def update_dhcp(context, dev, network_ref):
with open(conffile, 'w') as f:
f.write(get_dhcp_hosts(context, network_ref))
+ if FLAGS.use_single_default_gateway:
+ optsfile = _dhcp_file(dev, 'opts')
+ with open(optsfile, 'w') as f:
+ f.write(get_dhcp_opts(context, network_ref))
+ os.chmod(optsfile, 0644)
+
# Make sure dnsmasq can actually read it (it setuid()s to "nobody")
os.chmod(conffile, 0644)
@@ -563,6 +598,9 @@ def update_dhcp(context, dev, network_ref):
if FLAGS.dns_server:
cmd += ['-h', '-R', '--server=%s' % FLAGS.dns_server]
+ if FLAGS.use_single_default_gateway:
+ cmd += ['--dhcp-optsfile=%s' % _dhcp_file(dev, 'opts')]
+
_execute(*cmd, run_as_root=True)
@@ -625,13 +663,32 @@ def _host_lease(fixed_ip_ref):
instance_ref['hostname'] or '*')
+def _host_dhcp_network(fixed_ip_ref):
+ instance_ref = fixed_ip_ref['instance']
+ return 'NW-i%08d-%s' % (instance_ref['id'],
+ fixed_ip_ref['network_id'])
+
+
def _host_dhcp(fixed_ip_ref):
"""Return a host string for an address in dhcp-host format."""
instance_ref = fixed_ip_ref['instance']
- return '%s,%s.%s,%s' % (fixed_ip_ref['virtual_interface']['address'],
- instance_ref['hostname'],
- FLAGS.dhcp_domain,
- fixed_ip_ref['address'])
+ vif = fixed_ip_ref['virtual_interface']
+ if FLAGS.use_single_default_gateway:
+ return '%s,%s.%s,%s,%s' % (vif['address'],
+ instance_ref['hostname'],
+ FLAGS.dhcp_domain,
+ fixed_ip_ref['address'],
+ "net:" + _host_dhcp_network(fixed_ip_ref))
+ else:
+ return '%s,%s.%s,%s' % (vif['address'],
+ instance_ref['hostname'],
+ FLAGS.dhcp_domain,
+ fixed_ip_ref['address'])
+
+
+def _host_dhcp_opts(fixed_ip_ref):
+ """Return a host string for an address in dhcp-host format."""
+ return '%s,%s' % (_host_dhcp_network(fixed_ip_ref), 3)
def _execute(*cmd, **kwargs):
diff --git a/nova/network/manager.py b/nova/network/manager.py
index e6b30d1a0..da360720b 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -74,7 +74,7 @@ flags.DEFINE_string('flat_network_bridge', None,
'Bridge for simple network instances')
flags.DEFINE_string('flat_network_dns', '8.8.4.4',
'Dns for simple network')
-flags.DEFINE_bool('flat_injected', True,
+flags.DEFINE_bool('flat_injected', False,
'Whether to attempt to inject network setup into guest')
flags.DEFINE_string('flat_interface', None,
'FlatDhcp will bridge into this interface if set')
@@ -128,8 +128,8 @@ class RPCAllocateFixedIP(object):
"""Calls allocate_fixed_ip once for each network."""
green_pool = greenpool.GreenPool()
- vpn = kwargs.pop('vpn')
- requested_networks = kwargs.pop('requested_networks')
+ vpn = kwargs.get('vpn')
+ requested_networks = kwargs.get('requested_networks')
for network in networks:
address = None
@@ -448,7 +448,7 @@ class NetworkManager(manager.SchedulerDependentManager):
try:
fixed_ips = kwargs.get('fixed_ips') or \
self.db.fixed_ip_get_by_instance(context, instance_id)
- except exceptions.FixedIpNotFoundForInstance:
+ except exception.FixedIpNotFoundForInstance:
fixed_ips = []
LOG.debug(_("network deallocation for instance |%s|"), instance_id,
context=context)
@@ -484,6 +484,9 @@ class NetworkManager(manager.SchedulerDependentManager):
for vif in vifs:
network = vif['network']
+ if network is None:
+ continue
+
# determine which of the instance's IPs belong to this network
network_IPs = [fixed_ip['address'] for fixed_ip in fixed_ips if
fixed_ip['network_id'] == network['id']]
@@ -546,21 +549,23 @@ class NetworkManager(manager.SchedulerDependentManager):
def _allocate_mac_addresses(self, context, instance_id, networks):
"""Generates mac addresses and creates vif rows in db for them."""
for network in networks:
- vif = {'address': self.generate_mac_address(),
+ self.add_virtual_interface(context, instance_id, network['id'])
+
+ def add_virtual_interface(self, context, instance_id, network_id):
+ vif = {'address': self.generate_mac_address(),
'instance_id': instance_id,
- 'network_id': network['id'],
+ 'network_id': network_id,
'uuid': str(utils.gen_uuid())}
- # try FLAG times to create a vif record with a unique mac_address
- for i in range(FLAGS.create_unique_mac_address_attempts):
- try:
- self.db.virtual_interface_create(context, vif)
- break
- except exception.VirtualInterfaceCreateException:
- vif['address'] = self.generate_mac_address()
- else:
- self.db.virtual_interface_delete_by_instance(context,
+ # try FLAG times to create a vif record with a unique mac_address
+ for _ in xrange(FLAGS.create_unique_mac_address_attempts):
+ try:
+ return self.db.virtual_interface_create(context, vif)
+ except exception.VirtualInterfaceCreateException:
+ vif['address'] = self.generate_mac_address()
+ else:
+ self.db.virtual_interface_delete_by_instance(context,
instance_id)
- raise exception.VirtualInterfaceMacAddressException()
+ raise exception.VirtualInterfaceMacAddressException()
def generate_mac_address(self):
"""Generate an Ethernet MAC address."""
@@ -789,6 +794,15 @@ class NetworkManager(manager.SchedulerDependentManager):
self._create_fixed_ips(context, network['id'])
return networks
+ def delete_network(self, context, fixed_range, require_disassociated=True):
+
+ network = db.network_get_by_cidr(context, fixed_range)
+
+ if require_disassociated and network.project_id is not None:
+ raise ValueError(_('Network must be disassociated from project %s'
+ ' before delete' % network.project_id))
+ db.network_delete_safe(context, network.id)
+
@property
def _bottom_reserved_ips(self): # pylint: disable=R0201
"""Number of reserved ips at the bottom of the range."""
@@ -890,7 +904,7 @@ class FlatManager(NetworkManager):
def _allocate_fixed_ips(self, context, instance_id, host, networks,
**kwargs):
"""Calls allocate_fixed_ip once for each network."""
- requested_networks = kwargs.pop('requested_networks')
+ requested_networks = kwargs.get('requested_networks')
for network in networks:
address = None
if requested_networks is not None:
@@ -991,7 +1005,8 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager):
address = network['vpn_private_address']
self.db.fixed_ip_associate(context,
address,
- instance_id)
+ instance_id,
+ reserved=True)
else:
address = kwargs.get('address', None)
if address:
diff --git a/nova/network/quantum/__init__.py b/nova/network/quantum/__init__.py
new file mode 100644
index 000000000..f7fbfb511
--- /dev/null
+++ b/nova/network/quantum/__init__.py
@@ -0,0 +1,16 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Nicira Networks
+# 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.
diff --git a/nova/network/quantum/client.py b/nova/network/quantum/client.py
new file mode 100644
index 000000000..40c68dfdc
--- /dev/null
+++ b/nova/network/quantum/client.py
@@ -0,0 +1,307 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Citrix Systems
+# 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.
+# @author: Tyler Smith, Cisco Systems
+
+import httplib
+import json
+import socket
+import urllib
+
+
+# FIXME(danwent): All content in this file should be removed once the
+# packaging work for the quantum client libraries is complete.
+# At that point, we will be able to just install the libraries as a
+# dependency and import from quantum.client.* and quantum.common.*
+# Until then, we have simplified versions of these classes in this file.
+
+class JSONSerializer(object):
+ """This is a simple json-only serializer to use until we can just grab
+ the standard serializer from the quantum library.
+ """
+ def serialize(self, data, content_type):
+ try:
+ return json.dumps(data)
+ except TypeError:
+ pass
+ return json.dumps(to_primitive(data))
+
+ def deserialize(self, data, content_type):
+ return json.loads(data)
+
+
+# The full client lib will expose more
+# granular exceptions, for now, just try to distinguish
+# between the cases we care about.
+class QuantumNotFoundException(Exception):
+ """Indicates that Quantum Server returned 404"""
+ pass
+
+
+class QuantumServerException(Exception):
+ """Indicates any non-404 error from Quantum Server"""
+ pass
+
+
+class QuantumIOException(Exception):
+ """Indicates network IO trouble reaching Quantum Server"""
+ pass
+
+
+class api_call(object):
+ """A Decorator to add support for format and tenant overriding"""
+ def __init__(self, func):
+ self.func = func
+
+ def __get__(self, instance, owner):
+ def with_params(*args, **kwargs):
+ """Temporarily set format and tenant for this request"""
+ (format, tenant) = (instance.format, instance.tenant)
+
+ if 'format' in kwargs:
+ instance.format = kwargs['format']
+ if 'tenant' in kwargs:
+ instance.tenant = kwargs['tenant']
+
+ ret = None
+ try:
+ ret = self.func(instance, *args)
+ finally:
+ (instance.format, instance.tenant) = (format, tenant)
+ return ret
+ return with_params
+
+
+class Client(object):
+ """A base client class - derived from Glance.BaseClient"""
+
+ action_prefix = '/v1.0/tenants/{tenant_id}'
+
+ """Action query strings"""
+ networks_path = "/networks"
+ network_path = "/networks/%s"
+ ports_path = "/networks/%s/ports"
+ port_path = "/networks/%s/ports/%s"
+ attachment_path = "/networks/%s/ports/%s/attachment"
+
+ def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None,
+ format="xml", testing_stub=None, key_file=None,
+ cert_file=None, logger=None):
+ """Creates a new client to some service.
+
+ :param host: The host where service resides
+ :param port: The port where service resides
+ :param use_ssl: True to use SSL, False to use HTTP
+ :param tenant: The tenant ID to make requests with
+ :param format: The format to query the server with
+ :param testing_stub: A class that stubs basic server methods for tests
+ :param key_file: The SSL key file to use if use_ssl is true
+ :param cert_file: The SSL cert file to use if use_ssl is true
+ """
+ self.host = host
+ self.port = port
+ self.use_ssl = use_ssl
+ self.tenant = tenant
+ self.format = format
+ self.connection = None
+ self.testing_stub = testing_stub
+ self.key_file = key_file
+ self.cert_file = cert_file
+ self.logger = logger
+
+ def get_connection_type(self):
+ """Returns the proper connection type"""
+ if self.testing_stub:
+ return self.testing_stub
+ elif self.use_ssl:
+ return httplib.HTTPSConnection
+ else:
+ return httplib.HTTPConnection
+
+ def do_request(self, method, action, body=None,
+ headers=None, params=None):
+ """Connects to the server and issues a request.
+ Returns the result data, or raises an appropriate exception if
+ HTTP status code is not 2xx
+
+ :param method: HTTP method ("GET", "POST", "PUT", etc...)
+ :param body: string of data to send, or None (default)
+ :param headers: mapping of key/value pairs to add as headers
+ :param params: dictionary of key/value pairs to add to append
+ to action
+ """
+
+ # Ensure we have a tenant id
+ if not self.tenant:
+ raise Exception(_("Tenant ID not set"))
+
+ # Add format and tenant_id
+ action += ".%s" % self.format
+ action = Client.action_prefix + action
+ action = action.replace('{tenant_id}', self.tenant)
+
+ if type(params) is dict:
+ action += '?' + urllib.urlencode(params)
+
+ try:
+ connection_type = self.get_connection_type()
+ headers = headers or {"Content-Type":
+ "application/%s" % self.format}
+
+ # Open connection and send request, handling SSL certs
+ certs = {'key_file': self.key_file, 'cert_file': self.cert_file}
+ certs = dict((x, certs[x]) for x in certs if certs[x] != None)
+
+ if self.use_ssl and len(certs):
+ c = connection_type(self.host, self.port, **certs)
+ else:
+ c = connection_type(self.host, self.port)
+
+ if self.logger:
+ self.logger.debug(
+ _("Quantum Client Request:\n%(method)s %(action)s\n" %
+ locals()))
+ if body:
+ self.logger.debug(body)
+
+ c.request(method, action, body, headers)
+ res = c.getresponse()
+ status_code = self.get_status_code(res)
+ data = res.read()
+
+ if self.logger:
+ self.logger.debug("Quantum Client Reply (code = %s) :\n %s" \
+ % (str(status_code), data))
+
+ if status_code == httplib.NOT_FOUND:
+ raise QuantumNotFoundException(
+ _("Quantum entity not found: %s" % data))
+
+ if status_code in (httplib.OK,
+ httplib.CREATED,
+ httplib.ACCEPTED,
+ httplib.NO_CONTENT):
+ if data is not None and len(data):
+ return self.deserialize(data, status_code)
+ else:
+ raise QuantumServerException(
+ _("Server %(status_code)s error: %(data)s"
+ % locals()))
+
+ except (socket.error, IOError), e:
+ raise QuantumIOException(_("Unable to connect to "
+ "server. Got error: %s" % e))
+
+ def get_status_code(self, response):
+ """Returns the integer status code from the response, which
+ can be either a Webob.Response (used in testing) or httplib.Response
+ """
+ if hasattr(response, 'status_int'):
+ return response.status_int
+ else:
+ return response.status
+
+ def serialize(self, data):
+ if not data:
+ return None
+ elif type(data) is dict:
+ return JSONSerializer().serialize(data, self.content_type())
+ else:
+ raise Exception(_("unable to deserialize object of type = '%s'" %
+ type(data)))
+
+ def deserialize(self, data, status_code):
+ if status_code == 202:
+ return data
+ return JSONSerializer().deserialize(data, self.content_type())
+
+ def content_type(self, format=None):
+ if not format:
+ format = self.format
+ return "application/%s" % (format)
+
+ @api_call
+ def list_networks(self):
+ """Fetches a list of all networks for a tenant"""
+ return self.do_request("GET", self.networks_path)
+
+ @api_call
+ def show_network_details(self, network):
+ """Fetches the details of a certain network"""
+ return self.do_request("GET", self.network_path % (network))
+
+ @api_call
+ def create_network(self, body=None):
+ """Creates a new network"""
+ body = self.serialize(body)
+ return self.do_request("POST", self.networks_path, body=body)
+
+ @api_call
+ def update_network(self, network, body=None):
+ """Updates a network"""
+ body = self.serialize(body)
+ return self.do_request("PUT", self.network_path % (network), body=body)
+
+ @api_call
+ def delete_network(self, network):
+ """Deletes the specified network"""
+ return self.do_request("DELETE", self.network_path % (network))
+
+ @api_call
+ def list_ports(self, network):
+ """Fetches a list of ports on a given network"""
+ return self.do_request("GET", self.ports_path % (network))
+
+ @api_call
+ def show_port_details(self, network, port):
+ """Fetches the details of a certain port"""
+ return self.do_request("GET", self.port_path % (network, port))
+
+ @api_call
+ def create_port(self, network, body=None):
+ """Creates a new port on a given network"""
+ body = self.serialize(body)
+ return self.do_request("POST", self.ports_path % (network), body=body)
+
+ @api_call
+ def delete_port(self, network, port):
+ """Deletes the specified port from a network"""
+ return self.do_request("DELETE", self.port_path % (network, port))
+
+ @api_call
+ def set_port_state(self, network, port, body=None):
+ """Sets the state of the specified port"""
+ body = self.serialize(body)
+ return self.do_request("PUT",
+ self.port_path % (network, port), body=body)
+
+ @api_call
+ def show_port_attachment(self, network, port):
+ """Fetches the attachment-id associated with the specified port"""
+ return self.do_request("GET", self.attachment_path % (network, port))
+
+ @api_call
+ def attach_resource(self, network, port, body=None):
+ """Sets the attachment-id of the specified port"""
+ body = self.serialize(body)
+ return self.do_request("PUT",
+ self.attachment_path % (network, port), body=body)
+
+ @api_call
+ def detach_resource(self, network, port):
+ """Removes the attachment-id of the specified port"""
+ return self.do_request("DELETE",
+ self.attachment_path % (network, port))
diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py
new file mode 100644
index 000000000..23a9aba0d
--- /dev/null
+++ b/nova/network/quantum/manager.py
@@ -0,0 +1,324 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Nicira Networks, Inc
+# 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 db
+from nova import exception
+from nova import flags
+from nova import log as logging
+from nova import manager
+from nova.network import manager
+from nova.network.quantum import quantum_connection
+from nova import utils
+
+LOG = logging.getLogger("nova.network.quantum.manager")
+
+FLAGS = flags.FLAGS
+
+flags.DEFINE_string('quantum_ipam_lib',
+ 'nova.network.quantum.nova_ipam_lib',
+ "Indicates underlying IP address management library")
+
+
+class QuantumManager(manager.FlatManager):
+ """NetworkManager class that communicates with a Quantum service
+ via a web services API to provision VM network connectivity.
+
+ For IP Address management, QuantumManager can be configured to
+ use either Nova's local DB or the Melange IPAM service.
+
+ Currently, the QuantumManager does NOT support any of the 'gateway'
+ functionality implemented by the Nova VlanManager, including:
+ * floating IPs
+ * DHCP
+ * NAT gateway
+
+ Support for these capabilities are targted for future releases.
+ """
+
+ def __init__(self, q_conn=None, ipam_lib=None, *args, **kwargs):
+ """Initialize two key libraries, the connection to a
+ Quantum service, and the library for implementing IPAM.
+
+ Calls inherited FlatManager constructor.
+ """
+
+ if not q_conn:
+ q_conn = quantum_connection.QuantumClientConnection()
+ self.q_conn = q_conn
+
+ if not ipam_lib:
+ ipam_lib = FLAGS.quantum_ipam_lib
+ self.ipam = utils.import_object(ipam_lib).get_ipam_lib(self)
+
+ super(QuantumManager, self).__init__(*args, **kwargs)
+
+ def create_networks(self, context, label, cidr, multi_host, num_networks,
+ network_size, cidr_v6, gateway_v6, bridge,
+ bridge_interface, dns1=None, dns2=None, uuid=None,
+ **kwargs):
+ """Unlike other NetworkManagers, with QuantumManager, each
+ create_networks calls should create only a single network.
+
+ Two scenarios exist:
+ - no 'uuid' is specified, in which case we contact
+ Quantum and create a new network.
+ - an existing 'uuid' is specified, corresponding to
+ a Quantum network created out of band.
+
+ In both cases, we initialize a subnet using the IPAM lib.
+ """
+ if num_networks != 1:
+ raise Exception(_("QuantumManager requires that only one"
+ " network is created per call"))
+ q_tenant_id = kwargs["project_id"] or FLAGS.quantum_default_tenant_id
+ quantum_net_id = uuid
+ if quantum_net_id:
+ if not self.q_conn.network_exists(q_tenant_id, quantum_net_id):
+ raise Exception(_("Unable to find existing quantum " \
+ " network for tenant '%(q_tenant_id)s' with "
+ "net-id '%(quantum_net_id)s'" % locals()))
+ else:
+ # otherwise, create network from default quantum pool
+ quantum_net_id = self.q_conn.create_network(q_tenant_id, label)
+
+ ipam_tenant_id = kwargs.get("project_id", None)
+ priority = kwargs.get("priority", 0)
+ self.ipam.create_subnet(context, label, ipam_tenant_id, quantum_net_id,
+ priority, cidr, gateway_v6, cidr_v6, dns1, dns2)
+
+ def delete_network(self, context, fixed_range):
+ """Lookup network by IPv4 cidr, delete both the IPAM
+ subnet and the corresponding Quantum network.
+ """
+ project_id = context.project_id
+ quantum_net_id = self.ipam.get_network_id_by_cidr(
+ context, fixed_range, project_id)
+ self.ipam.delete_subnets_by_net_id(context, quantum_net_id,
+ project_id)
+ q_tenant_id = project_id or FLAGS.quantum_default_tenant_id
+ self.q_conn.delete_network(q_tenant_id, quantum_net_id)
+
+ def allocate_for_instance(self, context, **kwargs):
+ """Called by compute when it is creating a new VM.
+
+ There are three key tasks:
+ - Determine the number and order of vNICs to create
+ - Allocate IP addresses
+ - Create ports on a Quantum network and attach vNICs.
+
+ We support two approaches to determining vNICs:
+ - By default, a VM gets a vNIC for any network belonging
+ to the VM's project, and a vNIC for any "global" network
+ that has a NULL project_id. vNIC order is determined
+ by the network's 'priority' field.
+ - If the 'os-create-server-ext' was used to create the VM,
+ only the networks in 'requested_networks' are used to
+ create vNICs, and the vNIC order is determiend by the
+ order in the requested_networks array.
+
+ For each vNIC, use the FlatManager to create the entries
+ in the virtual_interfaces table, contact Quantum to
+ create a port and attachment the vNIC, and use the IPAM
+ lib to allocate IP addresses.
+ """
+ instance_id = kwargs.pop('instance_id')
+ instance_type_id = kwargs['instance_type_id']
+ host = kwargs.pop('host')
+ project_id = kwargs.pop('project_id')
+ LOG.debug(_("network allocations for instance %s"), instance_id)
+
+ requested_networks = kwargs.get('requested_networks')
+
+ if requested_networks:
+ net_proj_pairs = [(net_id, project_id) \
+ for (net_id, _i) in requested_networks]
+ else:
+ net_proj_pairs = self.ipam.get_project_and_global_net_ids(context,
+ project_id)
+
+ # Create a port via quantum and attach the vif
+ for (quantum_net_id, project_id) in net_proj_pairs:
+
+ # FIXME(danwent): We'd like to have the manager be
+ # completely decoupled from the nova networks table.
+ # However, other parts of nova sometimes go behind our
+ # back and access network data directly from the DB. So
+ # for now, the quantum manager knows that there is a nova
+ # networks DB table and accesses it here. updating the
+ # virtual_interfaces table to use UUIDs would be one
+ # solution, but this would require significant work
+ # elsewhere.
+ admin_context = context.elevated()
+ network_ref = db.network_get_by_uuid(admin_context,
+ quantum_net_id)
+
+ vif_rec = manager.FlatManager.add_virtual_interface(self,
+ context, instance_id, network_ref['id'])
+
+ # talk to Quantum API to create and attach port.
+ q_tenant_id = project_id or FLAGS.quantum_default_tenant_id
+ self.q_conn.create_and_attach_port(q_tenant_id, quantum_net_id,
+ vif_rec['uuid'])
+ self.ipam.allocate_fixed_ip(context, project_id, quantum_net_id,
+ vif_rec)
+
+ return self.get_instance_nw_info(context, instance_id,
+ instance_type_id, host)
+
+ def get_instance_nw_info(self, context, instance_id,
+ instance_type_id, host):
+ """This method is used by compute to fetch all network data
+ that should be used when creating the VM.
+
+ The method simply loops through all virtual interfaces
+ stored in the nova DB and queries the IPAM lib to get
+ the associated IP data.
+
+ The format of returned data is 'defined' by the initial
+ set of NetworkManagers found in nova/network/manager.py .
+ Ideally this 'interface' will be more formally defined
+ in the future.
+ """
+ network_info = []
+ instance = db.instance_get(context, instance_id)
+ project_id = instance.project_id
+
+ admin_context = context.elevated()
+ vifs = db.virtual_interface_get_by_instance(admin_context,
+ instance_id)
+ for vif in vifs:
+ q_tenant_id = project_id
+ ipam_tenant_id = project_id
+ net_id, port_id = self.q_conn.get_port_by_attachment(q_tenant_id,
+ vif['uuid'])
+ if not net_id:
+ q_tenant_id = FLAGS.quantum_default_tenant_id
+ ipam_tenant_id = None
+ net_id, port_id = self.q_conn.get_port_by_attachment(
+ q_tenant_id, vif['uuid'])
+ if not net_id:
+ # TODO(bgh): We need to figure out a way to tell if we
+ # should actually be raising this exception or not.
+ # In the case that a VM spawn failed it may not have
+ # attached the vif and raising the exception here
+ # prevents deletion of the VM. In that case we should
+ # probably just log, continue, and move on.
+ raise Exception(_("No network for for virtual interface %s") %
+ vif['uuid'])
+ (v4_subnet, v6_subnet) = self.ipam.get_subnets_by_net_id(context,
+ ipam_tenant_id, net_id)
+ v4_ips = self.ipam.get_v4_ips_by_interface(context,
+ net_id, vif['uuid'],
+ project_id=ipam_tenant_id)
+ v6_ips = self.ipam.get_v6_ips_by_interface(context,
+ net_id, vif['uuid'],
+ project_id=ipam_tenant_id)
+
+ quantum_net_id = v4_subnet['network_id'] or v6_subnet['network_id']
+
+ def ip_dict(ip, subnet):
+ return {
+ "ip": ip,
+ "netmask": subnet["netmask"],
+ "enabled": "1"}
+
+ network_dict = {
+ 'cidr': v4_subnet['cidr'],
+ 'injected': True,
+ 'multi_host': False}
+
+ info = {
+ 'gateway': v4_subnet['gateway'],
+ 'dhcp_server': v4_subnet['gateway'],
+ 'broadcast': v4_subnet['broadcast'],
+ 'mac': vif['address'],
+ 'vif_uuid': vif['uuid'],
+ 'dns': [],
+ 'ips': [ip_dict(ip, v4_subnet) for ip in v4_ips]}
+
+ if v6_subnet:
+ if v6_subnet['cidr']:
+ network_dict['cidr_v6'] = v6_subnet['cidr']
+ info['ip6s'] = [ip_dict(ip, v6_subnet) for ip in v6_ips]
+
+ if v6_subnet['gateway']:
+ info['gateway6'] = v6_subnet['gateway']
+
+ dns_dict = {}
+ for s in [v4_subnet, v6_subnet]:
+ for k in ['dns1', 'dns2']:
+ if s and s[k]:
+ dns_dict[s[k]] = None
+ info['dns'] = [d for d in dns_dict.keys()]
+
+ network_info.append((network_dict, info))
+ return network_info
+
+ def deallocate_for_instance(self, context, **kwargs):
+ """Called when a VM is terminated. Loop through each virtual
+ interface in the Nova DB and remove the Quantum port and
+ clear the IP allocation using the IPAM. Finally, remove
+ the virtual interfaces from the Nova DB.
+ """
+ instance_id = kwargs.get('instance_id')
+ project_id = kwargs.pop('project_id', None)
+
+ admin_context = context.elevated()
+ vifs = db.virtual_interface_get_by_instance(admin_context,
+ instance_id)
+ for vif_ref in vifs:
+ interface_id = vif_ref['uuid']
+ q_tenant_id = project_id
+ ipam_tenant_id = project_id
+ (net_id, port_id) = self.q_conn.get_port_by_attachment(q_tenant_id,
+ interface_id)
+ if not net_id:
+ q_tenant_id = FLAGS.quantum_default_tenant_id
+ ipam_tenant_id = None
+ (net_id, port_id) = self.q_conn.get_port_by_attachment(
+ q_tenant_id, interface_id)
+ if not net_id:
+ LOG.error("Unable to find port with attachment: %s" %
+ (interface_id))
+ continue
+ self.q_conn.detach_and_delete_port(q_tenant_id,
+ net_id, port_id)
+
+ self.ipam.deallocate_ips_by_vif(context, ipam_tenant_id,
+ net_id, vif_ref)
+
+ try:
+ db.virtual_interface_delete_by_instance(admin_context,
+ instance_id)
+ except exception.InstanceNotFound:
+ LOG.error(_("Attempted to deallocate non-existent instance: %s" %
+ (instance_id)))
+
+ def validate_networks(self, context, networks):
+ """Validates that this tenant has quantum networks with the associated
+ UUIDs. This is called by the 'os-create-server-ext' API extension
+ code so that we can return an API error code to the caller if they
+ request an invalid network.
+ """
+ if networks is None:
+ return
+
+ project_id = context.project_id
+ for (net_id, _i) in networks:
+ self.ipam.verify_subnet_exists(context, project_id, net_id)
+ if not self.q_conn.network_exists(project_id, net_id):
+ raise exception.NetworkNotFound(network_id=net_id)
diff --git a/nova/network/quantum/melange_connection.py b/nova/network/quantum/melange_connection.py
new file mode 100644
index 000000000..71ac9b5f1
--- /dev/null
+++ b/nova/network/quantum/melange_connection.py
@@ -0,0 +1,141 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 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 httplib
+import socket
+import urllib
+import json
+
+from nova import flags
+
+
+FLAGS = flags.FLAGS
+
+flags.DEFINE_string('melange_host',
+ '127.0.0.1',
+ 'HOST for connecting to melange')
+
+flags.DEFINE_string('melange_port',
+ '9898',
+ 'PORT for connecting to melange')
+
+json_content_type = {'Content-type': "application/json"}
+
+
+# FIXME(danwent): talk to the Melange folks about creating a
+# client lib that we can import as a library, instead of
+# have to have all of the client code in here.
+class MelangeConnection(object):
+
+ def __init__(self, host=None, port=None, use_ssl=False):
+ if host is None:
+ host = FLAGS.melange_host
+ if port is None:
+ port = int(FLAGS.melange_port)
+ self.host = host
+ self.port = port
+ self.use_ssl = use_ssl
+ self.version = "v0.1"
+
+ def get(self, path, params=None, headers=None):
+ return self.do_request("GET", path, params=params, headers=headers)
+
+ def post(self, path, body=None, headers=None):
+ return self.do_request("POST", path, body=body, headers=headers)
+
+ def delete(self, path, headers=None):
+ return self.do_request("DELETE", path, headers=headers)
+
+ def _get_connection(self):
+ if self.use_ssl:
+ return httplib.HTTPSConnection(self.host, self.port)
+ else:
+ return httplib.HTTPConnection(self.host, self.port)
+
+ def do_request(self, method, path, body=None, headers=None, params=None):
+ headers = headers or {}
+ params = params or {}
+
+ url = "/%s/%s.json" % (self.version, path)
+ if params:
+ url += "?%s" % urllib.urlencode(params)
+ try:
+ connection = self._get_connection()
+ connection.request(method, url, body, headers)
+ response = connection.getresponse()
+ response_str = response.read()
+ if response.status < 400:
+ return response_str
+ raise Exception(_("Server returned error: %s" % response_str))
+ except (socket.error, IOError), e:
+ raise Exception(_("Unable to connect to "
+ "server. Got error: %s" % e))
+
+ def allocate_ip(self, network_id, vif_id,
+ project_id=None, mac_address=None):
+ tenant_scope = "/tenants/%s" % project_id if project_id else ""
+ request_body = (json.dumps(dict(network=dict(mac_address=mac_address,
+ tenant_id=project_id)))
+ if mac_address else None)
+ url = ("ipam%(tenant_scope)s/networks/%(network_id)s/"
+ "interfaces/%(vif_id)s/ip_allocations" % locals())
+ response = self.post(url, body=request_body,
+ headers=json_content_type)
+ return json.loads(response)['ip_addresses']
+
+ def create_block(self, network_id, cidr,
+ project_id=None, dns1=None, dns2=None):
+ tenant_scope = "/tenants/%s" % project_id if project_id else ""
+
+ url = "ipam%(tenant_scope)s/ip_blocks" % locals()
+
+ req_params = dict(ip_block=dict(cidr=cidr, network_id=network_id,
+ type='private', dns1=dns1, dns2=dns2))
+ self.post(url, body=json.dumps(req_params),
+ headers=json_content_type)
+
+ def delete_block(self, block_id, project_id=None):
+ tenant_scope = "/tenants/%s" % project_id if project_id else ""
+
+ url = "ipam%(tenant_scope)s/ip_blocks/%(block_id)s" % locals()
+
+ self.delete(url, headers=json_content_type)
+
+ def get_blocks(self, project_id=None):
+ tenant_scope = "/tenants/%s" % project_id if project_id else ""
+
+ url = "ipam%(tenant_scope)s/ip_blocks" % locals()
+
+ response = self.get(url, headers=json_content_type)
+ return json.loads(response)
+
+ def get_allocated_ips(self, network_id, vif_id, project_id=None):
+ tenant_scope = "/tenants/%s" % project_id if project_id else ""
+
+ url = ("ipam%(tenant_scope)s/networks/%(network_id)s/"
+ "interfaces/%(vif_id)s/ip_allocations" % locals())
+
+ response = self.get(url, headers=json_content_type)
+ return json.loads(response)['ip_addresses']
+
+ def deallocate_ips(self, network_id, vif_id, project_id=None):
+ tenant_scope = "/tenants/%s" % project_id if project_id else ""
+
+ url = ("ipam%(tenant_scope)s/networks/%(network_id)s/"
+ "interfaces/%(vif_id)s/ip_allocations" % locals())
+
+ self.delete(url, headers=json_content_type)
diff --git a/nova/network/quantum/melange_ipam_lib.py b/nova/network/quantum/melange_ipam_lib.py
new file mode 100644
index 000000000..a0ac10fd3
--- /dev/null
+++ b/nova/network/quantum/melange_ipam_lib.py
@@ -0,0 +1,205 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Nicira Networks, Inc
+# 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 netaddr import IPNetwork
+
+from nova import db
+from nova import exception
+from nova import flags
+from nova import log as logging
+from nova.network.quantum import melange_connection
+
+
+LOG = logging.getLogger("nova.network.quantum.melange_ipam_lib")
+
+FLAGS = flags.FLAGS
+
+
+def get_ipam_lib(net_man):
+ return QuantumMelangeIPAMLib()
+
+
+class QuantumMelangeIPAMLib(object):
+ """Implements Quantum IP Address Management (IPAM) interface
+ using the Melange service, which is access using the Melange
+ web services API.
+ """
+
+ def __init__(self):
+ """Initialize class used to connect to Melange server"""
+ self.m_conn = melange_connection.MelangeConnection()
+
+ def create_subnet(self, context, label, project_id,
+ quantum_net_id, priority, cidr=None,
+ gateway_v6=None, cidr_v6=None,
+ dns1=None, dns2=None):
+ """Contact Melange and create a subnet for any non-NULL
+ IPv4 or IPv6 subnets.
+
+ Also create a entry in the Nova networks DB, but only
+ to store values not represented in Melange or to
+ temporarily provide compatibility with Nova code that
+ accesses IPAM data directly via the DB (e.g., nova-api)
+ """
+ tenant_id = project_id or FLAGS.quantum_default_tenant_id
+ if cidr:
+ self.m_conn.create_block(quantum_net_id, cidr,
+ project_id=tenant_id,
+ dns1=dns1, dns2=dns2)
+ if cidr_v6:
+ self.m_conn.create_block(quantum_net_id, cidr_v6,
+ project_id=tenant_id,
+ dns1=dns1, dns2=dns2)
+
+ net = {"uuid": quantum_net_id,
+ "project_id": project_id,
+ "priority": priority,
+ "label": label}
+ admin_context = context.elevated()
+ network = db.network_create_safe(admin_context, net)
+
+ def allocate_fixed_ip(self, context, project_id, quantum_net_id, vif_ref):
+ """Pass call to allocate fixed IP on to Melange"""
+ tenant_id = project_id or FLAGS.quantum_default_tenant_id
+ self.m_conn.allocate_ip(quantum_net_id,
+ vif_ref['uuid'], project_id=tenant_id,
+ mac_address=vif_ref['address'])
+
+ def get_network_id_by_cidr(self, context, cidr, project_id):
+ """Find the Quantum UUID associated with a IPv4 CIDR
+ address for the specified tenant.
+ """
+ tenant_id = project_id or FLAGS.quantum_default_tenant_id
+ all_blocks = self.m_conn.get_blocks(tenant_id)
+ for b in all_blocks['ip_blocks']:
+ if b['cidr'] == cidr:
+ return b['network_id']
+ raise exception.NotFound(_("No network found for cidr %s" % cidr))
+
+ def delete_subnets_by_net_id(self, context, net_id, project_id):
+ """Find Melange block associated with the Quantum UUID,
+ then tell Melange to delete that block.
+ """
+ admin_context = context.elevated()
+ tenant_id = project_id or FLAGS.quantum_default_tenant_id
+ all_blocks = self.m_conn.get_blocks(tenant_id)
+ for b in all_blocks['ip_blocks']:
+ if b['network_id'] == net_id:
+ self.m_conn.delete_block(b['id'], tenant_id)
+
+ network = db.network_get_by_uuid(admin_context, net_id)
+ db.network_delete_safe(context, network['id'])
+
+ def get_project_and_global_net_ids(self, context, project_id):
+ """Fetches all networks associated with this project, or
+ that are "global" (i.e., have no project set).
+ Returns list sorted by 'priority' (lowest integer value
+ is highest priority).
+ """
+ if project_id is None:
+ raise Exception(_("get_project_and_global_net_ids must be called"
+ " with a non-null project_id"))
+
+ admin_context = context.elevated()
+
+ # Decorate with priority
+ priority_nets = []
+ for tenant_id in (project_id, FLAGS.quantum_default_tenant_id):
+ blocks = self.m_conn.get_blocks(tenant_id)
+ for ip_block in blocks['ip_blocks']:
+ network_id = ip_block['network_id']
+ network = db.network_get_by_uuid(admin_context, network_id)
+ if network:
+ priority = network['priority']
+ priority_nets.append((priority, network_id, tenant_id))
+
+ # Sort by priority
+ priority_nets.sort()
+
+ # Undecorate
+ return [(network_id, tenant_id)
+ for priority, network_id, tenant_id in priority_nets]
+
+ def get_subnets_by_net_id(self, context, project_id, net_id):
+ """Returns information about the IPv4 and IPv6 subnets
+ associated with a Quantum Network UUID.
+ """
+
+ # FIXME(danwent): Melange actually returns the subnet info
+ # when we query for a particular interface. We may want to
+ # rework the ipam_manager python API to let us take advantage of
+ # this, as right now we have to get all blocks and cycle through
+ # them.
+ subnet_v4 = None
+ subnet_v6 = None
+ tenant_id = project_id or FLAGS.quantum_default_tenant_id
+ all_blocks = self.m_conn.get_blocks(tenant_id)
+ for b in all_blocks['ip_blocks']:
+ if b['network_id'] == net_id:
+ subnet = {'network_id': b['network_id'],
+ 'cidr': b['cidr'],
+ 'gateway': b['gateway'],
+ 'broadcast': b['broadcast'],
+ 'netmask': b['netmask'],
+ 'dns1': b['dns1'],
+ 'dns2': b['dns2']}
+
+ if IPNetwork(b['cidr']).version == 6:
+ subnet_v6 = subnet
+ else:
+ subnet_v4 = subnet
+ return (subnet_v4, subnet_v6)
+
+ def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id):
+ """Returns a list of IPv4 address strings associated with
+ the specified virtual interface.
+ """
+ return self._get_ips_by_interface(context, net_id, vif_id,
+ project_id, 4)
+
+ def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id):
+ """Returns a list of IPv6 address strings associated with
+ the specified virtual interface.
+ """
+ return self._get_ips_by_interface(context, net_id, vif_id,
+ project_id, 6)
+
+ def _get_ips_by_interface(self, context, net_id, vif_id, project_id,
+ ip_version):
+ """Helper method to fetch v4 or v6 addresses for a particular
+ virtual interface.
+ """
+ tenant_id = project_id or FLAGS.quantum_default_tenant_id
+ ip_list = self.m_conn.get_allocated_ips(net_id, vif_id, tenant_id)
+ return [ip['address'] for ip in ip_list
+ if IPNetwork(ip['address']).version == ip_version]
+
+ def verify_subnet_exists(self, context, project_id, quantum_net_id):
+ """Confirms that a subnet exists that is associated with the
+ specified Quantum Network UUID.
+ """
+ tenant_id = project_id or FLAGS.quantum_default_tenant_id
+ v4_subnet, v6_subnet = self.get_subnets_by_net_id(context, tenant_id,
+ quantum_net_id)
+ return v4_subnet is not None
+
+ def deallocate_ips_by_vif(self, context, project_id, net_id, vif_ref):
+ """Deallocate all fixed IPs associated with the specified
+ virtual interface.
+ """
+ tenant_id = project_id or FLAGS.quantum_default_tenant_id
+ self.m_conn.deallocate_ips(net_id, vif_ref['uuid'], tenant_id)
diff --git a/nova/network/quantum/nova_ipam_lib.py b/nova/network/quantum/nova_ipam_lib.py
new file mode 100644
index 000000000..21dee8f6a
--- /dev/null
+++ b/nova/network/quantum/nova_ipam_lib.py
@@ -0,0 +1,195 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Nicira Networks, Inc
+# 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 netaddr
+
+from nova import db
+from nova import exception
+from nova import flags
+from nova import ipv6
+from nova import log as logging
+from nova.network import manager
+from nova.network.quantum import melange_connection as melange
+from nova import utils
+
+
+LOG = logging.getLogger("nova.network.quantum.nova_ipam_lib")
+
+FLAGS = flags.FLAGS
+
+
+def get_ipam_lib(net_man):
+ return QuantumNovaIPAMLib(net_man)
+
+
+class QuantumNovaIPAMLib(object):
+ """Implements Quantum IP Address Management (IPAM) interface
+ using the local Nova database. This implementation is inline
+ with how IPAM is used by other NetworkManagers.
+ """
+
+ def __init__(self, net_manager):
+ """Holds a reference to the "parent" network manager, used
+ to take advantage of various FlatManager methods to avoid
+ code duplication.
+ """
+ self.net_manager = net_manager
+
+ def create_subnet(self, context, label, tenant_id,
+ quantum_net_id, priority, cidr=None,
+ gateway_v6=None, cidr_v6=None,
+ dns1=None, dns2=None):
+ """Re-use the basic FlatManager create_networks method to
+ initialize the networks and fixed_ips tables in Nova DB.
+
+ Also stores a few more fields in the networks table that
+ are needed by Quantum but not the FlatManager.
+ """
+ admin_context = context.elevated()
+ subnet_size = len(netaddr.IPNetwork(cidr))
+ networks = manager.FlatManager.create_networks(self.net_manager,
+ admin_context, label, cidr,
+ False, 1, subnet_size, cidr_v6,
+ gateway_v6, quantum_net_id, None, dns1, dns2)
+
+ if len(networks) != 1:
+ raise Exception(_("Error creating network entry"))
+
+ network = networks[0]
+ net = {"project_id": tenant_id,
+ "priority": priority,
+ "uuid": quantum_net_id}
+ db.network_update(admin_context, network['id'], net)
+
+ def get_network_id_by_cidr(self, context, cidr, project_id):
+ """ Grabs Quantum network UUID based on IPv4 CIDR. """
+ admin_context = context.elevated()
+ network = db.network_get_by_cidr(admin_context, cidr)
+ if not network:
+ raise Exception(_("No network with fixed_range = %s" %
+ fixed_range))
+ return network['uuid']
+
+ def delete_subnets_by_net_id(self, context, net_id, project_id):
+ """Deletes a network based on Quantum UUID. Uses FlatManager
+ delete_network to avoid duplication.
+ """
+ admin_context = context.elevated()
+ network = db.network_get_by_uuid(admin_context, net_id)
+ if not network:
+ raise Exception(_("No network with net_id = %s" % net_id))
+ manager.FlatManager.delete_network(self.net_manager,
+ admin_context, network['cidr'],
+ require_disassociated=False)
+
+ def get_project_and_global_net_ids(self, context, project_id):
+ """Fetches all networks associated with this project, or
+ that are "global" (i.e., have no project set).
+ Returns list sorted by 'priority'.
+ """
+ admin_context = context.elevated()
+ networks = db.project_get_networks(admin_context, project_id, False)
+ networks.extend(db.project_get_networks(admin_context, None, False))
+ id_priority_map = {}
+ net_list = []
+ for n in networks:
+ net_id = n['uuid']
+ net_list.append((net_id, n["project_id"]))
+ id_priority_map[net_id] = n['priority']
+ return sorted(net_list, key=lambda x: id_priority_map[x[0]])
+
+ def allocate_fixed_ip(self, context, tenant_id, quantum_net_id, vif_rec):
+ """Allocates a single fixed IPv4 address for a virtual interface."""
+ admin_context = context.elevated()
+ network = db.network_get_by_uuid(admin_context, quantum_net_id)
+ if network['cidr']:
+ address = db.fixed_ip_associate_pool(admin_context,
+ network['id'],
+ vif_rec['instance_id'])
+ values = {'allocated': True,
+ 'virtual_interface_id': vif_rec['id']}
+ db.fixed_ip_update(admin_context, address, values)
+
+ def get_subnets_by_net_id(self, context, tenant_id, net_id):
+ """Returns information about the IPv4 and IPv6 subnets
+ associated with a Quantum Network UUID.
+ """
+ n = db.network_get_by_uuid(context.elevated(), net_id)
+ subnet_data_v4 = {
+ 'network_id': n['uuid'],
+ 'cidr': n['cidr'],
+ 'gateway': n['gateway'],
+ 'broadcast': n['broadcast'],
+ 'netmask': n['netmask'],
+ 'dns1': n['dns1'],
+ 'dns2': n['dns2']}
+ subnet_data_v6 = {
+ 'network_id': n['uuid'],
+ 'cidr': n['cidr_v6'],
+ 'gateway': n['gateway_v6'],
+ 'broadcast': None,
+ 'netmask': None,
+ 'dns1': None,
+ 'dns2': None}
+ return (subnet_data_v4, subnet_data_v6)
+
+ def get_v4_ips_by_interface(self, context, net_id, vif_id, project_id):
+ """Returns a list of IPv4 address strings associated with
+ the specified virtual interface, based on the fixed_ips table.
+ """
+ vif_rec = db.virtual_interface_get_by_uuid(context, vif_id)
+ fixed_ips = db.fixed_ip_get_by_virtual_interface(context,
+ vif_rec['id'])
+ return [fixed_ip['address'] for fixed_ip in fixed_ips]
+
+ def get_v6_ips_by_interface(self, context, net_id, vif_id, project_id):
+ """Returns a list containing a single IPv6 address strings
+ associated with the specified virtual interface.
+ """
+ admin_context = context.elevated()
+ network = db.network_get_by_uuid(admin_context, net_id)
+ vif_rec = db.virtual_interface_get_by_uuid(context, vif_id)
+ if network['cidr_v6']:
+ ip = ipv6.to_global(network['cidr_v6'],
+ vif_rec['address'],
+ project_id)
+ return [ip]
+ return []
+
+ def verify_subnet_exists(self, context, tenant_id, quantum_net_id):
+ """Confirms that a subnet exists that is associated with the
+ specified Quantum Network UUID. Raises an exception if no
+ such subnet exists.
+ """
+ admin_context = context.elevated()
+ db.network_get_by_uuid(admin_context, quantum_net_id)
+
+ def deallocate_ips_by_vif(self, context, tenant_id, net_id, vif_ref):
+ """Deallocate all fixed IPs associated with the specified
+ virtual interface.
+ """
+ try:
+ admin_context = context.elevated()
+ fixed_ips = db.fixed_ip_get_by_virtual_interface(admin_context,
+ vif_ref['id'])
+ for fixed_ip in fixed_ips:
+ db.fixed_ip_update(admin_context, fixed_ip['address'],
+ {'allocated': False,
+ 'virtual_interface_id': None})
+ except exception.FixedIpNotFoundForInstance:
+ LOG.error(_('No fixed IPs to deallocate for vif %s' %
+ vif_ref['id']))
diff --git a/nova/network/quantum/quantum_connection.py b/nova/network/quantum/quantum_connection.py
new file mode 100644
index 000000000..21917653c
--- /dev/null
+++ b/nova/network/quantum/quantum_connection.py
@@ -0,0 +1,118 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Nicira Networks
+# 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 flags
+from nova import log as logging
+from nova.network.quantum import client as quantum_client
+from nova import utils
+
+
+LOG = logging.getLogger("nova.network.quantum.quantum_connection")
+FLAGS = flags.FLAGS
+
+flags.DEFINE_string('quantum_connection_host',
+ '127.0.0.1',
+ 'HOST for connecting to quantum')
+
+flags.DEFINE_string('quantum_connection_port',
+ '9696',
+ 'PORT for connecting to quantum')
+
+flags.DEFINE_string('quantum_default_tenant_id',
+ "default",
+ 'Default tenant id when creating quantum networks')
+
+
+class QuantumClientConnection(object):
+ """Abstracts connection to Quantum service into higher level
+ operations performed by the QuantumManager.
+
+ Separating this out as a class also let's us create a 'fake'
+ version of this class for unit tests.
+ """
+
+ def __init__(self):
+ """Initialize Quantum client class based on flags."""
+ self.client = quantum_client.Client(FLAGS.quantum_connection_host,
+ FLAGS.quantum_connection_port,
+ format="json",
+ logger=LOG)
+
+ def create_network(self, tenant_id, network_name):
+ """Create network using specified name, return Quantum
+ network UUID.
+ """
+ data = {'network': {'name': network_name}}
+ resdict = self.client.create_network(data, tenant=tenant_id)
+ return resdict["network"]["id"]
+
+ def delete_network(self, tenant_id, net_id):
+ """Deletes Quantum network with specified UUID."""
+ self.client.delete_network(net_id, tenant=tenant_id)
+
+ def network_exists(self, tenant_id, net_id):
+ """Determine if a Quantum network exists for the
+ specified tenant.
+ """
+ try:
+ self.client.show_network_details(net_id, tenant=tenant_id)
+ return True
+ except client.QuantumNotFoundException:
+ # Not really an error. Real errors will be propogated to caller
+ return False
+
+ def create_and_attach_port(self, tenant_id, net_id, interface_id):
+ """Creates a Quantum port on the specified network, sets
+ status to ACTIVE to enable traffic, and attaches the
+ vNIC with the specified interface-id.
+ """
+ LOG.debug(_("Connecting interface %(interface_id)s to "
+ "net %(net_id)s for %(tenant_id)s" % locals()))
+ port_data = {'port': {'state': 'ACTIVE'}}
+ resdict = self.client.create_port(net_id, port_data, tenant=tenant_id)
+ port_id = resdict["port"]["id"]
+
+ attach_data = {'attachment': {'id': interface_id}}
+ self.client.attach_resource(net_id, port_id, attach_data,
+ tenant=tenant_id)
+
+ def detach_and_delete_port(self, tenant_id, net_id, port_id):
+ """Detach and delete the specified Quantum port."""
+ LOG.debug(_("Deleting port %(port_id)s on net %(net_id)s"
+ " for %(tenant_id)s" % locals()))
+
+ self.client.detach_resource(net_id, port_id, tenant=tenant_id)
+ self.client.delete_port(net_id, port_id, tenant=tenant_id)
+
+ def get_port_by_attachment(self, tenant_id, attachment_id):
+ """Given a tenant, search for the Quantum network and port
+ UUID that has the specified interface-id attachment.
+ """
+ # FIXME(danwent): this will be inefficient until the Quantum
+ # API implements querying a port by the interface-id
+ net_list_resdict = self.client.list_networks(tenant=tenant_id)
+ for n in net_list_resdict["networks"]:
+ net_id = n['id']
+ port_list_resdict = self.client.list_ports(net_id,
+ tenant=tenant_id)
+ for p in port_list_resdict["ports"]:
+ port_id = p["id"]
+ port_get_resdict = self.client.show_port_attachment(net_id,
+ port_id, tenant=tenant_id)
+ if attachment_id == port_get_resdict["attachment"]["id"]:
+ return (net_id, port_id)
+ return (None, None)
diff --git a/nova/scheduler/abstract_scheduler.py b/nova/scheduler/abstract_scheduler.py
index 7f17b642f..6e8c7d715 100644
--- a/nova/scheduler/abstract_scheduler.py
+++ b/nova/scheduler/abstract_scheduler.py
@@ -20,8 +20,8 @@ customize the behavior: filter_hosts() and weigh_hosts(). The default
behavior is to simply select all hosts and weight them the same.
"""
-import operator
import json
+import operator
import M2Crypto
@@ -110,7 +110,6 @@ class AbstractScheduler(driver.Scheduler):
flavor_id = instance_type['flavorid']
reservation_id = instance_properties['reservation_id']
files = kwargs['injected_files']
- ipgroup = None # Not supported in OS API ... yet
child_zone = zone_info['child_zone']
child_blob = zone_info['child_blob']
zone = db.zone_get(context, child_zone)
@@ -124,8 +123,17 @@ class AbstractScheduler(driver.Scheduler):
except novaclient_exceptions.BadRequest, e:
raise exception.NotAuthorized(_("Bad credentials attempting "
"to talk to zone at %(url)s.") % locals())
- nova.servers.create(name, image_ref, flavor_id, ipgroup, meta, files,
- child_blob, reservation_id=reservation_id)
+ # NOTE(Vek): Novaclient has two different calling conventions
+ # for this call, depending on whether you're using
+ # 1.0 or 1.1 API: in 1.0, there's an ipgroups
+ # argument after flavor_id which isn't present in
+ # 1.1. To work around this, all the extra
+ # arguments are passed as keyword arguments
+ # (there's a reasonable default for ipgroups in the
+ # novaclient call).
+ nova.servers.create(name, image_ref, flavor_id,
+ meta=meta, files=files, zone_blob=child_blob,
+ reservation_id=reservation_id)
def _provision_resource_from_blob(self, context, build_plan_item,
instance_id, request_spec, kwargs):
@@ -269,9 +277,6 @@ class AbstractScheduler(driver.Scheduler):
# Filter local hosts based on requirements ...
filtered_hosts = self.filter_hosts(topic, request_spec,
unfiltered_hosts)
- if not filtered_hosts:
- LOG.warn(_("No hosts available"))
- return []
# weigh the selected hosts.
# weighted_hosts = [{weight=weight, hostname=hostname,
diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py
index 55cea5f8f..719437b73 100644
--- a/nova/scheduler/api.py
+++ b/nova/scheduler/api.py
@@ -103,22 +103,6 @@ def update_service_capabilities(context, service_name, host, capabilities):
return rpc.fanout_cast(context, 'scheduler', kwargs)
-def _wrap_method(function, self):
- """Wrap method to supply self."""
- def _wrap(*args, **kwargs):
- return function(self, *args, **kwargs)
- return _wrap
-
-
-def _process(func, zone):
- """Worker stub for green thread pool. Give the worker
- an authenticated nova client and zone info."""
- nova = novaclient.Client(zone.username, zone.password, None,
- zone.api_url)
- nova.authenticate()
- return func(nova, zone)
-
-
def call_zone_method(context, method_name, errors_to_ignore=None,
novaclient_collection_name='zones', zones=None,
*args, **kwargs):
@@ -166,6 +150,32 @@ def child_zone_helper(zone_list, func):
For example, if you are calling server.pause(), the list will
be whatever the response from server.pause() is. One entry
per child zone called."""
+
+ def _wrap_method(function, arg1):
+ """Wrap method to supply an argument."""
+ def _wrap(*args, **kwargs):
+ return function(arg1, *args, **kwargs)
+ return _wrap
+
+ def _process(func, zone):
+ """Worker stub for green thread pool. Give the worker
+ an authenticated nova client and zone info."""
+ try:
+ nova = novaclient.Client(zone.username, zone.password, None,
+ zone.api_url)
+ nova.authenticate()
+ except novaclient_exceptions.BadRequest, e:
+ url = zone.api_url
+ LOG.warn(_("Failed request to zone; URL=%(url)s: %(e)s")
+ % locals())
+ # This is being returned instead of raised, so that when
+ # results are processed in unmarshal_result() after the
+ # greenpool.imap completes, the exception can be raised
+ # there if no other zones had a response.
+ return exception.ZoneRequestError()
+ else:
+ return func(nova, zone)
+
green_pool = greenpool.GreenPool()
return [result for result in green_pool.imap(
_wrap_method(_process, func), zone_list)]
@@ -260,6 +270,8 @@ class reroute_compute(object):
if not FLAGS.enable_zone_routing:
raise exception.InstanceNotFound(instance_id=item_uuid)
+ self.item_uuid = item_uuid
+
zones = db.zone_get_all(context)
if not zones:
raise exception.InstanceNotFound(instance_id=item_uuid)
@@ -342,9 +354,13 @@ class reroute_compute(object):
dict {'server':{k:v}}. Others may return a list of them, like
{'servers':[{k,v}]}"""
reduced_response = []
+ found_exception = None
for zone_response in zone_responses:
if not zone_response:
continue
+ if isinstance(zone_response, BaseException):
+ found_exception = zone_response
+ continue
server = zone_response.__dict__
@@ -355,7 +371,9 @@ class reroute_compute(object):
reduced_response.append(dict(server=server))
if reduced_response:
return reduced_response[0] # first for now.
- return {}
+ elif found_exception:
+ raise found_exception
+ raise exception.InstanceNotFound(instance_id=self.item_uuid)
def redirect_handler(f):
diff --git a/nova/scheduler/base_scheduler.py b/nova/scheduler/base_scheduler.py
index 35e5af035..e8629ca92 100644
--- a/nova/scheduler/base_scheduler.py
+++ b/nova/scheduler/base_scheduler.py
@@ -27,6 +27,8 @@ from nova.scheduler import abstract_scheduler
from nova.scheduler import host_filter
FLAGS = flags.FLAGS
+flags.DEFINE_boolean('spread_first', False,
+ 'Use a spread-first zone scheduler strategy')
LOG = logging.getLogger('nova.scheduler.base_scheduler')
@@ -55,5 +57,22 @@ class BaseScheduler(abstract_scheduler.AbstractScheduler):
scheduling objectives
"""
# NOTE(sirp): The default logic is the same as the NoopCostFunction
- return [dict(weight=1, hostname=hostname, capabilities=capabilities)
- for hostname, capabilities in hosts]
+ hosts = [dict(weight=1, hostname=hostname, capabilities=capabilities)
+ for hostname, capabilities in hosts]
+
+ # NOTE(Vek): What we actually need to return is enough hosts
+ # for all the instances!
+ num_instances = request_spec.get('num_instances', 1)
+ instances = []
+ while num_instances > len(hosts):
+ instances.extend(hosts)
+ num_instances -= len(hosts)
+ if num_instances > 0:
+ instances.extend(hosts[:num_instances])
+
+ # Adjust the weights for a spread-first strategy
+ if FLAGS.spread_first:
+ for i, host in enumerate(hosts):
+ host['weight'] = i + 1
+
+ return instances
diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py
index ba8fb925e..078b72d67 100644
--- a/nova/tests/api/openstack/contrib/test_createserverext.py
+++ b/nova/tests/api/openstack/contrib/test_createserverext.py
@@ -112,6 +112,8 @@ class CreateserverextTest(test.TestCase):
return [{'id': '1234', 'display_name': 'fakeinstance',
'uuid': FAKE_UUID,
+ 'user_id': 'fake',
+ 'project_id': 'fake',
'created_at': "",
'updated_at': ""}]
diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py
index 642f2b841..0744f0a11 100644
--- a/nova/tests/api/openstack/contrib/test_floating_ips.py
+++ b/nova/tests/api/openstack/contrib/test_floating_ips.py
@@ -212,11 +212,45 @@ class FloatingIpTest(test.TestCase):
"fixed_ip": None}
self.assertEqual(ip, expected)
- def test_floating_ip_release(self):
+ def test_floating_ip_release_associated(self):
+ self.disassociated = False
+
+ def get_floating_ip(ignore, context, id):
+ return {'id': 1, 'address': '10.10.10.10',
+ 'fixed_ip': {'id': 1}}
+
+ def disassociate(ignore, context, floating_address):
+ self.disassociated = True
+
+ self.stubs.Set(network.api.API, "get_floating_ip",
+ get_floating_ip)
+ self.stubs.Set(network.api.API, "disassociate_floating_ip",
+ disassociate)
+ req = webob.Request.blank('/v1.1/123/os-floating-ips/1')
+ req.method = 'DELETE'
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+ self.assertTrue(self.disassociated)
+
+ def test_floating_ip_release_disassociated(self):
+ self.disassociated = False
+
+ def fake_get_floating_ip(ignore, context, id):
+ return {'id': 1, 'address': '10.10.10.10',
+ 'fixed_ip': None}
+
+ def fake_disassociate(ignore, context, floating_address):
+ self.disassociated = True
+
+ self.stubs.Set(network.api.API, "get_floating_ip",
+ fake_get_floating_ip)
+ self.stubs.Set(network.api.API, "disassociate_floating_ip",
+ fake_disassociate)
req = webob.Request.blank('/v1.1/123/os-floating-ips/1')
req.method = 'DELETE'
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 202)
+ self.assertFalse(self.disassociated)
def test_add_floating_ip_to_instance(self):
self.stubs.Set(network.api.API, "associate_floating_ip",
@@ -289,8 +323,45 @@ class FloatingIpTest(test.TestCase):
self.assertEqual(resp.status_int, 202)
self.assertTrue(self.disassociated)
- def test_remove_floating_ip_from_instance(self):
- body = dict(removeFloatingIp=dict(address='11.0.0.1'))
+ def test_remove_associated_floating_ip_from_instance(self):
+ self.disassociated = False
+
+ def fake_get_floating_ip_by_ip(ignore, context, ip):
+ return {'id': 1, 'address': '10.10.10.10',
+ 'fixed_ip': {'id': 1}}
+
+ def fake_disassociate(ignore, context, floating_address):
+ self.disassociated = True
+
+ self.stubs.Set(network.api.API, "get_floating_ip_by_ip",
+ fake_get_floating_ip_by_ip)
+ self.stubs.Set(network.api.API, "disassociate_floating_ip",
+ fake_disassociate)
+ body = dict(removeFloatingIp=dict(address='10.10.10.10'))
+ req = webob.Request.blank('/v1.1/123/servers/test_inst/action')
+ req.method = "POST"
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ resp = req.get_response(fakes.wsgi_app())
+ self.assertEqual(resp.status_int, 202)
+ self.assertTrue(self.disassociated)
+
+ def test_remove_disassociated_floating_ip_from_instance(self):
+ self.disassociated = False
+
+ def fake_get_floating_ip_by_ip(ignore, context, ip):
+ return {'id': 1, 'address': '10.10.10.10',
+ 'fixed_ip': None}
+
+ def fake_disassociate(ignore, context, floating_address):
+ self.disassociated = True
+
+ self.stubs.Set(network.api.API, "get_floating_ip_by_ip",
+ fake_get_floating_ip_by_ip)
+ self.stubs.Set(network.api.API, "disassociate_floating_ip",
+ fake_disassociate)
+ body = dict(removeFloatingIp=dict(address='10.10.10.10'))
req = webob.Request.blank('/v1.1/123/servers/test_inst/action')
req.method = "POST"
req.body = json.dumps(body)
@@ -298,6 +369,7 @@ class FloatingIpTest(test.TestCase):
resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 202)
+ self.assertFalse(self.disassociated)
def test_bad_address_param_in_remove_floating_ip(self):
body = dict(removeFloatingIp=dict(badparam='11.0.0.1'))
diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py
index 44681d395..098b1e284 100644
--- a/nova/tests/api/openstack/fakes.py
+++ b/nova/tests/api/openstack/fakes.py
@@ -124,7 +124,7 @@ def stub_out_key_pair_funcs(stubs, have_key_pair=True):
def stub_out_image_service(stubs):
- def fake_get_image_service(image_href):
+ def fake_get_image_service(context, image_href):
return (nova.image.fake.FakeImageService(), image_href)
stubs.Set(nova.image, 'get_image_service', fake_get_image_service)
stubs.Set(nova.image, 'get_default_image_service',
diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py
index 6448e9986..6890e0e9e 100644
--- a/nova/tests/api/openstack/test_images.py
+++ b/nova/tests/api/openstack/test_images.py
@@ -365,8 +365,10 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
{'id': 125, 'name': 'saving snapshot'},
{'id': 126, 'name': 'active snapshot'},
{'id': 127, 'name': 'killed snapshot'},
- {'id': 128, 'name': 'active UUID snapshot'},
- {'id': 130, 'name': None}]
+ {'id': 128, 'name': 'deleted snapshot'},
+ {'id': 129, 'name': 'pending_delete snapshot'},
+ {'id': 130, 'name': 'active UUID snapshot'},
+ {'id': 132, 'name': None}]
self.assertDictListMatch(response_list, expected)
@@ -408,7 +410,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
"name": "queued snapshot",
"updated": self.NOW_API_FORMAT,
"created": self.NOW_API_FORMAT,
- "status": "QUEUED",
+ "status": "SAVING",
"progress": 0,
'server': {
'id': '42',
@@ -459,7 +461,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
self.assertEqual(expected_image.toxml(), actual_image.toxml())
def test_get_image_xml_no_name(self):
- request = webob.Request.blank('/v1.0/images/130')
+ request = webob.Request.blank('/v1.0/images/132')
request.accept = "application/xml"
response = request.get_response(fakes.wsgi_app())
@@ -467,7 +469,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
expected_now = self.NOW_API_FORMAT
expected_image = minidom.parseString("""
- <image id="130"
+ <image id="132"
name="None"
updated="%(expected_now)s"
created="%(expected_now)s"
@@ -604,7 +606,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
'name': 'queued snapshot',
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
- 'status': 'QUEUED',
+ 'status': 'SAVING',
'progress': 0,
},
{
@@ -628,11 +630,27 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
'name': 'killed snapshot',
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
- 'status': 'FAILED',
+ 'status': 'ERROR',
'progress': 0,
},
{
'id': 128,
+ 'name': 'deleted snapshot',
+ 'updated': self.NOW_API_FORMAT,
+ 'created': self.NOW_API_FORMAT,
+ 'status': 'DELETED',
+ 'progress': 0,
+ },
+ {
+ 'id': 129,
+ 'name': 'pending_delete snapshot',
+ 'updated': self.NOW_API_FORMAT,
+ 'created': self.NOW_API_FORMAT,
+ 'status': 'DELETED',
+ 'progress': 0,
+ },
+ {
+ 'id': 130,
'name': 'active UUID snapshot',
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
@@ -640,7 +658,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
'progress': 100,
},
{
- 'id': 130,
+ 'id': 132,
'name': None,
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
@@ -687,7 +705,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
},
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
- 'status': 'QUEUED',
+ 'status': 'SAVING',
'progress': 0,
'server': {
'id': '42',
@@ -780,7 +798,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
},
'updated': self.NOW_API_FORMAT,
'created': self.NOW_API_FORMAT,
- 'status': 'FAILED',
+ 'status': 'ERROR',
'progress': 0,
'server': {
'id': '42',
@@ -803,7 +821,69 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
}],
},
{
- 'id': '128',
+ 'id': 128,
+ 'name': 'deleted snapshot',
+ 'metadata': {
+ u'instance_ref': u'http://localhost/v1.1/servers/42',
+ u'user_id': u'fake',
+ },
+ 'updated': self.NOW_API_FORMAT,
+ 'created': self.NOW_API_FORMAT,
+ 'status': 'DELETED',
+ 'progress': 0,
+ 'server': {
+ 'id': 42,
+ "links": [{
+ "rel": "self",
+ "href": server_href,
+ },
+ {
+ "rel": "bookmark",
+ "href": server_bookmark,
+ }],
+ },
+ "links": [{
+ "rel": "self",
+ "href": "http://localhost/v1.1/fake/images/128",
+ },
+ {
+ "rel": "bookmark",
+ "href": "http://localhost/fake/images/128",
+ }],
+ },
+ {
+ 'id': 129,
+ 'name': 'pending_delete snapshot',
+ 'metadata': {
+ u'instance_ref': u'http://localhost/v1.1/servers/42',
+ u'user_id': u'fake',
+ },
+ 'updated': self.NOW_API_FORMAT,
+ 'created': self.NOW_API_FORMAT,
+ 'status': 'DELETED',
+ 'progress': 0,
+ 'server': {
+ 'id': 42,
+ "links": [{
+ "rel": "self",
+ "href": server_href,
+ },
+ {
+ "rel": "bookmark",
+ "href": server_bookmark,
+ }],
+ },
+ "links": [{
+ "rel": "self",
+ "href": "http://localhost/v1.1/fake/images/129",
+ },
+ {
+ "rel": "bookmark",
+ "href": "http://localhost/fake/images/129",
+ }],
+ },
+ {
+ 'id': '130',
'name': 'active UUID snapshot',
'metadata': {
u'instance_ref': u'http://localhost/v1.1/servers/cb8360cb',
@@ -826,15 +906,15 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
},
"links": [{
"rel": "self",
- "href": "http://localhost/v1.1/fake/images/128",
+ "href": "http://localhost/v1.1/fake/images/130",
},
{
"rel": "bookmark",
- "href": "http://localhost/fake/images/128",
+ "href": "http://localhost/fake/images/130",
}],
},
{
- 'id': '130',
+ 'id': 132,
'name': None,
'metadata': {},
'updated': self.NOW_API_FORMAT,
@@ -843,11 +923,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
'progress': 100,
"links": [{
"rel": "self",
- "href": "http://localhost/v1.1/fake/images/130",
+ "href": "http://localhost/v1.1/fake/images/132",
},
{
"rel": "bookmark",
- "href": "http://localhost/fake/images/130",
+ "href": "http://localhost/fake/images/132",
}],
},
]
@@ -1076,7 +1156,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
"""We should return a 404 if we request an image that doesn't belong
to us
"""
- req = webob.Request.blank('/v1.0/images/129')
+ req = webob.Request.blank('/v1.0/images/131')
res = req.get_response(fakes.wsgi_app())
self.assertEqual(res.status_int, 404)
@@ -1144,13 +1224,15 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
# Snapshots for User 1
server_ref = 'http://localhost/v1.1/servers/42'
snapshot_properties = {'instance_ref': server_ref, 'user_id': 'fake'}
- for status in ('queued', 'saving', 'active', 'killed'):
+ statuses = ('queued', 'saving', 'active','killed',
+ 'deleted', 'pending_delete')
+ for status in statuses:
add_fixture(id=image_id, name='%s snapshot' % status,
is_public=False, status=status,
properties=snapshot_properties)
image_id += 1
- # Snapshot for User 1 with uuid (128)
+ # Snapshot for User 1 with uuid (130)
server_ref = 'http://localhost/v1.1/servers/cb8360cb'
snapshot_props = {'instance_ref': server_ref, 'user_id': 'fake'}
add_fixture(id=image_id, name='active UUID snapshot',
@@ -1159,7 +1241,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase):
image_id += 1
- # Snapshot for User 2 (129)
+ # Snapshot for User 2 (131)
other_snapshot_properties = {'instance_id': '43', 'user_id': 'other'}
add_fixture(id=image_id, name='someone elses snapshot',
is_public=False, status='active',
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 1591ea56c..f0a1c5ce5 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -347,6 +347,8 @@ class ServersTest(test.TestCase):
"server": {
"id": 1,
"uuid": FAKE_UUID,
+ "user_id": "fake",
+ "tenant_id": "fake",
"updated": "2010-11-11T11:00:00Z",
"created": "2010-10-10T12:00:00Z",
"progress": 0,
@@ -446,6 +448,8 @@ class ServersTest(test.TestCase):
expected = minidom.parseString("""
<server id="1"
uuid="%(expected_uuid)s"
+ userId="fake"
+ tenantId="fake"
xmlns="http://docs.openstack.org/compute/api/v1.1"
xmlns:atom="http://www.w3.org/2005/Atom"
name="server1"
@@ -515,6 +519,8 @@ class ServersTest(test.TestCase):
"server": {
"id": 1,
"uuid": FAKE_UUID,
+ "user_id": "fake",
+ "tenant_id": "fake",
"updated": "2010-11-11T11:00:00Z",
"created": "2010-10-10T12:00:00Z",
"progress": 100,
@@ -610,6 +616,8 @@ class ServersTest(test.TestCase):
"server": {
"id": 1,
"uuid": FAKE_UUID,
+ "user_id": "fake",
+ "tenant_id": "fake",
"updated": "2010-11-11T11:00:00Z",
"created": "2010-10-10T12:00:00Z",
"progress": 100,
@@ -1199,6 +1207,26 @@ class ServersTest(test.TestCase):
self.assertEqual(len(servers), 1)
self.assertEqual(servers[0]['id'], 100)
+ def test_tenant_id_filter_converts_to_project_id_for_admin(self):
+ def fake_get_all(context, filters=None):
+ self.assertNotEqual(filters, None)
+ self.assertEqual(filters['project_id'], 'faketenant')
+ self.assertFalse(filters.get('tenant_id'))
+ return [stub_instance(100)]
+
+ self.stubs.Set(nova.db.api, 'instance_get_all_by_filters',
+ fake_get_all)
+ self.flags(allow_admin_api=True)
+
+ req = webob.Request.blank('/v1.1/fake/servers?tenant_id=faketenant')
+ # Use admin context
+ context = nova.context.RequestContext('testuser', 'testproject',
+ is_admin=True)
+ res = req.get_response(fakes.wsgi_app(fake_auth_context=context))
+ res_dict = json.loads(res.body)
+ # Failure in fake_get_all returns non 200 status code
+ self.assertEqual(res.status_int, 200)
+
def test_get_servers_allows_flavor_v1_1(self):
def fake_get_all(compute_self, context, search_opts=None):
self.assertNotEqual(search_opts, None)
@@ -1455,6 +1483,8 @@ class ServersTest(test.TestCase):
'access_ip_v4': '1.2.3.4',
'access_ip_v6': 'fead::1234',
'image_ref': image_ref,
+ '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),
"config_drive": self.config_drive,
@@ -3103,7 +3133,7 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase):
"name": "new-server-test",
"imageRef": "1",
"flavorRef": "1",
- "networks": []
+ "networks": [],
}}
self.assertEquals(request['body'], expected)
@@ -3330,6 +3360,8 @@ class TestServerInstanceCreation(test.TestCase):
self.injected_files = None
return [{'id': '1234', 'display_name': 'fakeinstance',
+ 'user_id': 'fake',
+ 'project_id': 'fake',
'uuid': FAKE_UUID}]
def set_admin_password(self, *args, **kwargs):
@@ -3583,10 +3615,14 @@ class TestGetKernelRamdiskFromImage(test.TestCase):
self.assertRaises(exception.NotFound, self._get_k_r, image_meta)
def test_ami_no_ramdisk(self):
- """If an ami is missing a ramdisk it should raise NotFound"""
+ """If an ami is missing a ramdisk, return kernel ID and None for
+ ramdisk ID
+ """
image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami',
'properties': {'kernel_id': 1}}
- self.assertRaises(exception.NotFound, self._get_k_r, image_meta)
+ kernel_id, ramdisk_id = self._get_k_r(image_meta)
+ self.assertEqual(kernel_id, 1)
+ self.assertEqual(ramdisk_id, None)
def test_ami_kernel_ramdisk_present(self):
"""Return IDs if both kernel and ramdisk are present"""
@@ -3621,8 +3657,8 @@ class ServersViewBuilderV11Test(test.TestCase):
"created_at": created_at,
"updated_at": updated_at,
"admin_pass": "",
- "user_id": "",
- "project_id": "",
+ "user_id": "fake",
+ "project_id": "fake",
"image_ref": "5",
"kernel_id": "",
"ramdisk_id": "",
@@ -3647,7 +3683,6 @@ class ServersViewBuilderV11Test(test.TestCase):
"terminated_at": utils.utcnow(),
"availability_zone": "",
"display_name": "test_server",
- "display_description": "",
"locked": False,
"metadata": [],
"accessIPv4": "1.2.3.4",
@@ -3680,7 +3715,6 @@ class ServersViewBuilderV11Test(test.TestCase):
"id": 1,
"uuid": self.instance['uuid'],
"name": "test_server",
- "key_name": '',
"links": [
{
"rel": "self",
@@ -3691,7 +3725,6 @@ class ServersViewBuilderV11Test(test.TestCase):
"href": "http://localhost/servers/1",
},
],
- "config_drive": None,
}
}
@@ -3704,8 +3737,6 @@ class ServersViewBuilderV11Test(test.TestCase):
"id": 1,
"uuid": self.instance['uuid'],
"name": "test_server",
- "key_name": '',
- "config_drive": None,
"links": [
{
"rel": "self",
@@ -3730,6 +3761,8 @@ class ServersViewBuilderV11Test(test.TestCase):
"server": {
"id": 1,
"uuid": self.instance['uuid'],
+ "user_id": "fake",
+ "tenant_id": "fake",
"updated": "2010-11-11T11:00:00Z",
"created": "2010-10-10T12:00:00Z",
"progress": 0,
@@ -3785,6 +3818,8 @@ class ServersViewBuilderV11Test(test.TestCase):
"server": {
"id": 1,
"uuid": self.instance['uuid'],
+ "user_id": "fake",
+ "tenant_id": "fake",
"updated": "2010-11-11T11:00:00Z",
"created": "2010-10-10T12:00:00Z",
"progress": 100,
@@ -3841,6 +3876,8 @@ class ServersViewBuilderV11Test(test.TestCase):
"server": {
"id": 1,
"uuid": self.instance['uuid'],
+ "user_id": "fake",
+ "tenant_id": "fake",
"updated": "2010-11-11T11:00:00Z",
"created": "2010-10-10T12:00:00Z",
"progress": 0,
@@ -3897,6 +3934,8 @@ class ServersViewBuilderV11Test(test.TestCase):
"server": {
"id": 1,
"uuid": self.instance['uuid'],
+ "user_id": "fake",
+ "tenant_id": "fake",
"updated": "2010-11-11T11:00:00Z",
"created": "2010-10-10T12:00:00Z",
"progress": 0,
@@ -3956,6 +3995,8 @@ class ServersViewBuilderV11Test(test.TestCase):
"server": {
"id": 1,
"uuid": self.instance['uuid'],
+ "user_id": "fake",
+ "tenant_id": "fake",
"updated": "2010-11-11T11:00:00Z",
"created": "2010-10-10T12:00:00Z",
"progress": 0,
@@ -4024,6 +4065,8 @@ class ServerXMLSerializationTest(test.TestCase):
fixture = {
"server": {
"id": 1,
+ "user_id": "fake",
+ "tenant_id": "fake",
"uuid": FAKE_UUID,
'created': self.TIMESTAMP,
'updated': self.TIMESTAMP,
@@ -4161,6 +4204,8 @@ class ServerXMLSerializationTest(test.TestCase):
"server": {
"id": 1,
"uuid": FAKE_UUID,
+ "user_id": "fake",
+ "tenant_id": "fake",
'created': self.TIMESTAMP,
'updated': self.TIMESTAMP,
"progress": 0,
@@ -4361,6 +4406,8 @@ class ServerXMLSerializationTest(test.TestCase):
{
"id": 1,
"uuid": FAKE_UUID,
+ "user_id": "fake",
+ "tenant_id": "fake",
'created': self.TIMESTAMP,
'updated': self.TIMESTAMP,
"progress": 0,
@@ -4416,6 +4463,8 @@ class ServerXMLSerializationTest(test.TestCase):
{
"id": 2,
"uuid": FAKE_UUID,
+ "user_id": 'fake',
+ "tenant_id": 'fake',
'created': self.TIMESTAMP,
'updated': self.TIMESTAMP,
"progress": 100,
@@ -4535,6 +4584,8 @@ class ServerXMLSerializationTest(test.TestCase):
fixture = {
"server": {
"id": 1,
+ "user_id": "fake",
+ "tenant_id": "fake",
"uuid": FAKE_UUID,
'created': self.TIMESTAMP,
'updated': self.TIMESTAMP,
@@ -4671,6 +4722,8 @@ class ServerXMLSerializationTest(test.TestCase):
"server": {
"id": 1,
"uuid": FAKE_UUID,
+ "user_id": "fake",
+ "tenant_id": "fake",
'created': self.TIMESTAMP,
'updated': self.TIMESTAMP,
"progress": 0,
diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py
new file mode 100644
index 000000000..1ecb99b31
--- /dev/null
+++ b/nova/tests/fake_network.py
@@ -0,0 +1,164 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Rackspace
+# 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 db
+from nova import flags
+from nova import test
+from nova.network import manager as network_manager
+
+
+HOST = "testhost"
+FLAGS = flags.FLAGS
+
+
+class FakeModel(dict):
+ """Represent a model from the db"""
+ def __init__(self, *args, **kwargs):
+ self.update(kwargs)
+
+ def __getattr__(self, name):
+ return self[name]
+
+
+flavor = {'id': 0,
+ 'name': 'fake_flavor',
+ 'memory_mb': 2048,
+ 'vcpus': 2,
+ 'local_gb': 10,
+ 'flavor_id': 0,
+ 'swap': 0,
+ 'rxtx_quota': 0,
+ 'rxtx_cap': 3}
+
+
+def fake_network(network_id, ipv6=None):
+ if ipv6 is None:
+ ipv6 = FLAGS.use_ipv6
+ fake_network = {'id': network_id,
+ 'label': 'test%d' % network_id,
+ 'injected': False,
+ 'multi_host': False,
+ 'cidr': '192.168.%d.0/24' % network_id,
+ 'cidr_v6': None,
+ 'netmask': '255.255.255.0',
+ 'netmask_v6': None,
+ 'bridge': 'fake_br%d' % network_id,
+ 'bridge_interface': 'fake_eth%d' % network_id,
+ 'gateway': '192.168.%d.1' % network_id,
+ 'gateway_v6': None,
+ 'broadcast': '192.168.%d.255' % network_id,
+ 'dns1': '192.168.%d.3' % network_id,
+ 'dns2': '192.168.%d.4' % network_id,
+ 'vlan': None,
+ 'host': None,
+ 'project_id': 'fake_project',
+ 'vpn_public_address': '192.168.%d.2' % network_id}
+ if ipv6:
+ fake_network['cidr_v6'] = '2001:db8:0:%x::/64' % network_id
+ fake_network['gateway_v6'] = '2001:db8:0:%x::1' % network_id
+ fake_network['netmask_v6'] = '64'
+
+ return fake_network
+
+
+def vifs(n):
+ for x in xrange(n):
+ yield {'id': x,
+ 'address': 'DE:AD:BE:EF:00:%02x' % x,
+ 'uuid': '00000000-0000-0000-0000-00000000000000%02d' % x,
+ 'network_id': x,
+ 'network': FakeModel(**fake_network(x)),
+ 'instance_id': 0}
+
+
+def floating_ip_ids():
+ for i in xrange(99):
+ yield i
+
+
+def fixed_ip_ids():
+ for i in xrange(99):
+ yield i
+
+
+floating_ip_id = floating_ip_ids()
+fixed_ip_id = fixed_ip_ids()
+
+
+def next_fixed_ip(network_id, num_floating_ips=0):
+ next_id = fixed_ip_id.next()
+ f_ips = [FakeModel(**next_floating_ip(next_id))
+ for i in xrange(num_floating_ips)]
+ return {'id': next_id,
+ 'network_id': network_id,
+ 'address': '192.168.%d.1%02d' % (network_id, next_id),
+ 'instance_id': 0,
+ 'allocated': False,
+ # and since network_id and vif_id happen to be equivalent
+ 'virtual_interface_id': network_id,
+ 'floating_ips': f_ips}
+
+
+def next_floating_ip(fixed_ip_id):
+ next_id = floating_ip_id.next()
+ return {'id': next_id,
+ 'address': '10.10.10.1%02d' % next_id,
+ 'fixed_ip_id': fixed_ip_id,
+ 'project_id': None,
+ 'auto_assigned': False}
+
+
+def ipv4_like(ip, match_string):
+ ip = ip.split('.')
+ match_octets = match_string.split('.')
+
+ for i, octet in enumerate(match_octets):
+ if octet == '*':
+ continue
+ if octet != ip[i]:
+ return False
+ return True
+
+
+def fake_get_instance_nw_info(stubs, num_networks=1, ips_per_vif=2,
+ floating_ips_per_fixed_ip=0):
+ # stubs is the self.stubs from the test
+ # ips_per_vif is the number of ips each vif will have
+ # num_floating_ips is number of float ips for each fixed ip
+ network = network_manager.FlatManager(host=HOST)
+ network.db = db
+
+ # reset the fixed and floating ip generators
+ global floating_ip_id, fixed_ip_id
+ floating_ip_id = floating_ip_ids()
+ fixed_ip_id = fixed_ip_ids()
+
+ def fixed_ips_fake(*args, **kwargs):
+ return [next_fixed_ip(i, floating_ips_per_fixed_ip)
+ for i in xrange(num_networks) for j in xrange(ips_per_vif)]
+
+ def virtual_interfaces_fake(*args, **kwargs):
+ return [vif for vif in vifs(num_networks)]
+
+ def instance_type_fake(*args, **kwargs):
+ return flavor
+
+ stubs.Set(db, 'fixed_ip_get_by_instance', fixed_ips_fake)
+ stubs.Set(db, 'virtual_interface_get_by_instance', virtual_interfaces_fake)
+ stubs.Set(db, 'instance_type_get', instance_type_fake)
+
+ return network.get_instance_nw_info(None, 0, 0, None)
diff --git a/nova/tests/glance/stubs.py b/nova/tests/glance/stubs.py
index f2a19f22d..6b74e671c 100644
--- a/nova/tests/glance/stubs.py
+++ b/nova/tests/glance/stubs.py
@@ -16,14 +16,14 @@
import StringIO
-import nova.image
+from nova.image import glance
def stubout_glance_client(stubs):
- def fake_get_glance_client(image_href):
+ def fake_get_glance_client(context, image_href):
image_id = int(str(image_href).split('/')[-1])
return (FakeGlance('foo'), image_id)
- stubs.Set(nova.image, 'get_glance_client', fake_get_glance_client)
+ stubs.Set(glance, 'get_glance_client', fake_get_glance_client)
class FakeGlance(object):
diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py
index 343190427..49de9c854 100644
--- a/nova/tests/integrated/integrated_helpers.py
+++ b/nova/tests/integrated/integrated_helpers.py
@@ -64,7 +64,7 @@ class _IntegratedTestBase(test.TestCase):
self.flags(**f)
self.flags(verbose=True)
- def fake_get_image_service(image_href):
+ def fake_get_image_service(context, image_href):
image_id = int(str(image_href).split('/')[-1])
return (nova.image.fake.FakeImageService(), image_id)
self.stubs.Set(nova.image, 'get_image_service', fake_get_image_service)
diff --git a/nova/tests/scheduler/test_abstract_scheduler.py b/nova/tests/scheduler/test_abstract_scheduler.py
index aa97e2344..5549ea453 100644
--- a/nova/tests/scheduler/test_abstract_scheduler.py
+++ b/nova/tests/scheduler/test_abstract_scheduler.py
@@ -26,6 +26,7 @@ from nova import test
from nova.compute import api as compute_api
from nova.scheduler import driver
from nova.scheduler import abstract_scheduler
+from nova.scheduler import base_scheduler
from nova.scheduler import zone_manager
@@ -65,6 +66,11 @@ class FakeAbstractScheduler(abstract_scheduler.AbstractScheduler):
pass
+class FakeBaseScheduler(base_scheduler.BaseScheduler):
+ # No need to stub anything at the moment
+ pass
+
+
class FakeZoneManager(zone_manager.ZoneManager):
def __init__(self):
self.service_states = {
@@ -365,3 +371,52 @@ class AbstractSchedulerTestCase(test.TestCase):
self.assertEqual(fixture._decrypt_blob(test_data),
json.dumps(test_data))
+
+ def test_empty_local_hosts(self):
+ """
+ Create a nested set of FakeZones, try to build multiple instances
+ and ensure that a select call returns the appropriate build plan.
+ """
+ sched = FakeAbstractScheduler()
+ self.stubs.Set(sched, '_call_zone_method', fake_call_zone_method)
+ self.stubs.Set(nova.db, 'zone_get_all', fake_zone_get_all)
+
+ zm = FakeZoneManager()
+ # patch this to have no local hosts
+ zm.service_states = {}
+ sched.set_zone_manager(zm)
+
+ fake_context = {}
+ build_plan = sched.select(fake_context,
+ {'instance_type': {'memory_mb': 512},
+ 'num_instances': 4})
+
+ # 0 from local zones, 12 from remotes
+ self.assertEqual(12, len(build_plan))
+
+
+class BaseSchedulerTestCase(test.TestCase):
+ """Test case for Base Scheduler."""
+
+ def test_weigh_hosts(self):
+ """
+ Try to weigh a short list of hosts and make sure enough
+ entries for a larger number instances are returned.
+ """
+
+ sched = FakeBaseScheduler()
+
+ # Fake out a list of hosts
+ zm = FakeZoneManager()
+ hostlist = [(host, services['compute'])
+ for host, services in zm.service_states.items()
+ if 'compute' in services]
+
+ # Call weigh_hosts()
+ num_instances = len(hostlist) * 2 + len(hostlist) / 2
+ instlist = sched.weigh_hosts('compute',
+ dict(num_instances=num_instances),
+ hostlist)
+
+ # Should be enough entries to cover all instances
+ self.assertEqual(len(instlist), num_instances)
diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py
index a52dd041a..890348192 100644
--- a/nova/tests/scheduler/test_scheduler.py
+++ b/nova/tests/scheduler/test_scheduler.py
@@ -963,9 +963,14 @@ class FakeZone(object):
self.password = password
+ZONE_API_URL1 = "http://1.example.com"
+ZONE_API_URL2 = "http://2.example.com"
+
+
def zone_get_all(context):
return [
- FakeZone(1, 'http://example.com', 'bob', 'xxx'),
+ FakeZone(1, ZONE_API_URL1, 'bob', 'xxx'),
+ FakeZone(2, ZONE_API_URL2, 'bob', 'xxx'),
]
@@ -1065,7 +1070,9 @@ class ZoneRedirectTest(test.TestCase):
def test_unmarshal_single_server(self):
decorator = api.reroute_compute("foo")
- self.assertEquals(decorator.unmarshall_result([]), {})
+ decorator.item_uuid = 'fake_uuid'
+ self.assertRaises(exception.InstanceNotFound,
+ decorator.unmarshall_result, [])
self.assertEquals(decorator.unmarshall_result(
[FakeResource(dict(a=1, b=2)), ]),
dict(server=dict(a=1, b=2)))
@@ -1079,6 +1086,90 @@ class ZoneRedirectTest(test.TestCase):
[FakeResource(dict(_a=1, manager=2)), ]),
dict(server={}))
+ def test_one_zone_down_no_instances(self):
+
+ def _fake_issue_novaclient_command(nova, zone, *args, **kwargs):
+ return None
+
+ class FakeNovaClientWithFailure(object):
+ def __init__(self, username, password, method, api_url):
+ self.api_url = api_url
+
+ def authenticate(self):
+ if self.api_url == ZONE_API_URL2:
+ raise novaclient_exceptions.BadRequest('foo')
+
+ self.stubs.Set(api, '_issue_novaclient_command',
+ _fake_issue_novaclient_command)
+ self.stubs.Set(api.novaclient, 'Client', FakeNovaClientWithFailure)
+
+ @api.reroute_compute("get")
+ def do_get(self, context, uuid):
+ pass
+
+ self.assertRaises(exception.ZoneRequestError,
+ do_get, None, {}, FAKE_UUID)
+
+ def test_one_zone_down_got_instance(self):
+
+ def _fake_issue_novaclient_command(nova, zone, *args, **kwargs):
+ class FakeServer(object):
+ def __init__(self):
+ self.id = FAKE_UUID
+ self.test = '1234'
+ return FakeServer()
+
+ class FakeNovaClientWithFailure(object):
+ def __init__(self, username, password, method, api_url):
+ self.api_url = api_url
+
+ def authenticate(self):
+ if self.api_url == ZONE_API_URL2:
+ raise novaclient_exceptions.BadRequest('foo')
+
+ self.stubs.Set(api, '_issue_novaclient_command',
+ _fake_issue_novaclient_command)
+ self.stubs.Set(api.novaclient, 'Client', FakeNovaClientWithFailure)
+
+ @api.reroute_compute("get")
+ def do_get(self, context, uuid):
+ pass
+
+ try:
+ do_get(None, {}, FAKE_UUID)
+ except api.RedirectResult, e:
+ results = e.results
+ self.assertIn('server', results)
+ self.assertEqual(results['server']['id'], FAKE_UUID)
+ self.assertEqual(results['server']['test'], '1234')
+ except Exception, e:
+ self.fail(_("RedirectResult should have been raised"))
+ else:
+ self.fail(_("RedirectResult should have been raised"))
+
+ def test_zones_up_no_instances(self):
+
+ def _fake_issue_novaclient_command(nova, zone, *args, **kwargs):
+ return None
+
+ class FakeNovaClientNoFailure(object):
+ def __init__(self, username, password, method, api_url):
+ pass
+
+ def authenticate(self):
+ return
+
+ self.stubs.Set(api, '_issue_novaclient_command',
+ _fake_issue_novaclient_command)
+ self.stubs.Set(api.novaclient, 'Client', FakeNovaClientNoFailure)
+
+ @api.reroute_compute("get")
+ def do_get(self, context, uuid):
+ pass
+
+ self.assertRaises(exception.InstanceNotFound,
+ do_get, None, {}, FAKE_UUID)
+
class FakeServerCollection(object):
def get(self, instance_id):
@@ -1097,7 +1188,7 @@ class FakeEmptyServerCollection(object):
class FakeNovaClient(object):
- def __init__(self, collection):
+ def __init__(self, collection, *args, **kwargs):
self.servers = collection
@@ -1162,8 +1253,9 @@ class CallZoneMethodTest(test.TestCase):
context = {}
method = 'do_something'
results = api.call_zone_method(context, method)
- expected = [(1, 42)]
- self.assertEqual(expected, results)
+ self.assertEqual(len(results), 2)
+ self.assertIn((1, 42), results)
+ self.assertIn((2, 42), results)
def test_call_zone_method_not_present(self):
context = {}
diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py
index 3fe6a9b42..7fe353b3d 100644
--- a/nova/tests/test_cloud.py
+++ b/nova/tests/test_cloud.py
@@ -486,11 +486,9 @@ class CloudTestCase(test.TestCase):
inst2 = db.instance_create(self.context, args2)
db.instance_destroy(self.context, inst1.id)
result = self.cloud.describe_instances(self.context)
+ self.assertEqual(len(result['reservationSet']), 1)
result1 = result['reservationSet'][0]['instancesSet']
self.assertEqual(result1[0]['instanceId'],
- ec2utils.id_to_ec2_id(inst1.id))
- result2 = result['reservationSet'][1]['instancesSet']
- self.assertEqual(result2[0]['instanceId'],
ec2utils.id_to_ec2_id(inst2.id))
def _block_device_mapping_create(self, instance_id, mappings):
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index 65fdffbd6..4d463572b 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -300,11 +300,20 @@ class ComputeTestCase(test.TestCase):
self.compute.resume_instance(self.context, instance_id)
self.compute.terminate_instance(self.context, instance_id)
- def test_reboot(self):
- """Ensure instance can be rebooted"""
+ def test_soft_reboot(self):
+ """Ensure instance can be soft rebooted"""
instance_id = self._create_instance()
+ reboot_type = "SOFT"
self.compute.run_instance(self.context, instance_id)
- self.compute.reboot_instance(self.context, instance_id)
+ self.compute.reboot_instance(self.context, instance_id, reboot_type)
+ self.compute.terminate_instance(self.context, instance_id)
+
+ def test_hard_reboot(self):
+ """Ensure instance can be hard rebooted"""
+ instance_id = self._create_instance()
+ reboot_type = "HARD"
+ self.compute.run_instance(self.context, instance_id)
+ self.compute.reboot_instance(self.context, instance_id, reboot_type)
self.compute.terminate_instance(self.context, instance_id)
def test_set_admin_password(self):
diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py
index 8c6775b29..233ee14de 100644
--- a/nova/tests/test_libvirt.py
+++ b/nova/tests/test_libvirt.py
@@ -37,61 +37,20 @@ from nova.compute import power_state
from nova.compute import vm_states
from nova.virt.libvirt import connection
from nova.virt.libvirt import firewall
+from nova.tests import fake_network
libvirt = None
FLAGS = flags.FLAGS
+_fake_network_info = fake_network.fake_get_instance_nw_info
+_ipv4_like = fake_network.ipv4_like
+
def _concurrency(wait, done, target):
wait.wait()
done.send()
-def _create_network_info(count=1, ipv6=None):
- if ipv6 is None:
- ipv6 = FLAGS.use_ipv6
- fake = 'fake'
- fake_ip = '10.11.12.13'
- fake_ip_2 = '0.0.0.1'
- fake_ip_3 = '0.0.0.1'
- fake_vlan = 100
- fake_bridge_interface = 'eth0'
- network = {'bridge': fake,
- 'cidr': fake_ip,
- 'cidr_v6': fake_ip,
- 'gateway_v6': fake,
- 'vlan': fake_vlan,
- 'bridge_interface': fake_bridge_interface}
- mapping = {'mac': fake,
- 'dhcp_server': '10.0.0.1',
- 'gateway': fake,
- 'gateway6': fake,
- '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)]
-
-
-def _setup_networking(instance_id, ip='1.2.3.4', mac='56:12:12:12:12:12'):
- ctxt = context.get_admin_context()
- network_ref = db.project_get_networks(ctxt,
- 'fake',
- associate=True)[0]
- vif = {'address': mac,
- 'network_id': network_ref['id'],
- 'instance_id': instance_id}
- vif_ref = db.virtual_interface_create(ctxt, vif)
-
- fixed_ip = {'address': ip,
- 'network_id': network_ref['id'],
- 'virtual_interface_id': vif_ref['id']}
- db.fixed_ip_create(ctxt, fixed_ip)
- db.fixed_ip_update(ctxt, ip, {'allocated': True,
- 'instance_id': instance_id})
-
-
class CacheConcurrencyTestCase(test.TestCase):
def setUp(self):
super(CacheConcurrencyTestCase, self).setUp()
@@ -163,7 +122,6 @@ class LibvirtConnTestCase(test.TestCase):
self.context = context.get_admin_context()
self.flags(instances_path='')
self.call_libvirt_dependant_setup = False
- self.test_ip = '10.11.12.13'
test_instance = {'memory_kb': '1024000',
'basepath': '/some/path',
@@ -277,12 +235,12 @@ class LibvirtConnTestCase(test.TestCase):
instance_ref = db.instance_create(self.context, self.test_instance)
result = conn._prepare_xml_info(instance_ref,
- _create_network_info(),
+ _fake_network_info(self.stubs, 1),
False)
self.assertTrue(len(result['nics']) == 1)
result = conn._prepare_xml_info(instance_ref,
- _create_network_info(2),
+ _fake_network_info(self.stubs, 2),
False)
self.assertTrue(len(result['nics']) == 2)
@@ -407,7 +365,7 @@ class LibvirtConnTestCase(test.TestCase):
def test_multi_nic(self):
instance_data = dict(self.test_instance)
- network_info = _create_network_info(2)
+ network_info = _fake_network_info(self.stubs, 2)
conn = connection.LibvirtConnection(True)
instance_ref = db.instance_create(self.context, instance_data)
xml = conn.to_xml(instance_ref, network_info, False)
@@ -417,15 +375,14 @@ class LibvirtConnTestCase(test.TestCase):
parameters = interfaces[0].findall('./filterref/parameter')
self.assertEquals(interfaces[0].get('type'), 'bridge')
self.assertEquals(parameters[0].get('name'), 'IP')
- self.assertEquals(parameters[0].get('value'), '10.11.12.13')
+ self.assertTrue(_ipv4_like(parameters[0].get('value'), '192.168'))
self.assertEquals(parameters[1].get('name'), 'DHCPSERVER')
- self.assertEquals(parameters[1].get('value'), '10.0.0.1')
+ self.assertTrue(_ipv4_like(parameters[1].get('value'), '192.168.*.1'))
def _check_xml_and_container(self, instance):
user_context = context.RequestContext(self.user_id,
self.project_id)
instance_ref = db.instance_create(user_context, instance)
- _setup_networking(instance_ref['id'], self.test_ip)
self.flags(libvirt_type='lxc')
conn = connection.LibvirtConnection(True)
@@ -433,7 +390,7 @@ class LibvirtConnTestCase(test.TestCase):
uri = conn.get_uri()
self.assertEquals(uri, 'lxc:///')
- network_info = _create_network_info()
+ network_info = _fake_network_info(self.stubs, 1)
xml = conn.to_xml(instance_ref, network_info)
tree = xml_to_tree(xml)
@@ -457,8 +414,6 @@ class LibvirtConnTestCase(test.TestCase):
network_ref = db.project_get_networks(context.get_admin_context(),
self.project_id)[0]
- _setup_networking(instance_ref['id'], self.test_ip)
-
type_uri_map = {'qemu': ('qemu:///system',
[(lambda t: t.find('.').get('type'), 'qemu'),
(lambda t: t.find('./os/type').text, 'hvm'),
@@ -504,9 +459,11 @@ class LibvirtConnTestCase(test.TestCase):
common_checks = [
(lambda t: t.find('.').tag, 'domain'),
(lambda t: t.find(parameter).get('name'), 'IP'),
- (lambda t: t.find(parameter).get('value'), '10.11.12.13'),
+ (lambda t: _ipv4_like(t.find(parameter).get('value'), '192.168'),
+ True),
(lambda t: t.findall(parameter)[1].get('name'), 'DHCPSERVER'),
- (lambda t: t.findall(parameter)[1].get('value'), '10.0.0.1'),
+ (lambda t: _ipv4_like(t.findall(parameter)[1].get('value'),
+ '192.168.*.1'), True),
(lambda t: t.find('./devices/serial/source').get(
'path').split('/')[1], 'console.log'),
(lambda t: t.find('./memory').text, '2097152')]
@@ -531,7 +488,7 @@ class LibvirtConnTestCase(test.TestCase):
uri = conn.get_uri()
self.assertEquals(uri, expected_uri)
- network_info = _create_network_info()
+ network_info = _fake_network_info(self.stubs, 1)
xml = conn.to_xml(instance_ref, network_info, rescue)
tree = xml_to_tree(xml)
for i, (check, expected_result) in enumerate(checks):
@@ -646,7 +603,7 @@ class LibvirtConnTestCase(test.TestCase):
self.create_fake_libvirt_mock()
instance_ref = db.instance_create(self.context, self.test_instance)
- network_info = _create_network_info()
+ network_info = _fake_network_info(self.stubs, 1)
# Start test
self.mox.ReplayAll()
@@ -743,7 +700,7 @@ class LibvirtConnTestCase(test.TestCase):
# qemu-img should be mockd since test environment might not have
# large disk space.
self.mox.StubOutWithMock(utils, "execute")
- utils.execute('sudo', 'qemu-img', 'create', '-f', 'raw',
+ utils.execute('qemu-img', 'create', '-f', 'raw',
'%s/%s/disk' % (tmpdir, instance_ref.name), '10G')
self.mox.ReplayAll()
@@ -795,7 +752,7 @@ class LibvirtConnTestCase(test.TestCase):
os.path.getsize("/test/disk").AndReturn(10 * 1024 * 1024 * 1024)
# another is qcow image, so qemu-img should be mocked.
self.mox.StubOutWithMock(utils, "execute")
- utils.execute('sudo', 'qemu-img', 'info', '/test/disk.local').\
+ utils.execute('qemu-img', 'info', '/test/disk.local').\
AndReturn((ret, ''))
self.mox.ReplayAll()
@@ -830,7 +787,7 @@ class LibvirtConnTestCase(test.TestCase):
conn.firewall_driver.setattr('setup_basic_filtering', fake_none)
conn.firewall_driver.setattr('prepare_instance_filter', fake_none)
- network_info = _create_network_info()
+ network_info = _fake_network_info(self.stubs, 1)
try:
conn.spawn(self.context, instance, network_info)
@@ -923,7 +880,6 @@ class IptablesFirewallTestCase(test.TestCase):
"""setup_basic_rules in nwfilter calls this."""
pass
self.fake_libvirt_connection = FakeLibvirtConnection()
- self.test_ip = '10.11.12.13'
self.fw = firewall.IptablesFirewallDriver(
get_connection=lambda: self.fake_libvirt_connection)
@@ -987,10 +943,6 @@ class IptablesFirewallTestCase(test.TestCase):
def test_static_filters(self):
instance_ref = self._create_instance_ref()
src_instance_ref = self._create_instance_ref()
- src_ip = '10.11.12.14'
- src_mac = '56:12:12:12:12:13'
- _setup_networking(instance_ref['id'], self.test_ip, src_mac)
- _setup_networking(src_instance_ref['id'], src_ip)
admin_ctxt = context.get_admin_context()
secgroup = db.security_group_create(admin_ctxt,
@@ -1061,10 +1013,17 @@ class IptablesFirewallTestCase(test.TestCase):
return '', ''
print cmd, kwargs
+ def get_fixed_ips(*args, **kwargs):
+ ips = []
+ for network, info in network_info:
+ ips.extend(info['ips'])
+ return [ip['ip'] for ip in ips]
+
from nova.network import linux_net
linux_net.iptables_manager.execute = fake_iptables_execute
- network_info = _create_network_info()
+ network_info = _fake_network_info(self.stubs, 1)
+ self.stubs.Set(db, 'instance_get_fixed_addresses', get_fixed_ips)
self.fw.prepare_instance_filter(instance_ref, network_info)
self.fw.apply_instance_filter(instance_ref, network_info)
@@ -1078,7 +1037,8 @@ class IptablesFirewallTestCase(test.TestCase):
instance_chain = None
for rule in self.out_rules:
# This is pretty crude, but it'll do for now
- if '-d 10.11.12.13 -j' in rule:
+ # last two octets change
+ if re.search('-d 192.168.[0-9]{1,3}.[0-9]{1,3} -j', rule):
instance_chain = rule.split(' ')[-1]
break
self.assertTrue(instance_chain, "The instance chain wasn't added")
@@ -1101,10 +1061,11 @@ class IptablesFirewallTestCase(test.TestCase):
self.assertTrue(len(filter(regex.match, self.out_rules)) > 0,
"ICMP Echo Request acceptance rule wasn't added")
- regex = re.compile('-A .* -j ACCEPT -p tcp -m multiport '
- '--dports 80:81 -s %s' % (src_ip,))
- self.assertTrue(len(filter(regex.match, self.out_rules)) > 0,
- "TCP port 80/81 acceptance rule wasn't added")
+ for ip in get_fixed_ips():
+ regex = re.compile('-A .* -j ACCEPT -p tcp -m multiport '
+ '--dports 80:81 -s %s' % ip)
+ self.assertTrue(len(filter(regex.match, self.out_rules)) > 0,
+ "TCP port 80/81 acceptance rule wasn't added")
regex = re.compile('-A .* -j ACCEPT -p tcp '
'-m multiport --dports 80:81 -s 192.168.10.0/24')
@@ -1114,24 +1075,27 @@ class IptablesFirewallTestCase(test.TestCase):
def test_filters_for_instance_with_ip_v6(self):
self.flags(use_ipv6=True)
- network_info = _create_network_info()
+ network_info = _fake_network_info(self.stubs, 1)
rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info)
self.assertEquals(len(rulesv4), 2)
- self.assertEquals(len(rulesv6), 3)
+ self.assertEquals(len(rulesv6), 1)
def test_filters_for_instance_without_ip_v6(self):
self.flags(use_ipv6=False)
- network_info = _create_network_info()
+ network_info = _fake_network_info(self.stubs, 1)
rulesv4, rulesv6 = self.fw._filters_for_instance("fake", network_info)
self.assertEquals(len(rulesv4), 2)
self.assertEquals(len(rulesv6), 0)
def test_multinic_iptables(self):
- ipv4_rules_per_network = 2
- ipv6_rules_per_network = 3
+ ipv4_rules_per_addr = 1
+ ipv4_addr_per_network = 2
+ ipv6_rules_per_addr = 1
+ ipv6_addr_per_network = 1
networks_count = 5
instance_ref = self._create_instance_ref()
- network_info = _create_network_info(networks_count)
+ network_info = _fake_network_info(self.stubs, networks_count,
+ ipv4_addr_per_network)
ipv4_len = len(self.fw.iptables.ipv4['filter'].rules)
ipv6_len = len(self.fw.iptables.ipv6['filter'].rules)
inst_ipv4, inst_ipv6 = self.fw.instance_rules(instance_ref,
@@ -1142,9 +1106,9 @@ class IptablesFirewallTestCase(test.TestCase):
ipv4_network_rules = len(ipv4) - len(inst_ipv4) - ipv4_len
ipv6_network_rules = len(ipv6) - len(inst_ipv6) - ipv6_len
self.assertEquals(ipv4_network_rules,
- ipv4_rules_per_network * networks_count)
+ ipv4_rules_per_addr * ipv4_addr_per_network * networks_count)
self.assertEquals(ipv6_network_rules,
- ipv6_rules_per_network * networks_count)
+ ipv6_rules_per_addr * ipv6_addr_per_network * networks_count)
def test_do_refresh_security_group_rules(self):
instance_ref = self._create_instance_ref()
@@ -1170,8 +1134,7 @@ class IptablesFirewallTestCase(test.TestCase):
fakefilter.nwfilterLookupByName
instance_ref = self._create_instance_ref()
- _setup_networking(instance_ref['id'], self.test_ip)
- network_info = _create_network_info()
+ network_info = _fake_network_info(self.stubs, 1)
self.fw.setup_basic_filtering(instance_ref, network_info)
self.fw.prepare_instance_filter(instance_ref, network_info)
self.fw.apply_instance_filter(instance_ref, network_info)
@@ -1186,13 +1149,12 @@ class IptablesFirewallTestCase(test.TestCase):
def test_provider_firewall_rules(self):
# setup basic instance data
instance_ref = self._create_instance_ref()
- _setup_networking(instance_ref['id'], self.test_ip)
# FRAGILE: peeks at how the firewall names chains
chain_name = 'inst-%s' % instance_ref['id']
# create a firewall via setup_basic_filtering like libvirt_conn.spawn
# should have a chain with 0 rules
- network_info = _create_network_info(1)
+ network_info = _fake_network_info(self.stubs, 1)
self.fw.setup_basic_filtering(instance_ref, network_info)
self.assertTrue('provider' in self.fw.iptables.ipv4['filter'].chains)
rules = [rule for rule in self.fw.iptables.ipv4['filter'].rules
@@ -1256,7 +1218,6 @@ class NWFilterTestCase(test.TestCase):
self.fake_libvirt_connection = Mock()
- self.test_ip = '10.11.12.13'
self.fw = firewall.NWFilterFirewall(
lambda: self.fake_libvirt_connection)
@@ -1372,11 +1333,9 @@ class NWFilterTestCase(test.TestCase):
instance_ref = self._create_instance()
inst_id = instance_ref['id']
- _setup_networking(instance_ref['id'], self.test_ip)
-
- def _ensure_all_called():
+ def _ensure_all_called(mac):
instance_filter = 'nova-instance-%s-%s' % (instance_ref['name'],
- 'fake')
+ mac.translate(None, ':'))
secgroup_filter = 'nova-secgroup-%s' % self.security_group['id']
for required in [secgroup_filter, 'allow-dhcp-server',
'no-arp-spoofing', 'no-ip-spoofing',
@@ -1392,17 +1351,22 @@ class NWFilterTestCase(test.TestCase):
self.security_group.id)
instance = db.instance_get(self.context, inst_id)
- network_info = _create_network_info()
+ network_info = _fake_network_info(self.stubs, 1)
+ # since there is one (network_info) there is one vif
+ # pass this vif's mac to _ensure_all_called()
+ # to set the instance_filter properly
+ mac = network_info[0][1]['mac']
+
self.fw.setup_basic_filtering(instance, network_info)
self.fw.prepare_instance_filter(instance, network_info)
self.fw.apply_instance_filter(instance, network_info)
- _ensure_all_called()
+ _ensure_all_called(mac)
self.teardown_security_group()
db.instance_destroy(context.get_admin_context(), instance_ref['id'])
def test_create_network_filters(self):
instance_ref = self._create_instance()
- network_info = _create_network_info(3)
+ network_info = _fake_network_info(self.stubs, 3)
result = self.fw._create_network_filters(instance_ref,
network_info,
"fake")
@@ -1425,8 +1389,7 @@ class NWFilterTestCase(test.TestCase):
instance = db.instance_get(self.context, inst_id)
- _setup_networking(instance_ref['id'], self.test_ip)
- network_info = _create_network_info()
+ network_info = _fake_network_info(self.stubs, 1)
self.fw.setup_basic_filtering(instance, network_info)
self.fw.prepare_instance_filter(instance, network_info)
self.fw.apply_instance_filter(instance, network_info)
diff --git a/nova/tests/test_linux_net.py b/nova/tests/test_linux_net.py
new file mode 100755
index 000000000..99577b88e
--- /dev/null
+++ b/nova/tests/test_linux_net.py
@@ -0,0 +1,347 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 NTT
+# 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 context
+from nova import db
+from nova import exception
+from nova import flags
+from nova import log as logging
+from nova import test
+from nova import utils
+from nova.network import manager as network_manager
+from nova.network import linux_net
+
+import mox
+
+FLAGS = flags.FLAGS
+
+LOG = logging.getLogger('nova.tests.network')
+
+
+HOST = "testhost"
+
+instances = [{'id': 0,
+ 'host': 'fake_instance00',
+ 'hostname': 'fake_instance00'},
+ {'id': 1,
+ 'host': 'fake_instance01',
+ 'hostname': 'fake_instance01'}]
+
+
+addresses = [{"address": "10.0.0.1"},
+ {"address": "10.0.0.2"},
+ {"address": "10.0.0.3"},
+ {"address": "10.0.0.4"},
+ {"address": "10.0.0.5"},
+ {"address": "10.0.0.6"}]
+
+
+networks = [{'id': 0,
+ 'uuid': "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
+ 'label': 'test0',
+ 'injected': False,
+ 'multi_host': False,
+ 'cidr': '192.168.0.0/24',
+ 'cidr_v6': '2001:db8::/64',
+ 'gateway_v6': '2001:db8::1',
+ 'netmask_v6': '64',
+ 'netmask': '255.255.255.0',
+ 'bridge': 'fa0',
+ 'bridge_interface': 'fake_fa0',
+ 'gateway': '192.168.0.1',
+ 'broadcast': '192.168.0.255',
+ 'dns1': '192.168.0.1',
+ 'dns2': '192.168.0.2',
+ 'dhcp_server': '0.0.0.0',
+ 'dhcp_start': '192.168.100.1',
+ 'vlan': None,
+ 'host': None,
+ 'project_id': 'fake_project',
+ 'vpn_public_address': '192.168.0.2'},
+ {'id': 1,
+ 'uuid': "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
+ 'label': 'test1',
+ 'injected': False,
+ 'multi_host': False,
+ 'cidr': '192.168.1.0/24',
+ 'cidr_v6': '2001:db9::/64',
+ 'gateway_v6': '2001:db9::1',
+ 'netmask_v6': '64',
+ 'netmask': '255.255.255.0',
+ 'bridge': 'fa1',
+ 'bridge_interface': 'fake_fa1',
+ 'gateway': '192.168.1.1',
+ 'broadcast': '192.168.1.255',
+ 'dns1': '192.168.0.1',
+ 'dns2': '192.168.0.2',
+ 'dhcp_server': '0.0.0.0',
+ 'dhcp_start': '192.168.100.1',
+ 'vlan': None,
+ 'host': None,
+ 'project_id': 'fake_project',
+ 'vpn_public_address': '192.168.1.2'}]
+
+
+fixed_ips = [{'id': 0,
+ 'network_id': 0,
+ 'address': '192.168.0.100',
+ 'instance_id': 0,
+ 'allocated': True,
+ 'virtual_interface_id': 0,
+ 'virtual_interface': addresses[0],
+ 'instance': instances[0],
+ 'floating_ips': []},
+ {'id': 1,
+ 'network_id': 1,
+ 'address': '192.168.1.100',
+ 'instance_id': 0,
+ 'allocated': True,
+ 'virtual_interface_id': 1,
+ 'virtual_interface': addresses[1],
+ 'instance': instances[0],
+ 'floating_ips': []},
+ {'id': 2,
+ 'network_id': 1,
+ 'address': '192.168.0.101',
+ 'instance_id': 1,
+ 'allocated': True,
+ 'virtual_interface_id': 2,
+ 'virtual_interface': addresses[2],
+ 'instance': instances[1],
+ 'floating_ips': []},
+ {'id': 3,
+ 'network_id': 0,
+ 'address': '192.168.1.101',
+ 'instance_id': 1,
+ 'allocated': True,
+ 'virtual_interface_id': 3,
+ 'virtual_interface': addresses[3],
+ 'instance': instances[1],
+ 'floating_ips': []},
+ {'id': 4,
+ 'network_id': 0,
+ 'address': '192.168.0.102',
+ 'instance_id': 0,
+ 'allocated': True,
+ 'virtual_interface_id': 4,
+ 'virtual_interface': addresses[4],
+ 'instance': instances[0],
+ 'floating_ips': []},
+ {'id': 5,
+ 'network_id': 1,
+ 'address': '192.168.1.102',
+ 'instance_id': 1,
+ 'allocated': True,
+ 'virtual_interface_id': 5,
+ 'virtual_interface': addresses[5],
+ 'instance': instances[1],
+ 'floating_ips': []}]
+
+
+vifs = [{'id': 0,
+ 'address': 'DE:AD:BE:EF:00:00',
+ 'uuid': '00000000-0000-0000-0000-0000000000000000',
+ 'network_id': 0,
+ 'network': networks[0],
+ 'instance_id': 0},
+ {'id': 1,
+ 'address': 'DE:AD:BE:EF:00:01',
+ 'uuid': '00000000-0000-0000-0000-0000000000000001',
+ 'network_id': 1,
+ 'network': networks[1],
+ 'instance_id': 0},
+ {'id': 2,
+ 'address': 'DE:AD:BE:EF:00:02',
+ 'uuid': '00000000-0000-0000-0000-0000000000000002',
+ 'network_id': 1,
+ 'network': networks[1],
+ 'instance_id': 1},
+ {'id': 3,
+ 'address': 'DE:AD:BE:EF:00:03',
+ 'uuid': '00000000-0000-0000-0000-0000000000000003',
+ 'network_id': 0,
+ 'network': networks[0],
+ 'instance_id': 1},
+ {'id': 4,
+ 'address': 'DE:AD:BE:EF:00:04',
+ 'uuid': '00000000-0000-0000-0000-0000000000000004',
+ 'network_id': 0,
+ 'network': networks[0],
+ 'instance_id': 0},
+ {'id': 5,
+ 'address': 'DE:AD:BE:EF:00:05',
+ 'uuid': '00000000-0000-0000-0000-0000000000000005',
+ 'network_id': 1,
+ 'network': networks[1],
+ 'instance_id': 1}]
+
+
+class LinuxNetworkTestCase(test.TestCase):
+
+ def setUp(self):
+ super(LinuxNetworkTestCase, self).setUp()
+ network_driver = FLAGS.network_driver
+ self.driver = utils.import_object(network_driver)
+ self.driver.db = db
+
+ def test_update_dhcp_for_nw00(self):
+ self.flags(use_single_default_gateway=True)
+ self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips')
+ self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance')
+
+ db.network_get_associated_fixed_ips(mox.IgnoreArg(),
+ mox.IgnoreArg())\
+ .AndReturn([fixed_ips[0],
+ fixed_ips[3]])
+
+ db.network_get_associated_fixed_ips(mox.IgnoreArg(),
+ mox.IgnoreArg())\
+ .AndReturn([fixed_ips[0],
+ fixed_ips[3]])
+ db.virtual_interface_get_by_instance(mox.IgnoreArg(),
+ mox.IgnoreArg())\
+ .AndReturn([vifs[0], vifs[1]])
+ db.virtual_interface_get_by_instance(mox.IgnoreArg(),
+ mox.IgnoreArg())\
+ .AndReturn([vifs[2], vifs[3]])
+ self.mox.ReplayAll()
+
+ self.driver.update_dhcp(None, "eth0", networks[0])
+
+ def test_update_dhcp_for_nw01(self):
+ self.flags(use_single_default_gateway=True)
+ self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips')
+ self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance')
+
+ db.network_get_associated_fixed_ips(mox.IgnoreArg(),
+ mox.IgnoreArg())\
+ .AndReturn([fixed_ips[1],
+ fixed_ips[2]])
+
+ db.network_get_associated_fixed_ips(mox.IgnoreArg(),
+ mox.IgnoreArg())\
+ .AndReturn([fixed_ips[1],
+ fixed_ips[2]])
+ db.virtual_interface_get_by_instance(mox.IgnoreArg(),
+ mox.IgnoreArg())\
+ .AndReturn([vifs[0], vifs[1]])
+ db.virtual_interface_get_by_instance(mox.IgnoreArg(),
+ mox.IgnoreArg())\
+ .AndReturn([vifs[2], vifs[3]])
+ self.mox.ReplayAll()
+
+ self.driver.update_dhcp(None, "eth0", networks[0])
+
+ def test_get_dhcp_hosts_for_nw00(self):
+ self.flags(use_single_default_gateway=True)
+ self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips')
+
+ db.network_get_associated_fixed_ips(mox.IgnoreArg(),
+ mox.IgnoreArg())\
+ .AndReturn([fixed_ips[0],
+ fixed_ips[3]])
+ self.mox.ReplayAll()
+
+ expected = \
+ "10.0.0.1,fake_instance00.novalocal,"\
+ "192.168.0.100,net:NW-i00000000-0\n"\
+ "10.0.0.4,fake_instance01.novalocal,"\
+ "192.168.1.101,net:NW-i00000001-0"
+ actual_hosts = self.driver.get_dhcp_hosts(None, networks[1])
+
+ self.assertEquals(actual_hosts, expected)
+
+ def test_get_dhcp_hosts_for_nw01(self):
+ self.flags(use_single_default_gateway=True)
+ self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips')
+
+ db.network_get_associated_fixed_ips(mox.IgnoreArg(),
+ mox.IgnoreArg())\
+ .AndReturn([fixed_ips[1],
+ fixed_ips[2]])
+ self.mox.ReplayAll()
+
+ expected = \
+ "10.0.0.2,fake_instance00.novalocal,"\
+ "192.168.1.100,net:NW-i00000000-1\n"\
+ "10.0.0.3,fake_instance01.novalocal,"\
+ "192.168.0.101,net:NW-i00000001-1"
+ actual_hosts = self.driver.get_dhcp_hosts(None, networks[0])
+
+ self.assertEquals(actual_hosts, expected)
+
+ def test_get_dhcp_opts_for_nw00(self):
+ self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips')
+ self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance')
+
+ db.network_get_associated_fixed_ips(mox.IgnoreArg(),
+ mox.IgnoreArg())\
+ .AndReturn([fixed_ips[0],
+ fixed_ips[3],
+ fixed_ips[4]])
+ db.virtual_interface_get_by_instance(mox.IgnoreArg(),
+ mox.IgnoreArg())\
+ .AndReturn([vifs[0],
+ vifs[1],
+ vifs[4]])
+ db.virtual_interface_get_by_instance(mox.IgnoreArg(),
+ mox.IgnoreArg())\
+ .AndReturn([vifs[2],
+ vifs[3],
+ vifs[5]])
+ self.mox.ReplayAll()
+
+ expected_opts = 'NW-i00000001-0,3'
+ actual_opts = self.driver.get_dhcp_opts(None, networks[0])
+
+ self.assertEquals(actual_opts, expected_opts)
+
+ def test_get_dhcp_opts_for_nw01(self):
+ self.mox.StubOutWithMock(db, 'network_get_associated_fixed_ips')
+ self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance')
+
+ db.network_get_associated_fixed_ips(mox.IgnoreArg(),
+ mox.IgnoreArg())\
+ .AndReturn([fixed_ips[1],
+ fixed_ips[2],
+ fixed_ips[5]])
+ db.virtual_interface_get_by_instance(mox.IgnoreArg(),
+ mox.IgnoreArg())\
+ .AndReturn([vifs[0],
+ vifs[1],
+ vifs[4]])
+ db.virtual_interface_get_by_instance(mox.IgnoreArg(),
+ mox.IgnoreArg())\
+ .AndReturn([vifs[2],
+ vifs[3],
+ vifs[5]])
+ self.mox.ReplayAll()
+
+ expected_opts = "NW-i00000000-1,3"
+ actual_opts = self.driver.get_dhcp_opts(None, networks[1])
+
+ self.assertEquals(actual_opts, expected_opts)
+
+ def test_dhcp_opts_not_default_gateway_network(self):
+ expected = "NW-i00000000-0,3"
+ actual = self.driver._host_dhcp_opts(fixed_ips[0])
+ self.assertEquals(actual, expected)
+
+ def test_host_dhcp_without_default_gateway_network(self):
+ expected = ("10.0.0.1,fake_instance00.novalocal,192.168.0.100")
+ actual = self.driver._host_dhcp(fixed_ips[0])
+ self.assertEquals(actual, expected)
diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py
index 25ff940f0..926ea065a 100644
--- a/nova/tests/test_network.py
+++ b/nova/tests/test_network.py
@@ -14,6 +14,7 @@
# 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 mox
from nova import context
from nova import db
@@ -21,9 +22,7 @@ from nova import exception
from nova import log as logging
from nova import test
from nova.network import manager as network_manager
-
-
-import mox
+from nova.tests import fake_network
LOG = logging.getLogger('nova.tests.network')
@@ -58,7 +57,7 @@ networks = [{'id': 0,
'dns1': '192.168.0.1',
'dns2': '192.168.0.2',
'vlan': None,
- 'host': None,
+ 'host': HOST,
'project_id': 'fake_project',
'vpn_public_address': '192.168.0.2'},
{'id': 1,
@@ -78,7 +77,7 @@ networks = [{'id': 0,
'dns1': '192.168.0.1',
'dns2': '192.168.0.2',
'vlan': None,
- 'host': None,
+ 'host': HOST,
'project_id': 'fake_project',
'vpn_public_address': '192.168.1.2'}]
@@ -118,9 +117,14 @@ vifs = [{'id': 0,
{'id': 1,
'address': 'DE:AD:BE:EF:00:01',
'uuid': '00000000-0000-0000-0000-0000000000000001',
- 'network_id': 0,
'network_id': 1,
'network': FakeModel(**networks[1]),
+ 'instance_id': 0},
+ {'id': 2,
+ 'address': 'DE:AD:BE:EF:00:02',
+ 'uuid': '00000000-0000-0000-0000-0000000000000002',
+ 'network_id': 2,
+ 'network': None,
'instance_id': 0}]
@@ -133,60 +137,50 @@ class FlatNetworkTestCase(test.TestCase):
is_admin=False)
def test_get_instance_nw_info(self):
- self.mox.StubOutWithMock(db, 'fixed_ip_get_by_instance')
- self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance')
- self.mox.StubOutWithMock(db, 'instance_type_get')
-
- db.fixed_ip_get_by_instance(mox.IgnoreArg(),
- mox.IgnoreArg()).AndReturn(fixed_ips)
- db.virtual_interface_get_by_instance(mox.IgnoreArg(),
- mox.IgnoreArg()).AndReturn(vifs)
- db.instance_type_get(mox.IgnoreArg(),
- mox.IgnoreArg()).AndReturn(flavor)
- self.mox.ReplayAll()
-
- nw_info = self.network.get_instance_nw_info(None, 0, 0, None)
+ fake_get_instance_nw_info = fake_network.fake_get_instance_nw_info
- self.assertTrue(nw_info)
+ nw_info = fake_get_instance_nw_info(self.stubs, 0, 2)
+ self.assertFalse(nw_info)
- for i, nw in enumerate(nw_info):
- i8 = i + 8
- check = {'bridge': 'fa%s' % i,
+ for i, (nw, info) in enumerate(nw_info):
+ check = {'bridge': 'fake_br%d' % i,
'cidr': '192.168.%s.0/24' % i,
- 'cidr_v6': '2001:db%s::/64' % i8,
+ 'cidr_v6': '2001:db8:0:%x::/64' % i,
'id': i,
'multi_host': False,
- 'injected': 'DONTCARE',
- 'bridge_interface': 'fake_fa%s' % i,
+ 'injected': False,
+ 'bridge_interface': 'fake_eth%d' % i,
'vlan': None}
- self.assertDictMatch(nw[0], check)
+ self.assertDictMatch(nw, check)
- check = {'broadcast': '192.168.%s.255' % i,
- 'dhcp_server': '192.168.%s.1' % i,
- 'dns': 'DONTCARE',
- 'gateway': '192.168.%s.1' % i,
- 'gateway6': '2001:db%s::1' % i8,
+ check = {'broadcast': '192.168.%d.255' % i,
+ 'dhcp_server': '192.168.%d.1' % i,
+ 'dns': ['192.168.%d.3' % n, '192.168.%d.4' % n],
+ 'gateway': '192.168.%d.1' % i,
+ 'gateway6': '2001:db8:0:%x::1' % i,
'ip6s': 'DONTCARE',
'ips': 'DONTCARE',
- 'label': 'test%s' % i,
- 'mac': 'DE:AD:BE:EF:00:0%s' % i,
- 'vif_uuid': ('00000000-0000-0000-0000-000000000000000%s' %
- i),
- 'rxtx_cap': 'DONTCARE',
+ 'label': 'test%d' % i,
+ 'mac': 'DE:AD:BE:EF:00:%02x' % i,
+ 'vif_uuid':
+ '00000000-0000-0000-0000-00000000000000%02d' % i,
+ 'rxtx_cap': 3,
'should_create_vlan': False,
'should_create_bridge': False}
- self.assertDictMatch(nw[1], check)
+ self.assertDictMatch(info, check)
check = [{'enabled': 'DONTCARE',
- 'ip': '2001:db%s::dcad:beff:feef:%s' % (i8, i),
+ 'ip': '2001:db8::dcad:beff:feef:%s' % i,
'netmask': '64'}]
- self.assertDictListMatch(nw[1]['ip6s'], check)
+ self.assertDictListMatch(info['ip6s'], check)
- check = [{'enabled': '1',
- 'ip': '192.168.%s.100' % i,
- 'netmask': '255.255.255.0'}]
- self.assertDictListMatch(nw[1]['ips'], check)
+ num_fixed_ips = len(info['ips'])
+ check = [{'enabled': 'DONTCARE',
+ 'ip': '192.168.%d.1%02d' % (i, ip_num),
+ 'netmask': '255.255.255.0'}
+ for ip_num in xrange(num_fixed_ips)]
+ self.assertDictListMatch(info['ips'], check)
def test_validate_networks(self):
self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
@@ -247,6 +241,34 @@ class FlatNetworkTestCase(test.TestCase):
self.network.validate_networks(None, requested_networks)
+ def test_add_fixed_ip_instance_without_vpn_requested_networks(self):
+ self.mox.StubOutWithMock(db, 'network_get')
+ self.mox.StubOutWithMock(db, 'network_update')
+ self.mox.StubOutWithMock(db, 'fixed_ip_associate_pool')
+ self.mox.StubOutWithMock(db, 'instance_get')
+ self.mox.StubOutWithMock(db,
+ 'virtual_interface_get_by_instance_and_network')
+ self.mox.StubOutWithMock(db, 'fixed_ip_update')
+
+ db.fixed_ip_update(mox.IgnoreArg(),
+ mox.IgnoreArg(),
+ mox.IgnoreArg())
+ db.virtual_interface_get_by_instance_and_network(mox.IgnoreArg(),
+ mox.IgnoreArg(), mox.IgnoreArg()).AndReturn({'id': 0})
+
+ db.instance_get(mox.IgnoreArg(),
+ mox.IgnoreArg()).AndReturn({'security_groups':
+ [{'id': 0}]})
+ db.fixed_ip_associate_pool(mox.IgnoreArg(),
+ mox.IgnoreArg(),
+ mox.IgnoreArg()).AndReturn('192.168.0.101')
+ db.network_get(mox.IgnoreArg(),
+ mox.IgnoreArg()).AndReturn(networks[0])
+ db.network_update(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg())
+ self.mox.ReplayAll()
+ self.network.add_fixed_ip_to_instance(self.context, 1, HOST,
+ networks[0]['id'])
+
class VlanNetworkTestCase(test.TestCase):
def setUp(self):
@@ -264,7 +286,8 @@ class VlanNetworkTestCase(test.TestCase):
db.fixed_ip_associate(mox.IgnoreArg(),
mox.IgnoreArg(),
- mox.IgnoreArg()).AndReturn('192.168.0.1')
+ mox.IgnoreArg(),
+ reserved=True).AndReturn('192.168.0.1')
db.fixed_ip_update(mox.IgnoreArg(),
mox.IgnoreArg(),
mox.IgnoreArg())
@@ -387,6 +410,32 @@ class VlanNetworkTestCase(test.TestCase):
mox.IgnoreArg(),
mox.IgnoreArg())
+ def test_add_fixed_ip_instance_without_vpn_requested_networks(self):
+ self.mox.StubOutWithMock(db, 'network_get')
+ self.mox.StubOutWithMock(db, 'fixed_ip_associate_pool')
+ self.mox.StubOutWithMock(db, 'instance_get')
+ self.mox.StubOutWithMock(db,
+ 'virtual_interface_get_by_instance_and_network')
+ self.mox.StubOutWithMock(db, 'fixed_ip_update')
+
+ db.fixed_ip_update(mox.IgnoreArg(),
+ mox.IgnoreArg(),
+ mox.IgnoreArg())
+ db.virtual_interface_get_by_instance_and_network(mox.IgnoreArg(),
+ mox.IgnoreArg(), mox.IgnoreArg()).AndReturn({'id': 0})
+
+ db.instance_get(mox.IgnoreArg(),
+ mox.IgnoreArg()).AndReturn({'security_groups':
+ [{'id': 0}]})
+ db.fixed_ip_associate_pool(mox.IgnoreArg(),
+ mox.IgnoreArg(),
+ mox.IgnoreArg()).AndReturn('192.168.0.101')
+ db.network_get(mox.IgnoreArg(),
+ mox.IgnoreArg()).AndReturn(networks[0])
+ self.mox.ReplayAll()
+ self.network.add_fixed_ip_to_instance(self.context, 1, HOST,
+ networks[0]['id'])
+
class CommonNetworkTestCase(test.TestCase):
diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py
new file mode 100644
index 000000000..0feec9b99
--- /dev/null
+++ b/nova/tests/test_quantum.py
@@ -0,0 +1,323 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 Nicira, Inc.
+# 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 context
+from nova import db
+from nova.db.sqlalchemy import models
+from nova.db.sqlalchemy.session import get_session
+from nova import exception
+from nova import ipv6
+from nova import log as logging
+from nova.network.quantum import manager as quantum_manager
+from nova import test
+from nova import utils
+
+LOG = logging.getLogger('nova.tests.quantum_network')
+
+
+# this class can be used for unit functional/testing on nova,
+# as it does not actually make remote calls to the Quantum service
+class FakeQuantumClientConnection(object):
+
+ def __init__(self):
+ self.nets = {}
+
+ def get_networks_for_tenant(self, tenant_id):
+ net_ids = []
+ for net_id, n in self.nets.items():
+ if n['tenant-id'] == tenant_id:
+ net_ids.append(net_id)
+ return net_ids
+
+ def create_network(self, tenant_id, network_name):
+
+ uuid = str(utils.gen_uuid())
+ self.nets[uuid] = {'net-name': network_name,
+ 'tenant-id': tenant_id,
+ 'ports': {}}
+ return uuid
+
+ def delete_network(self, tenant_id, net_id):
+ if self.nets[net_id]['tenant-id'] == tenant_id:
+ del self.nets[net_id]
+
+ def network_exists(self, tenant_id, net_id):
+ try:
+ return self.nets[net_id]['tenant-id'] == tenant_id
+ except KeyError:
+ return False
+
+ def _confirm_not_attached(self, interface_id):
+ for n in self.nets.values():
+ for p in n['ports'].values():
+ if p['attachment-id'] == interface_id:
+ raise Exception(_("interface '%s' is already attached" %
+ interface_id))
+
+ def create_and_attach_port(self, tenant_id, net_id, interface_id):
+ if not self.network_exists(tenant_id, net_id):
+ raise Exception(
+ _("network %(net_id)s does not exist for tenant %(tenant_id)"
+ % locals()))
+
+ self._confirm_not_attached(interface_id)
+ uuid = str(utils.gen_uuid())
+ self.nets[net_id]['ports'][uuid] = \
+ {"port-state": "ACTIVE",
+ "attachment-id": interface_id}
+
+ def detach_and_delete_port(self, tenant_id, net_id, port_id):
+ if not self.network_exists(tenant_id, net_id):
+ raise exception.NotFound(
+ _("network %(net_id)s does not exist "
+ "for tenant %(tenant_id)s" % locals()))
+ del self.nets[net_id]['ports'][port_id]
+
+ def get_port_by_attachment(self, tenant_id, attachment_id):
+ for net_id, n in self.nets.items():
+ if n['tenant-id'] == tenant_id:
+ for port_id, p in n['ports'].items():
+ if p['attachment-id'] == attachment_id:
+ return (net_id, port_id)
+
+ return (None, None)
+
+networks = [{'label': 'project1-net1',
+ 'injected': False,
+ 'multi_host': False,
+ 'cidr': '192.168.0.0/24',
+ 'cidr_v6': '2001:1db8::/64',
+ 'gateway_v6': '2001:1db8::1',
+ 'netmask_v6': '64',
+ 'netmask': '255.255.255.0',
+ 'bridge': None,
+ 'bridge_interface': None,
+ 'gateway': '192.168.0.1',
+ 'broadcast': '192.168.0.255',
+ 'dns1': '192.168.0.1',
+ 'dns2': '192.168.0.2',
+ 'vlan': None,
+ 'host': None,
+ 'vpn_public_address': None,
+ 'project_id': 'fake_project1',
+ 'priority': 1},
+ {'label': 'project2-net1',
+ 'injected': False,
+ 'multi_host': False,
+ 'cidr': '192.168.1.0/24',
+ 'cidr_v6': '2001:1db9::/64',
+ 'gateway_v6': '2001:1db9::1',
+ 'netmask_v6': '64',
+ 'netmask': '255.255.255.0',
+ 'bridge': None,
+ 'bridge_interface': None,
+ 'gateway': '192.168.1.1',
+ 'broadcast': '192.168.1.255',
+ 'dns1': '192.168.0.1',
+ 'dns2': '192.168.0.2',
+ 'vlan': None,
+ 'host': None,
+ 'project_id': 'fake_project2',
+ 'priority': 1},
+ {'label': "public",
+ 'injected': False,
+ 'multi_host': False,
+ 'cidr': '10.0.0.0/24',
+ 'cidr_v6': '2001:1dba::/64',
+ 'gateway_v6': '2001:1dba::1',
+ 'netmask_v6': '64',
+ 'netmask': '255.255.255.0',
+ 'bridge': None,
+ 'bridge_interface': None,
+ 'gateway': '10.0.0.1',
+ 'broadcast': '10.0.0.255',
+ 'dns1': '10.0.0.1',
+ 'dns2': '10.0.0.2',
+ 'vlan': None,
+ 'host': None,
+ 'project_id': None,
+ 'priority': 0},
+ {'label': "project2-net2",
+ 'injected': False,
+ 'multi_host': False,
+ 'cidr': '9.0.0.0/24',
+ 'cidr_v6': '2001:1dbb::/64',
+ 'gateway_v6': '2001:1dbb::1',
+ 'netmask_v6': '64',
+ 'netmask': '255.255.255.0',
+ 'bridge': None,
+ 'bridge_interface': None,
+ 'gateway': '9.0.0.1',
+ 'broadcast': '9.0.0.255',
+ 'dns1': '9.0.0.1',
+ 'dns2': '9.0.0.2',
+ 'vlan': None,
+ 'host': None,
+ 'project_id': "fake_project2",
+ 'priority': 2}]
+
+
+# this is a base class to be used by all other Quantum Test classes
+class QuantumTestCaseBase(object):
+
+ def test_create_and_delete_nets(self):
+ self._create_nets()
+ self._delete_nets()
+
+ def _create_nets(self):
+ for n in networks:
+ ctx = context.RequestContext('user1', n['project_id'])
+ self.net_man.create_networks(ctx,
+ label=n['label'], cidr=n['cidr'],
+ multi_host=n['multi_host'],
+ num_networks=1, network_size=256, cidr_v6=n['cidr_v6'],
+ gateway_v6=n['gateway_v6'], bridge=None,
+ bridge_interface=None, dns1=n['dns1'],
+ dns2=n['dns2'], project_id=n['project_id'],
+ priority=n['priority'])
+
+ def _delete_nets(self):
+ for n in networks:
+ ctx = context.RequestContext('user1', n['project_id'])
+ self.net_man.delete_network(ctx, n['cidr'])
+
+ def test_allocate_and_deallocate_instance_static(self):
+ self._create_nets()
+
+ project_id = "fake_project1"
+ ctx = context.RequestContext('user1', project_id)
+
+ instance_ref = db.api.instance_create(ctx,
+ {"project_id": project_id})
+ nw_info = self.net_man.allocate_for_instance(ctx,
+ instance_id=instance_ref['id'], host="",
+ instance_type_id=instance_ref['instance_type_id'],
+ project_id=project_id)
+
+ self.assertEquals(len(nw_info), 2)
+
+ # we don't know which order the NICs will be in until we
+ # introduce the notion of priority
+ # v4 cidr
+ self.assertTrue(nw_info[0][0]['cidr'].startswith("10."))
+ self.assertTrue(nw_info[1][0]['cidr'].startswith("192."))
+
+ # v4 address
+ self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("10."))
+ self.assertTrue(nw_info[1][1]['ips'][0]['ip'].startswith("192."))
+
+ # v6 cidr
+ self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1dba:"))
+ self.assertTrue(nw_info[1][0]['cidr_v6'].startswith("2001:1db8:"))
+
+ # v6 address
+ self.assertTrue(
+ nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dba:"))
+ self.assertTrue(
+ nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1db8:"))
+
+ self.net_man.deallocate_for_instance(ctx,
+ instance_id=instance_ref['id'],
+ project_id=project_id)
+
+ self._delete_nets()
+
+ def test_allocate_and_deallocate_instance_dynamic(self):
+ self._create_nets()
+ project_id = "fake_project2"
+ ctx = context.RequestContext('user1', project_id)
+
+ net_ids = self.net_man.q_conn.get_networks_for_tenant(project_id)
+ requested_networks = [(net_id, None) for net_id in net_ids]
+
+ self.net_man.validate_networks(ctx, requested_networks)
+
+ instance_ref = db.api.instance_create(ctx,
+ {"project_id": project_id})
+ nw_info = self.net_man.allocate_for_instance(ctx,
+ instance_id=instance_ref['id'], host="",
+ instance_type_id=instance_ref['instance_type_id'],
+ project_id=project_id,
+ requested_networks=requested_networks)
+
+ self.assertEquals(len(nw_info), 2)
+
+ # we don't know which order the NICs will be in until we
+ # introduce the notion of priority
+ # v4 cidr
+ self.assertTrue(nw_info[0][0]['cidr'].startswith("9.") or
+ nw_info[1][0]['cidr'].startswith("9."))
+ self.assertTrue(nw_info[0][0]['cidr'].startswith("192.") or
+ nw_info[1][0]['cidr'].startswith("192."))
+
+ # v4 address
+ self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("9.") or
+ nw_info[1][1]['ips'][0]['ip'].startswith("9."))
+ self.assertTrue(nw_info[0][1]['ips'][0]['ip'].startswith("192.") or
+ nw_info[1][1]['ips'][0]['ip'].startswith("192."))
+
+ # v6 cidr
+ self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1dbb:") or
+ nw_info[1][0]['cidr_v6'].startswith("2001:1dbb:"))
+ self.assertTrue(nw_info[0][0]['cidr_v6'].startswith("2001:1db9:") or
+ nw_info[1][0]['cidr_v6'].startswith("2001:1db9:"))
+
+ # v6 address
+ self.assertTrue(
+ nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1dbb:") or
+ nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1dbb:"))
+ self.assertTrue(
+ nw_info[0][1]['ip6s'][0]['ip'].startswith("2001:1db9:") or
+ nw_info[1][1]['ip6s'][0]['ip'].startswith("2001:1db9:"))
+
+ self.net_man.deallocate_for_instance(ctx,
+ instance_id=instance_ref['id'],
+ project_id=project_id)
+
+ self._delete_nets()
+
+ def test_validate_bad_network(self):
+ ctx = context.RequestContext('user1', 'fake_project1')
+ self.assertRaises(exception.NetworkNotFound,
+ self.net_man.validate_networks, ctx, [("", None)])
+
+
+class QuantumNovaIPAMTestCase(QuantumTestCaseBase, test.TestCase):
+
+ def setUp(self):
+ super(QuantumNovaIPAMTestCase, self).setUp()
+
+ self.net_man = quantum_manager.QuantumManager(
+ ipam_lib="nova.network.quantum.nova_ipam_lib",
+ q_conn=FakeQuantumClientConnection())
+
+ # Tests seem to create some networks by default, which
+ # we don't want. So we delete them.
+
+ ctx = context.RequestContext('user1', 'fake_project1').elevated()
+ for n in db.network_get_all(ctx):
+ db.network_delete_safe(ctx, n['id'])
+
+ # Other unit tests (e.g., test_compute.py) have a nasty
+ # habit of of creating fixed IPs and not cleaning up, which
+ # can confuse these tests, so we remove all existing fixed
+ # ips before starting.
+ session = get_session()
+ result = session.query(models.FixedIp).all()
+ with session.begin():
+ for fip_ref in result:
+ session.delete(fip_ref)
diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py
index 480247c91..440d3401b 100644
--- a/nova/tests/test_virt_drivers.py
+++ b/nova/tests/test_virt_drivers.py
@@ -103,8 +103,9 @@ class _VirtDriverTestCase(test.TestCase):
def test_reboot(self):
instance_ref = test_utils.get_test_instance()
network_info = test_utils.get_test_network_info()
+ reboot_type = "SOFT"
self.connection.spawn(self.ctxt, instance_ref, network_info)
- self.connection.reboot(instance_ref, network_info)
+ self.connection.reboot(instance_ref, network_info, reboot_type)
@catch_notimplementederror
def test_get_host_ip_addr(self):
diff --git a/nova/tests/test_vmwareapi.py b/nova/tests/test_vmwareapi.py
index 06daf46e8..e6da1690f 100644
--- a/nova/tests/test_vmwareapi.py
+++ b/nova/tests/test_vmwareapi.py
@@ -170,7 +170,8 @@ class VMWareAPIVMTestCase(test.TestCase):
self._create_vm()
info = self.conn.get_info(1)
self._check_vm_info(info, power_state.RUNNING)
- self.conn.reboot(self.instance, self.network_info)
+ reboot_type = "SOFT"
+ self.conn.reboot(self.instance, self.network_info, reboot_type)
info = self.conn.get_info(1)
self._check_vm_info(info, power_state.RUNNING)
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py
index 45dad3516..4a83d139e 100644
--- a/nova/tests/test_xenapi.py
+++ b/nova/tests/test_xenapi.py
@@ -494,6 +494,7 @@ class XenAPIVMTestCase(test.TestCase):
self.check_vm_params_for_linux_with_external_kernel()
def test_spawn_netinject_file(self):
+ self.flags(flat_injected=True)
db_fakes.stub_out_db_instance_api(self.stubs, injected=True)
self._tee_executed = False
@@ -611,7 +612,6 @@ class XenAPIVMTestCase(test.TestCase):
str(3 * 1024))
def test_rescue(self):
- self.flags(flat_injected=False)
instance = self._create_instance()
conn = xenapi_conn.get_connection(False)
conn.rescue(self.context, instance, None, [])
@@ -932,8 +932,9 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase):
self.fake_instance.architecture = 'x86-64'
def assert_disk_type(self, disk_type):
+ ctx = context.RequestContext('fake', 'fake')
dt = vm_utils.VMHelper.determine_disk_image_type(
- self.fake_instance)
+ self.fake_instance, ctx)
self.assertEqual(disk_type, dt)
def test_instance_disk(self):
diff --git a/nova/version.py b/nova/version.py
index 1f8d08e8c..06810df46 100644
--- a/nova/version.py
+++ b/nova/version.py
@@ -22,7 +22,7 @@ except ImportError:
'revno': 0}
-NOVA_VERSION = ['2011', '3']
+NOVA_VERSION = ['2012', '1']
YEAR, COUNT = NOVA_VERSION
FINAL = False # This becomes true at Release Candidate time
diff --git a/nova/virt/disk.py b/nova/virt/disk.py
index 52b2881e8..cd3422829 100644
--- a/nova/virt/disk.py
+++ b/nova/virt/disk.py
@@ -58,7 +58,7 @@ def extend(image, size):
file_size = os.path.getsize(image)
if file_size >= size:
return
- utils.execute('truncate', '-s', size, image)
+ utils.execute('qemu-img', 'resize', image, size)
# NOTE(vish): attempts to resize filesystem
utils.execute('e2fsck', '-fp', image, check_exit_code=False)
utils.execute('resize2fs', image, check_exit_code=False)
@@ -148,15 +148,17 @@ def destroy_container(target, instance, nbd=False):
LXC does not support qcow2 images yet.
"""
+ out, err = utils.execute('mount', run_as_root=True)
+ for loop in out.splitlines():
+ if instance['name'] in loop:
+ device = loop.split()[0]
+
try:
container_dir = '%s/rootfs' % target
utils.execute('umount', container_dir, run_as_root=True)
- finally:
- out, err = utils.execute('losetup', '-a', run_as_root=True)
- for loop in out.splitlines():
- if instance['name'] in loop:
- device = loop.split(loop, ':')
- _unlink_device(device, nbd)
+ _unlink_device(device, nbd)
+ except Exception, exn:
+ LOG.exception(_('Failed to remove container: %s'), exn)
def _link_device(image, nbd):
@@ -228,8 +230,8 @@ def _inject_metadata_into_fs(metadata, fs, execute=None):
metadata_path = os.path.join(fs, "meta.js")
metadata = dict([(m.key, m.value) for m in metadata])
- utils.execute('sudo', 'tee', metadata_path,
- process_input=json.dumps(metadata))
+ utils.execute('tee', metadata_path,
+ process_input=json.dumps(metadata), run_as_root=True)
def _inject_key_into_fs(key, fs, execute=None):
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index d05b51bd9..301346c6b 100644
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -165,12 +165,13 @@ class ComputeDriver(object):
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
- def reboot(self, instance, network_info):
+ def reboot(self, instance, network_info, reboot_type):
"""Reboot the specified instance.
:param instance: Instance object as returned by DB layer.
:param network_info:
:py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
+ :param reboot_type: Either a HARD or SOFT reboot
"""
# TODO(Vek): Need to pass context in for access to auth_token
raise NotImplementedError()
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index d5e2bf31b..3596d8353 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -103,7 +103,7 @@ class FakeConnection(driver.ComputeDriver):
if not instance['name'] in self.instances:
raise exception.InstanceNotRunning()
- def reboot(self, instance, network_info):
+ def reboot(self, instance, network_info, reboot_type):
pass
def get_host_ip_addr(self):
diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py
index 03a78db1f..76925b405 100644
--- a/nova/virt/hyperv.py
+++ b/nova/virt/hyperv.py
@@ -367,7 +367,7 @@ class HyperVConnection(driver.ComputeDriver):
wmi_obj.Properties_.Item(prop).Value
return newinst
- def reboot(self, instance, network_info):
+ def reboot(self, instance, network_info, reboot_type):
"""Reboot the specified instance."""
vm = self._lookup(instance.name)
if vm is None:
diff --git a/nova/virt/images.py b/nova/virt/images.py
index 54c691a40..810b359d9 100644
--- a/nova/virt/images.py
+++ b/nova/virt/images.py
@@ -37,7 +37,8 @@ def fetch(context, image_href, path, _user_id, _project_id):
# when it is added to glance. Right now there is no
# auth checking in glance, so we assume that access was
# checked before we got here.
- (image_service, image_id) = nova.image.get_image_service(image_href)
+ (image_service, image_id) = nova.image.get_image_service(context,
+ image_href)
with open(path, "wb") as image_file:
metadata = image_service.get(context, image_id, image_file)
return metadata
diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py
index 363a20ed0..f591ce02c 100644
--- a/nova/virt/libvirt/connection.py
+++ b/nova/virt/libvirt/connection.py
@@ -196,7 +196,7 @@ class LibvirtConnection(driver.ComputeDriver):
def _test_connection(self):
try:
- self._wrapped_conn.getInfo()
+ self._wrapped_conn.getCapabilities()
return True
except libvirt.libvirtError as e:
if e.get_error_code() == libvirt.VIR_ERR_SYSTEM_ERROR and \
@@ -398,10 +398,10 @@ class LibvirtConnection(driver.ComputeDriver):
virt_dom = self._lookup_by_name(instance['name'])
(image_service, image_id) = nova.image.get_image_service(
- instance['image_ref'])
+ context, instance['image_ref'])
base = image_service.show(context, image_id)
(snapshot_image_service, snapshot_image_id) = \
- nova.image.get_image_service(image_href)
+ nova.image.get_image_service(context, image_href)
snapshot = snapshot_image_service.show(context, snapshot_image_id)
metadata = {'is_public': False,
@@ -1696,7 +1696,7 @@ class LibvirtConnection(driver.ComputeDriver):
base = os.path.basename(info['path'])
# Get image type and create empty disk image.
instance_disk = os.path.join(instance_dir, base)
- utils.execute('sudo', 'qemu-img', 'create', '-f', info['type'],
+ utils.execute('qemu-img', 'create', '-f', info['type'],
instance_disk, info['local_gb'])
# if image has kernel and ramdisk, just download
@@ -1788,7 +1788,7 @@ class LibvirtConnection(driver.ComputeDriver):
if disk_type == 'raw':
size = int(os.path.getsize(path))
else:
- out, err = utils.execute('sudo', 'qemu-img', 'info', path)
+ out, err = utils.execute('qemu-img', 'info', path)
size = [i.split('(')[1].split()[0] for i in out.split('\n')
if i.strip().find('virtual size') >= 0]
size = int(size[0])
diff --git a/nova/virt/libvirt/firewall.py b/nova/virt/libvirt/firewall.py
index c2f4f91e8..0db10c7ce 100644
--- a/nova/virt/libvirt/firewall.py
+++ b/nova/virt/libvirt/firewall.py
@@ -338,8 +338,8 @@ class NWFilterFirewall(FirewallDriver):
'nova-allow-dhcp-server']
if FLAGS.use_ipv6:
- networks = [network for (network, _m) in network_info if
- network['gateway_v6']]
+ networks = [network for (network, info) in network_info if
+ info['gateway6']]
if networks:
instance_secgroup_filter_children.\
diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py
index 0b7438011..077c32474 100644
--- a/nova/virt/libvirt/vif.py
+++ b/nova/virt/libvirt/vif.py
@@ -101,7 +101,7 @@ class LibvirtOpenVswitchDriver(VIFDriver):
"""VIF driver for Open vSwitch."""
def get_dev_name(_self, iface_id):
- return "tap-" + iface_id[0:15]
+ return "tap" + iface_id[0:11]
def plug(self, instance, network, mapping):
iface_id = mapping['vif_uuid']
diff --git a/nova/virt/vmwareapi/fake.py b/nova/virt/vmwareapi/fake.py
index 4c62d18bb..0dea13aba 100644
--- a/nova/virt/vmwareapi/fake.py
+++ b/nova/virt/vmwareapi/fake.py
@@ -412,7 +412,7 @@ def fake_get_network(*args, **kwargs):
return [{'type': 'fake'}]
-def fake_fetch_image(image, instance, **kwargs):
+def fake_fetch_image(context, image, instance, **kwargs):
"""Fakes fetch image call. Just adds a reference to the db for the file."""
ds_name = kwargs.get("datastore_name")
file_path = kwargs.get("file_path")
@@ -420,12 +420,12 @@ def fake_fetch_image(image, instance, **kwargs):
_add_file(ds_file_path)
-def fake_upload_image(image, instance, **kwargs):
+def fake_upload_image(context, image, instance, **kwargs):
"""Fakes the upload of an image."""
pass
-def fake_get_vmdk_size_and_properties(image_id, instance):
+def fake_get_vmdk_size_and_properties(context, image_id, instance):
"""Fakes the file size and properties fetch for the image file."""
props = {"vmware_ostype": "otherGuest",
"vmware_adaptertype": "lsiLogic"}
diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py
index 07a6ba6ab..6bdc2f23a 100644
--- a/nova/virt/vmwareapi/vmops.py
+++ b/nova/virt/vmwareapi/vmops.py
@@ -157,7 +157,7 @@ class VMWareVMOps(object):
repository.
"""
image_size, image_properties = \
- vmware_images.get_vmdk_size_and_properties(
+ vmware_images.get_vmdk_size_and_properties(context,
instance.image_ref, instance)
vmdk_file_size_in_kb = int(image_size) / 1024
os_type = image_properties.get("vmware_ostype", "otherGuest")
@@ -282,6 +282,7 @@ class VMWareVMOps(object):
# Upload the -flat.vmdk file whose meta-data file we just created
# above
vmware_images.fetch_image(
+ context,
instance.image_ref,
instance,
host=self._session._host_ip,
@@ -448,6 +449,7 @@ class VMWareVMOps(object):
# Upload the contents of -flat.vmdk file which has the disk data.
LOG.debug(_("Uploading image %s") % snapshot_name)
vmware_images.upload_image(
+ context,
snapshot_name,
instance,
os_type=os_type,
diff --git a/nova/virt/vmwareapi/vmware_images.py b/nova/virt/vmwareapi/vmware_images.py
index f5f75dae2..53f2d372e 100644
--- a/nova/virt/vmwareapi/vmware_images.py
+++ b/nova/virt/vmwareapi/vmware_images.py
@@ -20,15 +20,13 @@ Utility functions for Image transfer.
from nova import exception
from nova import flags
-import nova.image
+from nova.image import glance
from nova import log as logging
from nova.virt.vmwareapi import io_util
from nova.virt.vmwareapi import read_write_util
LOG = logging.getLogger("nova.virt.vmwareapi.vmware_images")
-FLAGS = flags.FLAGS
-
QUEUE_BUFFER_SIZE = 10
@@ -87,36 +85,10 @@ def start_transfer(read_file_handle, data_size, write_file_handle=None,
write_file_handle.close()
-def fetch_image(image, instance, **kwargs):
- """Fetch an image for attaching to the newly created VM."""
- # Depending upon the image service, make appropriate image service call
- if FLAGS.image_service == "nova.image.glance.GlanceImageService":
- func = _get_glance_image
- elif FLAGS.image_service == "nova.image.s3.S3ImageService":
- func = _get_s3_image
- else:
- raise NotImplementedError(_("The Image Service %s is not implemented")
- % FLAGS.image_service)
- return func(image, instance, **kwargs)
-
-
-def upload_image(image, instance, **kwargs):
- """Upload the newly snapshotted VM disk file."""
- # Depending upon the image service, make appropriate image service call
- if FLAGS.image_service == "nova.image.glance.GlanceImageService":
- func = _put_glance_image
- elif FLAGS.image_service == "nova.image.s3.S3ImageService":
- func = _put_s3_image
- else:
- raise NotImplementedError(_("The Image Service %s is not implemented")
- % FLAGS.image_service)
- return func(image, instance, **kwargs)
-
-
-def _get_glance_image(image, instance, **kwargs):
+def fetch_image(context, image, instance, **kwargs):
"""Download image from the glance image server."""
LOG.debug(_("Downloading image %s from glance image server") % image)
- (glance_client, image_id) = nova.image.get_glance_client(image)
+ (glance_client, image_id) = glance.get_glance_client(context, image)
metadata, read_iter = glance_client.get_image(image_id)
read_file_handle = read_write_util.GlanceFileRead(read_iter)
file_size = int(metadata['size'])
@@ -132,17 +104,7 @@ def _get_glance_image(image, instance, **kwargs):
LOG.debug(_("Downloaded image %s from glance image server") % image)
-def _get_s3_image(image, instance, **kwargs):
- """Download image from the S3 image server."""
- raise NotImplementedError
-
-
-def _get_local_image(image, instance, **kwargs):
- """Download image from the local nova compute node."""
- raise NotImplementedError
-
-
-def _put_glance_image(image, instance, **kwargs):
+def upload_image(context, image, instance, **kwargs):
"""Upload the snapshotted vm disk file to Glance image server."""
LOG.debug(_("Uploading image %s to the Glance image server") % image)
read_file_handle = read_write_util.VmWareHTTPReadFile(
@@ -152,7 +114,7 @@ def _put_glance_image(image, instance, **kwargs):
kwargs.get("cookies"),
kwargs.get("file_path"))
file_size = read_file_handle.get_size()
- (glance_client, image_id) = nova.image.get_glance_client(image)
+ (glance_client, image_id) = glance.get_glance_client(context, image)
# The properties and other fields that we need to set for the image.
image_metadata = {"is_public": True,
"disk_format": "vmdk",
@@ -168,17 +130,7 @@ def _put_glance_image(image, instance, **kwargs):
LOG.debug(_("Uploaded image %s to the Glance image server") % image)
-def _put_local_image(image, instance, **kwargs):
- """Upload the snapshotted vm disk file to the local nova compute node."""
- raise NotImplementedError
-
-
-def _put_s3_image(image, instance, **kwargs):
- """Upload the snapshotted vm disk file to S3 image server."""
- raise NotImplementedError
-
-
-def get_vmdk_size_and_properties(image, instance):
+def get_vmdk_size_and_properties(context, image, instance):
"""
Get size of the vmdk file that is to be downloaded for attach in spawn.
Need this to create the dummy virtual disk for the meta-data file. The
@@ -186,12 +138,9 @@ def get_vmdk_size_and_properties(image, instance):
"""
LOG.debug(_("Getting image size for the image %s") % image)
- if FLAGS.image_service == "nova.image.glance.GlanceImageService":
- (glance_client, image_id) = nova.image.get_glance_client(image)
- meta_data = glance_client.get_image_meta(image_id)
- size, properties = meta_data["size"], meta_data["properties"]
- elif FLAGS.image_service == "nova.image.s3.S3ImageService":
- raise NotImplementedError
+ (glance_client, image_id) = glance.get_glance_client(context, image)
+ meta_data = glance_client.get_image_meta(image_id)
+ size, properties = meta_data["size"], meta_data["properties"]
LOG.debug(_("Got image size of %(size)s for the image %(image)s") %
locals())
return size, properties
diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py
index 243ee64f5..fa89a8f45 100644
--- a/nova/virt/vmwareapi_conn.py
+++ b/nova/virt/vmwareapi_conn.py
@@ -133,7 +133,7 @@ class VMWareESXConnection(driver.ComputeDriver):
"""Create snapshot from a running VM instance."""
self._vmops.snapshot(context, instance, name)
- def reboot(self, instance, network_info):
+ def reboot(self, instance, network_info, reboot_type):
"""Reboot VM instance."""
self._vmops.reboot(instance, network_info)
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index efbea7076..302238c98 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -31,12 +31,10 @@ import urllib
import uuid
from xml.dom import minidom
-import glance.client
from nova import db
from nova import exception
from nova import flags
-import nova.image
-from nova.image import glance as glance_image_service
+from nova.image import glance
from nova import log as logging
from nova import utils
from nova.compute import instance_types
@@ -383,8 +381,7 @@ class VMHelper(HelperBase):
os_type = instance.os_type or FLAGS.default_os_type
- glance_host, glance_port = \
- glance_image_service.pick_glance_api_server()
+ glance_host, glance_port = glance.pick_glance_api_server()
params = {'vdi_uuids': vdi_uuids,
'image_id': image_id,
'glance_host': glance_host,
@@ -447,8 +444,7 @@ class VMHelper(HelperBase):
# pass them as arguments
uuid_stack = [str(uuid.uuid4()) for i in xrange(2)]
- glance_host, glance_port = \
- glance_image_service.pick_glance_api_server()
+ glance_host, glance_port = glance.pick_glance_api_server()
params = {'image_id': image,
'glance_host': glance_host,
'glance_port': glance_port,
@@ -546,7 +542,7 @@ class VMHelper(HelperBase):
else:
sr_ref = safe_find_sr(session)
- glance_client, image_id = nova.image.get_glance_client(image)
+ glance_client, image_id = glance.get_glance_client(context, image)
glance_client.set_auth_token(getattr(context, 'auth_token', None))
meta, image_file = glance_client.get_image(image_id)
virtual_size = int(meta['size'])
@@ -606,7 +602,7 @@ class VMHelper(HelperBase):
raise e
@classmethod
- def determine_disk_image_type(cls, instance):
+ def determine_disk_image_type(cls, instance, context):
"""Disk Image Types are used to determine where the kernel will reside
within an image. To figure out which type we're dealing with, we use
the following rules:
@@ -639,7 +635,8 @@ class VMHelper(HelperBase):
'vhd': ImageType.DISK_VHD,
'iso': ImageType.DISK_ISO}
image_ref = instance.image_ref
- glance_client, image_id = nova.image.get_glance_client(image_ref)
+ glance_client, image_id = glance.get_glance_client(context,
+ image_ref)
meta = glance_client.get_image_meta(image_id)
disk_format = meta['disk_format']
try:
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index c5f105f40..fb9c602d9 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -135,7 +135,7 @@ class VMOps(object):
self._session.call_xenapi('VM.start', vm_ref, False, False)
def _create_disks(self, context, instance):
- disk_image_type = VMHelper.determine_disk_image_type(instance)
+ disk_image_type = VMHelper.determine_disk_image_type(instance, context)
vdis = VMHelper.fetch_image(context, self._session,
instance, instance.image_ref,
instance.user_id, instance.project_id,
@@ -176,7 +176,7 @@ class VMOps(object):
power_state.SHUTDOWN)
return
- disk_image_type = VMHelper.determine_disk_image_type(instance)
+ disk_image_type = VMHelper.determine_disk_image_type(instance, context)
kernel = None
ramdisk = None
try:
@@ -188,9 +188,16 @@ class VMOps(object):
ramdisk = VMHelper.fetch_image(context, self._session,
instance, instance.ramdisk_id, instance.user_id,
instance.project_id, ImageType.RAMDISK)[0]
- # Create the VM ref and attach the first disk
- first_vdi_ref = self._session.call_xenapi('VDI.get_by_uuid',
- vdis[0]['vdi_uuid'])
+
+ # NOTE(jk0): Since vdi_type may contain either 'os' or 'swap', we
+ # need to ensure that the 'swap' VDI is not chosen as the mount
+ # point for file injection.
+ first_vdi_ref = None
+ for vdi in vdis:
+ if vdi.get('vdi_type') != 'swap':
+ # Create the VM ref and attach the first disk
+ first_vdi_ref = self._session.call_xenapi(
+ 'VDI.get_by_uuid', vdi['vdi_uuid'])
vm_mode = instance.vm_mode and instance.vm_mode.lower()
if vm_mode == 'pv':
@@ -617,10 +624,15 @@ class VMOps(object):
str(new_disk_size))
LOG.debug(_("Resize instance %s complete") % (instance.name))
- def reboot(self, instance):
+ def reboot(self, instance, reboot_type):
"""Reboot VM instance."""
vm_ref = self._get_vm_opaque_ref(instance)
- task = self._session.call_xenapi('Async.VM.clean_reboot', vm_ref)
+
+ if reboot_type == "HARD":
+ task = self._session.call_xenapi('Async.VM.hard_reboot', vm_ref)
+ else:
+ task = self._session.call_xenapi('Async.VM.clean_reboot', vm_ref)
+
self._session.wait_for_task(task, instance.id)
def get_agent_version(self, instance, timeout=None):
diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py
index 0d23e7689..f6dbc19f8 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -203,9 +203,9 @@ class XenAPIConnection(driver.ComputeDriver):
""" Create snapshot from a running VM instance """
self._vmops.snapshot(context, instance, image_id)
- def reboot(self, instance, network_info):
+ def reboot(self, instance, network_info, reboot_type):
"""Reboot VM instance"""
- self._vmops.reboot(instance)
+ self._vmops.reboot(instance, reboot_type)
def set_admin_password(self, instance, new_pass):
"""Set the root/admin password on the VM instance"""