summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Meade <alex.meade@rackspace.com>2011-08-15 09:57:55 -0400
committerAlex Meade <alex.meade@rackspace.com>2011-08-15 09:57:55 -0400
commitfaa7239eea756bfd337a7d9c3e0ebb63cbbe58a2 (patch)
tree0490f073f346abaaaccc94eb627ab651c05bb845
parenta1dc7e0dbcff7130adb0274e6628ce30d1ac83c1 (diff)
parent1fe628fbe1f9964ac4536ce1c859d84d9cd8cb08 (diff)
downloadnova-faa7239eea756bfd337a7d9c3e0ebb63cbbe58a2.tar.gz
nova-faa7239eea756bfd337a7d9c3e0ebb63cbbe58a2.tar.xz
nova-faa7239eea756bfd337a7d9c3e0ebb63cbbe58a2.zip
merged trunk
-rw-r--r--Authors2
-rwxr-xr-xbin/nova-manage14
-rw-r--r--nova/api/ec2/__init__.py4
-rw-r--r--nova/api/openstack/create_instance_helper.py4
-rw-r--r--nova/api/openstack/servers.py29
-rw-r--r--nova/compute/api.py14
-rw-r--r--nova/db/sqlalchemy/models.py5
-rw-r--r--nova/network/manager.py87
-rw-r--r--nova/scheduler/manager.py9
-rw-r--r--nova/scheduler/multi.py73
-rw-r--r--nova/tests/api/openstack/test_extensions.py2
-rw-r--r--nova/tests/api/openstack/test_server_actions.py20
-rw-r--r--nova/tests/api/openstack/test_servers.py29
-rw-r--r--nova/tests/glance/stubs.py6
-rw-r--r--nova/tests/scheduler/test_scheduler.py24
-rw-r--r--nova/tests/test_libvirt.py41
-rw-r--r--nova/tests/test_network.py180
-rw-r--r--nova/tests/test_xenapi.py5
-rw-r--r--nova/virt/libvirt/firewall.py35
-rw-r--r--nova/virt/xenapi/fake.py20
-rw-r--r--nova/virt/xenapi/vm_utils.py109
-rw-r--r--nova/virt/xenapi/vmops.py56
22 files changed, 683 insertions, 85 deletions
diff --git a/Authors b/Authors
index e639cbf76..531994cb0 100644
--- a/Authors
+++ b/Authors
@@ -27,6 +27,7 @@ David Pravec <David.Pravec@danix.org>
Dean Troyer <dtroyer@gmail.com>
Devendra Modium <dmodium@isi.edu>
Devin Carlen <devin.carlen@gmail.com>
+Donal Lafferty <donal.lafferty@citrix.com>
Ed Leafe <ed@leafe.com>
Eldar Nugaev <reldan@oscloud.ru>
Eric Day <eday@oddments.org>
@@ -103,6 +104,7 @@ Tushar Patil <tushar.vitthal.patil@gmail.com>
Vasiliy Shlykov <vash@vasiliyshlykov.org>
Vishvananda Ishaya <vishvananda@gmail.com>
Vivek Y S <vivek.ys@gmail.com>
+Vladimir Popovski <vladimir@zadarastorage.com>
William Wolf <throughnothing@gmail.com>
Yoshiaki Tamura <yoshi@midokura.jp>
Youcef Laribi <Youcef.Laribi@eu.citrix.com>
diff --git a/bin/nova-manage b/bin/nova-manage
index 077a89d6f..02591a49c 100755
--- a/bin/nova-manage
+++ b/bin/nova-manage
@@ -882,6 +882,14 @@ class ServiceCommands(object):
services = [s for s in services if s['host'] == host]
if service:
services = [s for s in services if s['binary'] == service]
+ print_format = "%-16s %-36s %-16s %-10s %-5s %-10s"
+ print print_format % (
+ _('Binary'),
+ _('Host'),
+ _('Zone'),
+ _('Status'),
+ _('State'),
+ _('Updated_At'))
for svc in services:
delta = now - (svc['updated_at'] or svc['created_at'])
alive = (delta.seconds <= 15)
@@ -889,9 +897,9 @@ class ServiceCommands(object):
active = 'enabled'
if svc['disabled']:
active = 'disabled'
- print "%-10s %-10s %-8s %s %s" % (svc['host'], svc['binary'],
- active, art,
- svc['updated_at'])
+ print print_format % (svc['binary'], svc['host'],
+ svc['availability_zone'], active, art,
+ svc['updated_at'])
@args('--host', dest='host', metavar='<host>', help='Host')
@args('--service', dest='service', metavar='<service>',
diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py
index 8b6e47cfb..96df97393 100644
--- a/nova/api/ec2/__init__.py
+++ b/nova/api/ec2/__init__.py
@@ -358,6 +358,10 @@ class Executor(wsgi.Application):
LOG.debug(_('InvalidParameterValue raised: %s'), unicode(ex),
context=context)
return self._error(req, context, type(ex).__name__, unicode(ex))
+ except exception.InvalidPortRange as ex:
+ LOG.debug(_('InvalidPortRange raised: %s'), unicode(ex),
+ context=context)
+ return self._error(req, context, type(ex).__name__, unicode(ex))
except Exception as ex:
extra = {'environment': req.environ}
LOG.exception(_('Unexpected error raised: %s'), unicode(ex),
diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py
index 1425521a9..4e1da549e 100644
--- a/nova/api/openstack/create_instance_helper.py
+++ b/nova/api/openstack/create_instance_helper.py
@@ -122,6 +122,7 @@ class CreateInstanceHelper(object):
raise exc.HTTPBadRequest(explanation=msg)
zone_blob = server_dict.get('blob')
+ availability_zone = server_dict.get('availability_zone')
name = server_dict['name']
self._validate_server_name(name)
name = name.strip()
@@ -161,7 +162,8 @@ class CreateInstanceHelper(object):
zone_blob=zone_blob,
reservation_id=reservation_id,
min_count=min_count,
- max_count=max_count))
+ max_count=max_count,
+ availability_zone=availability_zone))
except quota.QuotaError as error:
self._handle_quota_error(error)
except exception.ImageNotFound as error:
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index fec319d8e..49903d503 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -163,7 +163,7 @@ class Controller(object):
@scheduler_api.redirect_handler
def update(self, req, id, body):
- """ Updates the server name or password """
+ """Update server name then pass on to version-specific controller"""
if len(req.body) == 0:
raise exc.HTTPUnprocessableEntity()
@@ -178,17 +178,15 @@ class Controller(object):
self.helper._validate_server_name(name)
update_dict['display_name'] = name.strip()
- self._parse_update(ctxt, id, body, update_dict)
-
try:
self.compute_api.update(ctxt, id, **update_dict)
except exception.NotFound:
raise exc.HTTPNotFound()
- return exc.HTTPNoContent()
+ return self._update(ctxt, req, id, body)
- def _parse_update(self, context, id, inst_dict, update_dict):
- pass
+ def _update(self, context, req, id, inst_dict):
+ return exc.HTTPNotImplemented()
@scheduler_api.redirect_handler
def action(self, req, id, body):
@@ -210,11 +208,15 @@ class Controller(object):
}
self.actions.update(admin_actions)
- for key in self.actions.keys():
- if key in body:
+ for key in body:
+ if key in self.actions:
return self.actions[key](body, req, id)
+ else:
+ msg = _("There is no such server action: %s") % (key,)
+ raise exc.HTTPBadRequest(explanation=msg)
- raise exc.HTTPNotImplemented()
+ msg = _("Invalid request body")
+ raise exc.HTTPBadRequest(explanation=msg)
def _action_create_backup(self, input_dict, req, instance_id):
"""Backup a server instance.
@@ -568,10 +570,11 @@ class ControllerV10(Controller):
def _limit_items(self, items, req):
return common.limited(items, req)
- def _parse_update(self, context, server_id, inst_dict, update_dict):
+ def _update(self, context, req, id, inst_dict):
if 'adminPass' in inst_dict['server']:
- self.compute_api.set_admin_password(context, server_id,
+ self.compute_api.set_admin_password(context, id,
inst_dict['server']['adminPass'])
+ return exc.HTTPNoContent()
def _action_resize(self, input_dict, req, id):
""" Resizes a given instance to the flavor size requested """
@@ -695,6 +698,10 @@ class ControllerV11(Controller):
LOG.info(msg)
raise exc.HTTPBadRequest(explanation=msg)
+ def _update(self, context, req, id, inst_dict):
+ instance = self.compute_api.routing_get(context, id)
+ return self._build_view(req, instance, is_detail=True)
+
def _action_resize(self, input_dict, req, id):
""" Resizes a given instance to the flavor size requested """
try:
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 91a0c93b2..e909e9959 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -393,10 +393,6 @@ class API(base.Base):
updates['hostname'] = self.hostname_factory(instance)
instance = self.update(context, instance_id, **updates)
-
- for group_id in security_groups:
- self.trigger_security_group_members_refresh(elevated, group_id)
-
return instance
def _ask_scheduler_to_create_instance(self, context, base_options,
@@ -565,18 +561,20 @@ class API(base.Base):
{"method": "refresh_security_group_rules",
"args": {"security_group_id": security_group.id}})
- def trigger_security_group_members_refresh(self, context, group_id):
+ def trigger_security_group_members_refresh(self, context, group_ids):
"""Called when a security group gains a new or loses a member.
Sends an update request to each compute node for whom this is
relevant.
"""
- # First, we get the security group rules that reference this group as
+ # First, we get the security group rules that reference these groups as
# the grantee..
- security_group_rules = \
+ security_group_rules = set()
+ for group_id in group_ids:
+ security_group_rules.update(
self.db.security_group_rule_get_by_security_group_grantee(
context,
- group_id)
+ group_id))
# ..then we distill the security groups to which they belong..
security_groups = set()
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index 939fde199..64b1bd5cd 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -479,6 +479,11 @@ class SecurityGroupIngressRule(BASE, NovaBase):
# Note: This is not the parent SecurityGroup. It's SecurityGroup we're
# granting access for.
group_id = Column(Integer, ForeignKey('security_groups.id'))
+ grantee_group = relationship("SecurityGroup",
+ foreign_keys=group_id,
+ primaryjoin='and_('
+ 'SecurityGroupIngressRule.group_id == SecurityGroup.id,'
+ 'SecurityGroupIngressRule.deleted == False)')
class ProviderFirewallRule(BASE, NovaBase):
diff --git a/nova/network/manager.py b/nova/network/manager.py
index 8fc6a295f..b1b3f8ba2 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -61,6 +61,7 @@ from nova import quota
from nova import utils
from nova import rpc
from nova.network import api as network_api
+from nova.compute import api as compute_api
import random
@@ -313,6 +314,7 @@ class NetworkManager(manager.SchedulerDependentManager):
network_driver = FLAGS.network_driver
self.driver = utils.import_object(network_driver)
self.network_api = network_api.API()
+ self.compute_api = compute_api.API()
super(NetworkManager, self).__init__(service_name='network',
*args, **kwargs)
@@ -368,6 +370,15 @@ class NetworkManager(manager.SchedulerDependentManager):
self.host)
return host
+ def _do_trigger_security_group_members_refresh_for_instance(self,
+ instance_id):
+ admin_context = context.get_admin_context()
+ instance_ref = self.db.instance_get(admin_context, instance_id)
+ groups = instance_ref['security_groups']
+ group_ids = [group['id'] for group in groups]
+ self.compute_api.trigger_security_group_members_refresh(admin_context,
+ group_ids)
+
def _get_networks_for_instance(self, context, instance_id, project_id):
"""Determine & return which networks an instance should connect to."""
# TODO(tr3buchet) maybe this needs to be updated in the future if
@@ -559,6 +570,8 @@ class NetworkManager(manager.SchedulerDependentManager):
address = self.db.fixed_ip_associate_pool(context.elevated(),
network['id'],
instance_id)
+ self._do_trigger_security_group_members_refresh_for_instance(
+ instance_id)
get_vif = self.db.virtual_interface_get_by_instance_and_network
vif = get_vif(context, instance_id, network['id'])
values = {'allocated': True,
@@ -573,6 +586,11 @@ class NetworkManager(manager.SchedulerDependentManager):
self.db.fixed_ip_update(context, address,
{'allocated': False,
'virtual_interface_id': None})
+ fixed_ip_ref = self.db.fixed_ip_get_by_address(context, address)
+ instance_ref = fixed_ip_ref['instance']
+ instance_id = instance_ref['id']
+ self._do_trigger_security_group_members_refresh_for_instance(
+ instance_id)
def lease_fixed_ip(self, context, address):
"""Called by dhcp-bridge when ip is leased."""
@@ -614,6 +632,64 @@ class NetworkManager(manager.SchedulerDependentManager):
network_ref = self.db.fixed_ip_get_network(context, address)
self._setup_network(context, network_ref)
+ def _validate_cidrs(self, context, cidr, num_networks, network_size):
+ significant_bits = 32 - int(math.log(network_size, 2))
+ req_net = netaddr.IPNetwork(cidr)
+ req_net_ip = str(req_net.ip)
+ req_size = network_size * num_networks
+ if req_size > req_net.size:
+ msg = _("network_size * num_networks exceeds cidr size")
+ raise ValueError(msg)
+ adjusted_cidr_str = req_net_ip + '/' + str(significant_bits)
+ adjusted_cidr = netaddr.IPNetwork(adjusted_cidr_str)
+ try:
+ used_nets = self.db.network_get_all(context)
+ except exception.NoNetworksFound:
+ used_nets = []
+ used_cidrs = [netaddr.IPNetwork(net['cidr']) for net in used_nets]
+ if adjusted_cidr in used_cidrs:
+ raise ValueError(_("cidr already in use"))
+ for adjusted_cidr_supernet in adjusted_cidr.supernet():
+ if adjusted_cidr_supernet in used_cidrs:
+ msg = _("requested cidr (%s) conflicts with existing supernet")
+ raise ValueError(msg % str(adjusted_cidr))
+ # watch for smaller subnets conflicting
+ used_supernets = []
+ for used_cidr in used_cidrs:
+ if not used_cidr:
+ continue
+ if used_cidr.size < network_size:
+ for ucsupernet in used_cidr.supernet():
+ if ucsupernet.size == network_size:
+ used_supernets.append(ucsupernet)
+ all_req_nets = []
+ if num_networks == 1:
+ if adjusted_cidr in used_supernets:
+ msg = _("requested cidr (%s) conflicts with existing smaller"
+ " cidr")
+ raise ValueError(msg % str(adjusted_cidr))
+ else:
+ all_req_nets.append(adjusted_cidr)
+ elif num_networks >= 2:
+ # split supernet into subnets
+ next_cidr = adjusted_cidr
+ for index in range(num_networks):
+ if next_cidr.first > req_net.last:
+ msg = _("Not enough subnets avail to satisfy requested "
+ "num_net works - some subnets in requested range"
+ " already in use")
+ raise ValueError(msg)
+ while True:
+ used_values = used_cidrs + used_supernets
+ if next_cidr in used_values:
+ next_cidr = next_cidr.next()
+ else:
+ all_req_nets.append(next_cidr)
+ next_cidr = next_cidr.next()
+ break
+ all_req_nets = sorted(list(set(all_req_nets)))
+ return all_req_nets
+
def create_networks(self, context, label, cidr, multi_host, num_networks,
network_size, cidr_v6, gateway_v6, bridge,
bridge_interface, dns1=None, dns2=None, **kwargs):
@@ -624,8 +700,8 @@ class NetworkManager(manager.SchedulerDependentManager):
network_size_v6 = 1 << 64
if cidr:
- fixed_net = netaddr.IPNetwork(cidr)
- significant_bits = 32 - int(math.log(network_size, 2))
+ req_cidrs = self._validate_cidrs(context, cidr, num_networks,
+ network_size)
for index in range(num_networks):
net = {}
@@ -635,9 +711,7 @@ class NetworkManager(manager.SchedulerDependentManager):
net['dns2'] = dns2
if cidr:
- start = index * network_size
- project_net = netaddr.IPNetwork('%s/%s' % (fixed_net[start],
- significant_bits))
+ project_net = req_cidrs[index]
net['cidr'] = str(project_net)
net['multi_host'] = multi_host
net['netmask'] = str(project_net.netmask)
@@ -857,7 +931,8 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager):
address = self.db.fixed_ip_associate_pool(context,
network['id'],
instance_id)
-
+ self._do_trigger_security_group_members_refresh_for_instance(
+ instance_id)
vif = self.db.virtual_interface_get_by_instance_and_network(context,
instance_id,
network['id'])
diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py
index c8b16b622..fcc318e59 100644
--- a/nova/scheduler/manager.py
+++ b/nova/scheduler/manager.py
@@ -34,12 +34,13 @@ from nova.scheduler import zone_manager
LOG = logging.getLogger('nova.scheduler.manager')
FLAGS = flags.FLAGS
flags.DEFINE_string('scheduler_driver',
- 'nova.scheduler.chance.ChanceScheduler',
- 'Driver to use for the scheduler')
+ 'nova.scheduler.multi.MultiScheduler',
+ 'Default driver to use for the scheduler')
class SchedulerManager(manager.Manager):
"""Chooses a host to run instances on."""
+
def __init__(self, scheduler_driver=None, *args, **kwargs):
self.zone_manager = zone_manager.ZoneManager()
if not scheduler_driver:
@@ -71,8 +72,8 @@ class SchedulerManager(manager.Manager):
def update_service_capabilities(self, context=None, service_name=None,
host=None, capabilities=None):
"""Process a capability update from a service node."""
- if not capability:
- capability = {}
+ if not capabilities:
+ capabilities = {}
self.zone_manager.update_service_capabilities(service_name,
host, capabilities)
diff --git a/nova/scheduler/multi.py b/nova/scheduler/multi.py
new file mode 100644
index 000000000..b1578033c
--- /dev/null
+++ b/nova/scheduler/multi.py
@@ -0,0 +1,73 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2010 Openstack, LLC.
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Scheduler that allows routing some calls to one driver and others to another.
+"""
+
+from nova import flags
+from nova import utils
+from nova.scheduler import driver
+
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('compute_scheduler_driver',
+ 'nova.scheduler.chance.ChanceScheduler',
+ 'Driver to use for scheduling compute calls')
+flags.DEFINE_string('volume_scheduler_driver',
+ 'nova.scheduler.chance.ChanceScheduler',
+ 'Driver to use for scheduling volume calls')
+
+
+# A mapping of methods to topics so we can figure out which driver to use.
+_METHOD_MAP = {'run_instance': 'compute',
+ 'start_instance': 'compute',
+ 'create_volume': 'volume'}
+
+
+class MultiScheduler(driver.Scheduler):
+ """A scheduler that holds multiple sub-schedulers.
+
+ This exists to allow flag-driven composibility of schedulers, allowing
+ third parties to integrate custom schedulers more easily.
+
+ """
+
+ def __init__(self):
+ super(MultiScheduler, self).__init__()
+ compute_driver = utils.import_object(FLAGS.compute_scheduler_driver)
+ volume_driver = utils.import_object(FLAGS.volume_scheduler_driver)
+
+ self.drivers = {'compute': compute_driver,
+ 'volume': volume_driver}
+
+ def __getattr__(self, key):
+ if not key.startswith('schedule_'):
+ raise AttributeError(key)
+ method = key[len('schedule_'):]
+ if method not in _METHOD_MAP:
+ raise AttributeError(key)
+ return getattr(self.drivers[_METHOD_MAP[method]], key)
+
+ def set_zone_manager(self, zone_manager):
+ for k, v in self.drivers.iteritems():
+ v.set_zone_manager(zone_manager)
+
+ def schedule(self, context, topic, *_args, **_kwargs):
+ return self.drivers[topic].schedule(context, topic, *_args, **_kwargs)
diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py
index 2c325368c..7d57cef21 100644
--- a/nova/tests/api/openstack/test_extensions.py
+++ b/nova/tests/api/openstack/test_extensions.py
@@ -283,7 +283,7 @@ class ActionExtensionTest(test.TestCase):
body = dict(blah=dict(name="test")) # Doesn't exist
url = "/123/servers/1/action"
response = self._send_server_action_request(url, body)
- self.assertEqual(501, response.status_int)
+ self.assertEqual(400, response.status_int)
def test_invalid_action(self):
body = dict(blah=dict(name="test"))
diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py
index e8c8c2b8d..bd98d7d1a 100644
--- a/nova/tests/api/openstack/test_server_actions.py
+++ b/nova/tests/api/openstack/test_server_actions.py
@@ -352,7 +352,7 @@ class ServerActionsTest(test.TestCase):
req.body = json.dumps(body)
req.headers["content-type"] = "application/json"
response = req.get_response(fakes.wsgi_app())
- self.assertEqual(501, response.status_int)
+ self.assertEqual(400, response.status_int)
def test_create_backup_with_metadata(self):
self.flags(allow_admin_api=True)
@@ -487,6 +487,24 @@ class ServerActionsTestV11(test.TestCase):
def tearDown(self):
self.stubs.UnsetAll()
+ def test_server_bad_body(self):
+ body = {}
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
+ def test_server_unknown_action(self):
+ body = {'sockTheFox': {'fakekey': '1234'}}
+ req = webob.Request.blank('/v1.1/servers/1/action')
+ req.method = 'POST'
+ req.content_type = 'application/json'
+ req.body = json.dumps(body)
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 400)
+
def test_server_change_password(self):
mock_method = MockSetAdminPassword()
self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method)
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 99c947467..32dc2f116 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -134,8 +134,8 @@ def return_security_group(context, instance_id, security_group_id):
pass
-def instance_update(context, instance_id, kwargs):
- return stub_instance(instance_id)
+def instance_update(context, instance_id, values):
+ return stub_instance(instance_id, name=values.get('display_name'))
def instance_addresses(context, instance_id):
@@ -145,7 +145,7 @@ def instance_addresses(context, instance_id):
def stub_instance(id, user_id='fake', project_id='fake', private_address=None,
public_addresses=None, host=None, power_state=0,
reservation_id="", uuid=FAKE_UUID, image_ref="10",
- flavor_id="1", interfaces=None):
+ flavor_id="1", interfaces=None, name=None):
metadata = []
metadata.append(InstanceMetadata(key='seq', value=id))
@@ -161,7 +161,7 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None,
host = str(host)
# ReservationID isn't sent back, hack it in there.
- server_name = "server%s" % id
+ server_name = name or "server%s" % id
if reservation_id != "":
server_name = "reservation_%s" % (reservation_id, )
@@ -1880,13 +1880,17 @@ class ServersTest(test.TestCase):
self.assertEqual(res.status_int, 400)
def test_update_server_name_v1_1(self):
+ self.stubs.Set(nova.db.api, 'instance_get',
+ return_server_with_attributes(name='server_test'))
req = webob.Request.blank('/v1.1/fake/servers/1')
req.method = 'PUT'
req.content_type = 'application/json'
- req.body = json.dumps({'server': {'name': 'new-name'}})
+ req.body = json.dumps({'server': {'name': 'server_test'}})
res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 204)
- self.assertEqual(res.body, '')
+ self.assertEqual(res.status_int, 200)
+ res_dict = json.loads(res.body)
+ self.assertEqual(res_dict['server']['id'], 1)
+ self.assertEqual(res_dict['server']['name'], 'server_test')
def test_update_server_adminPass_ignored_v1_1(self):
inst_dict = dict(name='server_test', adminPass='bacon')
@@ -1897,16 +1901,19 @@ class ServersTest(test.TestCase):
self.assertEqual(params, filtered_dict)
return filtered_dict
- self.stubs.Set(nova.db.api, 'instance_update',
- server_update)
+ self.stubs.Set(nova.db.api, 'instance_update', server_update)
+ self.stubs.Set(nova.db.api, 'instance_get',
+ return_server_with_attributes(name='server_test'))
req = webob.Request.blank('/v1.1/fake/servers/1')
req.method = 'PUT'
req.content_type = "application/json"
req.body = self.body
res = req.get_response(fakes.wsgi_app())
- self.assertEqual(res.status_int, 204)
- self.assertEqual(res.body, '')
+ self.assertEqual(res.status_int, 200)
+ res_dict = json.loads(res.body)
+ self.assertEqual(res_dict['server']['id'], 1)
+ self.assertEqual(res_dict['server']['name'], 'server_test')
def test_create_backup_schedules(self):
req = webob.Request.blank('/v1.0/servers/1/backup_schedule')
diff --git a/nova/tests/glance/stubs.py b/nova/tests/glance/stubs.py
index d51b19ccd..f2a19f22d 100644
--- a/nova/tests/glance/stubs.py
+++ b/nova/tests/glance/stubs.py
@@ -32,6 +32,7 @@ class FakeGlance(object):
IMAGE_RAMDISK = 3
IMAGE_RAW = 4
IMAGE_VHD = 5
+ IMAGE_ISO = 6
IMAGE_FIXTURES = {
IMAGE_MACHINE: {
@@ -58,6 +59,11 @@ class FakeGlance(object):
'image_meta': {'name': 'fakevhd', 'size': 0,
'disk_format': 'vhd',
'container_format': 'ovf'},
+ 'image_data': StringIO.StringIO('')},
+ IMAGE_ISO: {
+ 'image_meta': {'name': 'fakeiso', 'size': 0,
+ 'disk_format': 'iso',
+ 'container_format': 'bare'},
'image_data': StringIO.StringIO('')}}
def __init__(self, host, port=None, use_ssl=False, auth_tok=None):
diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py
index 7a26fd1bb..d70a6779f 100644
--- a/nova/tests/scheduler/test_scheduler.py
+++ b/nova/tests/scheduler/test_scheduler.py
@@ -36,8 +36,9 @@ from nova import test
from nova import rpc
from nova import utils
from nova.scheduler import api
-from nova.scheduler import manager
from nova.scheduler import driver
+from nova.scheduler import manager
+from nova.scheduler import multi
from nova.compute import power_state
@@ -391,7 +392,7 @@ class SimpleDriverTestCase(test.TestCase):
compute1.kill()
compute2.kill()
- def test_wont_sechedule_if_specified_host_is_down_no_queue(self):
+ def test_wont_schedule_if_specified_host_is_down_no_queue(self):
compute1 = service.Service('host1',
'nova-compute',
'compute',
@@ -903,6 +904,25 @@ class SimpleDriverTestCase(test.TestCase):
db.service_destroy(self.context, s_ref2['id'])
+class MultiDriverTestCase(SimpleDriverTestCase):
+ """Test case for multi driver."""
+
+ def setUp(self):
+ super(MultiDriverTestCase, self).setUp()
+ self.flags(connection_type='fake',
+ stub_network=True,
+ max_cores=4,
+ max_gigabytes=4,
+ network_manager='nova.network.manager.FlatManager',
+ volume_driver='nova.volume.driver.FakeISCSIDriver',
+ compute_scheduler_driver=('nova.scheduler.simple'
+ '.SimpleScheduler'),
+ volume_scheduler_driver=('nova.scheduler.simple'
+ '.SimpleScheduler'),
+ scheduler_driver='nova.scheduler.multi.MultiScheduler')
+ self.scheduler = manager.SchedulerManager()
+
+
class FakeZone(object):
def __init__(self, id, api_url, username, password):
self.id = id
diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py
index 2180cf4f0..8bdfd71b4 100644
--- a/nova/tests/test_libvirt.py
+++ b/nova/tests/test_libvirt.py
@@ -71,12 +71,12 @@ def _create_network_info(count=1, ipv6=None):
return [(network, mapping) for x in xrange(0, count)]
-def _setup_networking(instance_id, ip='1.2.3.4'):
+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': '56:12:12:12:12:12',
+ vif = {'address': mac,
'network_id': network_ref['id'],
'instance_id': instance_id}
vif_ref = db.virtual_interface_create(ctxt, vif)
@@ -884,7 +884,11 @@ class IptablesFirewallTestCase(test.TestCase):
def test_static_filters(self):
instance_ref = self._create_instance_ref()
- _setup_networking(instance_ref['id'], self.test_ip)
+ 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,
@@ -893,6 +897,12 @@ class IptablesFirewallTestCase(test.TestCase):
'name': 'testgroup',
'description': 'test group'})
+ src_secgroup = db.security_group_create(admin_ctxt,
+ {'user_id': 'fake',
+ 'project_id': 'fake',
+ 'name': 'testsourcegroup',
+ 'description': 'src group'})
+
db.security_group_rule_create(admin_ctxt,
{'parent_group_id': secgroup['id'],
'protocol': 'icmp',
@@ -914,9 +924,19 @@ class IptablesFirewallTestCase(test.TestCase):
'to_port': 81,
'cidr': '192.168.10.0/24'})
+ db.security_group_rule_create(admin_ctxt,
+ {'parent_group_id': secgroup['id'],
+ 'protocol': 'tcp',
+ 'from_port': 80,
+ 'to_port': 81,
+ 'group_id': src_secgroup['id']})
+
db.instance_add_security_group(admin_ctxt, instance_ref['id'],
secgroup['id'])
+ db.instance_add_security_group(admin_ctxt, src_instance_ref['id'],
+ src_secgroup['id'])
instance_ref = db.instance_get(admin_ctxt, instance_ref['id'])
+ src_instance_ref = db.instance_get(admin_ctxt, src_instance_ref['id'])
# self.fw.add_instance(instance_ref)
def fake_iptables_execute(*cmd, **kwargs):
@@ -969,17 +989,22 @@ class IptablesFirewallTestCase(test.TestCase):
self.assertTrue(security_group_chain,
"The security group chain wasn't added")
- regex = re.compile('-A .* -p icmp -s 192.168.11.0/24 -j ACCEPT')
+ regex = re.compile('-A .* -j ACCEPT -p icmp -s 192.168.11.0/24')
self.assertTrue(len(filter(regex.match, self.out_rules)) > 0,
"ICMP acceptance rule wasn't added")
- regex = re.compile('-A .* -p icmp -s 192.168.11.0/24 -m icmp '
- '--icmp-type 8 -j ACCEPT')
+ regex = re.compile('-A .* -j ACCEPT -p icmp -m icmp --icmp-type 8'
+ ' -s 192.168.11.0/24')
self.assertTrue(len(filter(regex.match, self.out_rules)) > 0,
"ICMP Echo Request acceptance rule wasn't added")
- regex = re.compile('-A .* -p tcp -s 192.168.10.0/24 -m multiport '
- '--dports 80:81 -j ACCEPT')
+ 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")
+
+ regex = re.compile('-A .* -j ACCEPT -p tcp '
+ '-m multiport --dports 80:81 -s 192.168.10.0/24')
self.assertTrue(len(filter(regex.match, self.out_rules)) > 0,
"TCP port 80/81 acceptance rule wasn't added")
db.instance_destroy(admin_ctxt, instance_ref['id'])
diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py
index 2ca8b64f4..c673f5d06 100644
--- a/nova/tests/test_network.py
+++ b/nova/tests/test_network.py
@@ -210,7 +210,11 @@ class VlanNetworkTestCase(test.TestCase):
self.mox.StubOutWithMock(db, 'fixed_ip_update')
self.mox.StubOutWithMock(db,
'virtual_interface_get_by_instance_and_network')
+ self.mox.StubOutWithMock(db, 'instance_get')
+ 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.1')
@@ -247,6 +251,17 @@ class CommonNetworkTestCase(test.TestCase):
return [dict(address='10.0.0.0'), dict(address='10.0.0.1'),
dict(address='10.0.0.2')]
+ def network_get_by_cidr(self, context, cidr):
+ raise exception.NetworkNotFoundForCidr()
+
+ def network_create_safe(self, context, net):
+ fakenet = {}
+ fakenet['id'] = 999
+ return fakenet
+
+ def network_get_all(self, context):
+ raise exception.NoNetworksFound()
+
def __init__(self):
self.db = self.FakeDB()
self.deallocate_called = None
@@ -254,6 +269,9 @@ class CommonNetworkTestCase(test.TestCase):
def deallocate_fixed_ip(self, context, address):
self.deallocate_called = address
+ def fake_create_fixed_ips(self, context, network_id):
+ return None
+
def test_remove_fixed_ip_from_instance(self):
manager = self.FakeNetworkManager()
manager.remove_fixed_ip_from_instance(None, 99, '10.0.0.1')
@@ -265,3 +283,165 @@ class CommonNetworkTestCase(test.TestCase):
self.assertRaises(exception.FixedIpNotFoundForSpecificInstance,
manager.remove_fixed_ip_from_instance,
None, 99, 'bad input')
+
+ def test_validate_cidrs(self):
+ manager = self.FakeNetworkManager()
+ nets = manager._validate_cidrs(None, '192.168.0.0/24', 1, 256)
+ self.assertEqual(1, len(nets))
+ cidrs = [str(net) for net in nets]
+ self.assertTrue('192.168.0.0/24' in cidrs)
+
+ def test_validate_cidrs_split_exact_in_half(self):
+ manager = self.FakeNetworkManager()
+ nets = manager._validate_cidrs(None, '192.168.0.0/24', 2, 128)
+ self.assertEqual(2, len(nets))
+ cidrs = [str(net) for net in nets]
+ self.assertTrue('192.168.0.0/25' in cidrs)
+ self.assertTrue('192.168.0.128/25' in cidrs)
+
+ def test_validate_cidrs_split_cidr_in_use_middle_of_range(self):
+ manager = self.FakeNetworkManager()
+ self.mox.StubOutWithMock(manager.db, 'network_get_all')
+ ctxt = mox.IgnoreArg()
+ manager.db.network_get_all(ctxt).AndReturn([{'id': 1,
+ 'cidr': '192.168.2.0/24'}])
+ self.mox.ReplayAll()
+ nets = manager._validate_cidrs(None, '192.168.0.0/16', 4, 256)
+ self.assertEqual(4, len(nets))
+ cidrs = [str(net) for net in nets]
+ exp_cidrs = ['192.168.0.0/24', '192.168.1.0/24', '192.168.3.0/24',
+ '192.168.4.0/24']
+ for exp_cidr in exp_cidrs:
+ self.assertTrue(exp_cidr in cidrs)
+ self.assertFalse('192.168.2.0/24' in cidrs)
+
+ def test_validate_cidrs_smaller_subnet_in_use(self):
+ manager = self.FakeNetworkManager()
+ self.mox.StubOutWithMock(manager.db, 'network_get_all')
+ ctxt = mox.IgnoreArg()
+ manager.db.network_get_all(ctxt).AndReturn([{'id': 1,
+ 'cidr': '192.168.2.9/25'}])
+ self.mox.ReplayAll()
+ # ValueError: requested cidr (192.168.2.0/24) conflicts with
+ # existing smaller cidr
+ args = [None, '192.168.2.0/24', 1, 256]
+ self.assertRaises(ValueError, manager._validate_cidrs, *args)
+
+ def test_validate_cidrs_split_smaller_cidr_in_use(self):
+ manager = self.FakeNetworkManager()
+ self.mox.StubOutWithMock(manager.db, 'network_get_all')
+ ctxt = mox.IgnoreArg()
+ manager.db.network_get_all(ctxt).AndReturn([{'id': 1,
+ 'cidr': '192.168.2.0/25'}])
+ self.mox.ReplayAll()
+ nets = manager._validate_cidrs(None, '192.168.0.0/16', 4, 256)
+ self.assertEqual(4, len(nets))
+ cidrs = [str(net) for net in nets]
+ exp_cidrs = ['192.168.0.0/24', '192.168.1.0/24', '192.168.3.0/24',
+ '192.168.4.0/24']
+ for exp_cidr in exp_cidrs:
+ self.assertTrue(exp_cidr in cidrs)
+ self.assertFalse('192.168.2.0/24' in cidrs)
+
+ def test_validate_cidrs_split_smaller_cidr_in_use2(self):
+ manager = self.FakeNetworkManager()
+ self.mox.StubOutWithMock(manager.db, 'network_get_all')
+ ctxt = mox.IgnoreArg()
+ manager.db.network_get_all(ctxt).AndReturn([{'id': 1,
+ 'cidr': '192.168.2.9/29'}])
+ self.mox.ReplayAll()
+ nets = manager._validate_cidrs(None, '192.168.2.0/24', 3, 32)
+ self.assertEqual(3, len(nets))
+ cidrs = [str(net) for net in nets]
+ exp_cidrs = ['192.168.2.32/27', '192.168.2.64/27', '192.168.2.96/27']
+ for exp_cidr in exp_cidrs:
+ self.assertTrue(exp_cidr in cidrs)
+ self.assertFalse('192.168.2.0/27' in cidrs)
+
+ def test_validate_cidrs_split_all_in_use(self):
+ manager = self.FakeNetworkManager()
+ self.mox.StubOutWithMock(manager.db, 'network_get_all')
+ ctxt = mox.IgnoreArg()
+ in_use = [{'id': 1, 'cidr': '192.168.2.9/29'},
+ {'id': 2, 'cidr': '192.168.2.64/26'},
+ {'id': 3, 'cidr': '192.168.2.128/26'}]
+ manager.db.network_get_all(ctxt).AndReturn(in_use)
+ self.mox.ReplayAll()
+ args = [None, '192.168.2.0/24', 3, 64]
+ # ValueError: Not enough subnets avail to satisfy requested num_
+ # networks - some subnets in requested range already
+ # in use
+ self.assertRaises(ValueError, manager._validate_cidrs, *args)
+
+ def test_validate_cidrs_one_in_use(self):
+ manager = self.FakeNetworkManager()
+ args = [None, '192.168.0.0/24', 2, 256]
+ # ValueError: network_size * num_networks exceeds cidr size
+ self.assertRaises(ValueError, manager._validate_cidrs, *args)
+
+ def test_validate_cidrs_already_used(self):
+ manager = self.FakeNetworkManager()
+ self.mox.StubOutWithMock(manager.db, 'network_get_all')
+ ctxt = mox.IgnoreArg()
+ manager.db.network_get_all(ctxt).AndReturn([{'id': 1,
+ 'cidr': '192.168.0.0/24'}])
+ self.mox.ReplayAll()
+ # ValueError: cidr already in use
+ args = [None, '192.168.0.0/24', 1, 256]
+ self.assertRaises(ValueError, manager._validate_cidrs, *args)
+
+ def test_validate_cidrs_too_many(self):
+ manager = self.FakeNetworkManager()
+ args = [None, '192.168.0.0/24', 200, 256]
+ # ValueError: Not enough subnets avail to satisfy requested
+ # num_networks
+ self.assertRaises(ValueError, manager._validate_cidrs, *args)
+
+ def test_validate_cidrs_split_partial(self):
+ manager = self.FakeNetworkManager()
+ nets = manager._validate_cidrs(None, '192.168.0.0/16', 2, 256)
+ returned_cidrs = [str(net) for net in nets]
+ self.assertTrue('192.168.0.0/24' in returned_cidrs)
+ self.assertTrue('192.168.1.0/24' in returned_cidrs)
+
+ def test_validate_cidrs_conflict_existing_supernet(self):
+ manager = self.FakeNetworkManager()
+ self.mox.StubOutWithMock(manager.db, 'network_get_all')
+ ctxt = mox.IgnoreArg()
+ fakecidr = [{'id': 1, 'cidr': '192.168.0.0/8'}]
+ manager.db.network_get_all(ctxt).AndReturn(fakecidr)
+ self.mox.ReplayAll()
+ args = [None, '192.168.0.0/24', 1, 256]
+ # ValueError: requested cidr (192.168.0.0/24) conflicts
+ # with existing supernet
+ self.assertRaises(ValueError, manager._validate_cidrs, *args)
+
+ def test_create_networks(self):
+ cidr = '192.168.0.0/24'
+ manager = self.FakeNetworkManager()
+ self.stubs.Set(manager, '_create_fixed_ips',
+ self.fake_create_fixed_ips)
+ args = [None, 'foo', cidr, None, 1, 256, 'fd00::/48', None, None,
+ None]
+ result = manager.create_networks(*args)
+ self.assertEqual(manager.create_networks(*args), None)
+
+ def test_create_networks_cidr_already_used(self):
+ manager = self.FakeNetworkManager()
+ self.mox.StubOutWithMock(manager.db, 'network_get_all')
+ ctxt = mox.IgnoreArg()
+ fakecidr = [{'id': 1, 'cidr': '192.168.0.0/24'}]
+ manager.db.network_get_all(ctxt).AndReturn(fakecidr)
+ self.mox.ReplayAll()
+ args = [None, 'foo', '192.168.0.0/24', None, 1, 256,
+ 'fd00::/48', None, None, None]
+ self.assertRaises(ValueError, manager.create_networks, *args)
+
+ def test_create_networks_many(self):
+ cidr = '192.168.0.0/16'
+ manager = self.FakeNetworkManager()
+ self.stubs.Set(manager, '_create_fixed_ips',
+ self.fake_create_fixed_ips)
+ args = [None, 'foo', cidr, None, 10, 256, 'fd00::/48', None, None,
+ None]
+ self.assertEqual(manager.create_networks(*args), None)
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py
index 1deb5a780..2f0559366 100644
--- a/nova/tests/test_xenapi.py
+++ b/nova/tests/test_xenapi.py
@@ -519,6 +519,11 @@ class XenAPIVMTestCase(test.TestCase):
os_type="windows", architecture="i386")
self.check_vm_params_for_windows()
+ def test_spawn_iso_glance(self):
+ self._test_spawn(glance_stubs.FakeGlance.IMAGE_ISO, None, None,
+ os_type="windows", architecture="i386")
+ self.check_vm_params_for_windows()
+
def test_spawn_glance(self):
self._test_spawn(glance_stubs.FakeGlance.IMAGE_MACHINE,
glance_stubs.FakeGlance.IMAGE_KERNEL,
diff --git a/nova/virt/libvirt/firewall.py b/nova/virt/libvirt/firewall.py
index 9ce57b6c9..16e5070c6 100644
--- a/nova/virt/libvirt/firewall.py
+++ b/nova/virt/libvirt/firewall.py
@@ -664,11 +664,10 @@ class IptablesFirewallDriver(FirewallDriver):
LOG.debug(_('Adding security group rule: %r'), rule)
if not rule.cidr:
- # Eventually, a mechanism to grant access for security
- # groups will turn up here. It'll use ipsets.
- continue
+ version = 4
+ else:
+ version = netutils.get_ip_version(rule.cidr)
- version = netutils.get_ip_version(rule.cidr)
if version == 4:
fw_rules = ipv4_rules
else:
@@ -678,16 +677,16 @@ class IptablesFirewallDriver(FirewallDriver):
if version == 6 and rule.protocol == 'icmp':
protocol = 'icmpv6'
- args = ['-p', protocol, '-s', rule.cidr]
+ args = ['-j ACCEPT', '-p', protocol]
- if rule.protocol in ['udp', 'tcp']:
+ if protocol in ['udp', 'tcp']:
if rule.from_port == rule.to_port:
args += ['--dport', '%s' % (rule.from_port,)]
else:
args += ['-m', 'multiport',
'--dports', '%s:%s' % (rule.from_port,
rule.to_port)]
- elif rule.protocol == 'icmp':
+ elif protocol == 'icmp':
icmp_type = rule.from_port
icmp_code = rule.to_port
@@ -706,9 +705,22 @@ class IptablesFirewallDriver(FirewallDriver):
args += ['-m', 'icmp6', '--icmpv6-type',
icmp_type_arg]
- args += ['-j ACCEPT']
- fw_rules += [' '.join(args)]
-
+ if rule.cidr:
+ LOG.info('Using cidr %r', rule.cidr)
+ args += ['-s', rule.cidr]
+ fw_rules += [' '.join(args)]
+ else:
+ if rule['grantee_group']:
+ for instance in rule['grantee_group']['instances']:
+ LOG.info('instance: %r', instance)
+ ips = db.instance_get_fixed_addresses(ctxt,
+ instance['id'])
+ LOG.info('ips: %r', ips)
+ for ip in ips:
+ subrule = args + ['-s %s' % ip]
+ fw_rules += [' '.join(subrule)]
+
+ LOG.info('Using fw_rules: %r', fw_rules)
ipv4_rules += ['-j $sg-fallback']
ipv6_rules += ['-j $sg-fallback']
@@ -719,7 +731,8 @@ class IptablesFirewallDriver(FirewallDriver):
return self.nwfilter.instance_filter_exists(instance)
def refresh_security_group_members(self, security_group):
- pass
+ self.do_refresh_security_group_rules(security_group)
+ self.iptables.apply()
def refresh_security_group_rules(self, security_group, network_info=None):
self.do_refresh_security_group_rules(security_group, network_info)
diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py
index d5ac39473..1aa642e4e 100644
--- a/nova/virt/xenapi/fake.py
+++ b/nova/virt/xenapi/fake.py
@@ -194,6 +194,7 @@ def create_local_pifs():
Do this one per host."""
for host_ref in _db_content['host'].keys():
_create_local_pif(host_ref)
+ _create_local_sr_iso(host_ref)
def create_local_srs():
@@ -222,6 +223,25 @@ def _create_local_sr(host_ref):
return sr_ref
+def _create_local_sr_iso(host_ref):
+ sr_ref = _create_object(
+ 'SR',
+ {'name_label': 'Local storage ISO',
+ 'type': 'lvm',
+ 'content_type': 'iso',
+ 'shared': False,
+ 'physical_size': str(1 << 30),
+ 'physical_utilisation': str(0),
+ 'virtual_allocation': str(0),
+ 'other_config': {
+ 'i18n-original-value-name_label': 'Local storage ISO',
+ 'i18n-key': 'local-storage-iso'},
+ 'VDIs': []})
+ pbd_ref = create_pbd('', host_ref, sr_ref, True)
+ _db_content['SR'][sr_ref]['PBDs'] = [pbd_ref]
+ return sr_ref
+
+
def _create_local_pif(host_ref):
pif_ref = _create_object('PIF',
{'name-label': 'Fake PIF',
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index 6c44d53d4..ba5cf4b49 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -77,6 +77,7 @@ class ImageType:
3 - raw disk image (local SR, NOT partitioned by plugin)
4 - vhd disk image (local SR, NOT inspected by XS, PV assumed for
linux, HVM assumed for Windows)
+ 5 - ISO disk image (local SR, NOT partitioned by plugin)
"""
KERNEL = 0
@@ -84,14 +85,17 @@ class ImageType:
DISK = 2
DISK_RAW = 3
DISK_VHD = 4
- _ids = (KERNEL, RAMDISK, DISK, DISK_RAW, DISK_VHD)
+ DISK_ISO = 5
+ _ids = (KERNEL, RAMDISK, DISK, DISK_RAW, DISK_VHD, DISK_ISO)
KERNEL_STR = "kernel"
RAMDISK_STR = "ramdisk"
DISK_STR = "os"
DISK_RAW_STR = "os_raw"
DISK_VHD_STR = "vhd"
- _strs = (KERNEL_STR, RAMDISK_STR, DISK_STR, DISK_RAW_STR, DISK_VHD_STR)
+ DISK_ISO_STR = "iso"
+ _strs = (KERNEL_STR, RAMDISK_STR, DISK_STR, DISK_RAW_STR, DISK_VHD_STR,
+ DISK_ISO_STR)
@classmethod
def to_string(cls, image_type):
@@ -223,6 +227,30 @@ class VMHelper(HelperBase):
return vbd_ref
@classmethod
+ def create_cd_vbd(cls, session, vm_ref, vdi_ref, userdevice, bootable):
+ """Create a VBD record. Returns a Deferred that gives the new
+ VBD reference specific to CDRom devices."""
+ vbd_rec = {}
+ vbd_rec['VM'] = vm_ref
+ vbd_rec['VDI'] = vdi_ref
+ vbd_rec['userdevice'] = str(userdevice)
+ vbd_rec['bootable'] = bootable
+ vbd_rec['mode'] = 'RO'
+ vbd_rec['type'] = 'CD'
+ vbd_rec['unpluggable'] = True
+ vbd_rec['empty'] = False
+ vbd_rec['other_config'] = {}
+ vbd_rec['qos_algorithm_type'] = ''
+ vbd_rec['qos_algorithm_params'] = {}
+ vbd_rec['qos_supported_algorithms'] = []
+ LOG.debug(_('Creating a CDROM-specific VBD for VM %(vm_ref)s,'
+ ' VDI %(vdi_ref)s ... ') % locals())
+ vbd_ref = session.call_xenapi('VBD.create', vbd_rec)
+ LOG.debug(_('Created a CDROM-specific VBD %(vbd_ref)s '
+ ' for VM %(vm_ref)s, VDI %(vdi_ref)s.') % locals())
+ return vbd_ref
+
+ @classmethod
def find_vbd_by_number(cls, session, vm_ref, number):
"""Get the VBD reference from the device number"""
vbd_refs = session.get_xenapi().VM.get_VBDs(vm_ref)
@@ -368,6 +396,23 @@ class VMHelper(HelperBase):
session.wait_for_task(task, instance.id)
@classmethod
+ def fetch_blank_disk(cls, session, instance_type_id):
+ # Size the blank harddrive to suit the machine type:
+ one_gig = 1024 * 1024 * 1024
+ req_type = instance_types.get_instance_type(instance_type_id)
+ req_size = req_type['local_gb']
+
+ LOG.debug("Creating blank HD of size %(req_size)d gigs"
+ % locals())
+ vdi_size = one_gig * req_size
+
+ LOG.debug("ISO vm create: Looking for the SR")
+ sr_ref = safe_find_sr(session)
+
+ vdi_ref = cls.create_vdi(session, sr_ref, 'blank HD', vdi_size, False)
+ return vdi_ref
+
+ @classmethod
def fetch_image(cls, context, session, instance_id, image, user_id,
project_id, image_type):
"""Fetch image from glance based on image type.
@@ -449,7 +494,12 @@ class VMHelper(HelperBase):
# DISK restores
LOG.debug(_("Fetching image %(image)s") % locals())
LOG.debug(_("Image Type: %s"), ImageType.to_string(image_type))
- sr_ref = safe_find_sr(session)
+
+ if image_type == ImageType.DISK_ISO:
+ sr_ref = safe_find_iso_sr(session)
+ LOG.debug(_("ISO: Found sr possibly containing the ISO image"))
+ else:
+ sr_ref = safe_find_sr(session)
glance_client, image_id = nova.image.get_glance_client(image)
glance_client.set_auth_token(getattr(context, 'auth_token', None))
@@ -527,7 +577,8 @@ class VMHelper(HelperBase):
ImageType.RAMDISK: 'RAMDISK',
ImageType.DISK: 'DISK',
ImageType.DISK_RAW: 'DISK_RAW',
- ImageType.DISK_VHD: 'DISK_VHD'}
+ ImageType.DISK_VHD: 'DISK_VHD',
+ ImageType.DISK_ISO: 'DISK_ISO'}
disk_format = pretty_format[image_type]
image_ref = instance.image_ref
instance_id = instance.id
@@ -540,7 +591,8 @@ class VMHelper(HelperBase):
'aki': ImageType.KERNEL,
'ari': ImageType.RAMDISK,
'raw': ImageType.DISK_RAW,
- 'vhd': ImageType.DISK_VHD}
+ 'vhd': ImageType.DISK_VHD,
+ 'iso': ImageType.DISK_ISO}
image_ref = instance.image_ref
glance_client, image_id = nova.image.get_glance_client(image_ref)
meta = glance_client.get_image_meta(image_id)
@@ -574,6 +626,8 @@ class VMHelper(HelperBase):
available
3. Glance (DISK): pv is assumed
+
+ 4. Glance (DISK_ISO): no pv is assumed
"""
LOG.debug(_("Looking up vdi %s for PV kernel"), vdi_ref)
@@ -589,6 +643,9 @@ class VMHelper(HelperBase):
elif disk_image_type == ImageType.DISK:
# 3. Disk
is_pv = True
+ elif disk_image_type == ImageType.DISK_ISO:
+ # 4. ISO
+ is_pv = False
else:
raise exception.Error(_("Unknown image format %(disk_image_type)s")
% locals())
@@ -832,6 +889,48 @@ def find_sr(session):
return None
+def safe_find_iso_sr(session):
+ """Same as find_iso_sr except raises a NotFound exception if SR cannot be
+ determined
+ """
+ sr_ref = find_iso_sr(session)
+ if sr_ref is None:
+ raise exception.NotFound(_('Cannot find SR of content-type ISO'))
+ return sr_ref
+
+
+def find_iso_sr(session):
+ """Return the storage repository to hold ISO images"""
+ host = session.get_xenapi_host()
+ sr_refs = session.get_xenapi().SR.get_all()
+ for sr_ref in sr_refs:
+ sr_rec = session.get_xenapi().SR.get_record(sr_ref)
+
+ LOG.debug(_("ISO: looking at SR %(sr_rec)s") % locals())
+ if not sr_rec['content_type'] == 'iso':
+ LOG.debug(_("ISO: not iso content"))
+ continue
+ if not 'i18n-key' in sr_rec['other_config']:
+ LOG.debug(_("ISO: iso content_type, no 'i18n-key' key"))
+ continue
+ if not sr_rec['other_config']['i18n-key'] == 'local-storage-iso':
+ LOG.debug(_("ISO: iso content_type, i18n-key value not "
+ "'local-storage-iso'"))
+ continue
+
+ LOG.debug(_("ISO: SR MATCHing our criteria"))
+ for pbd_ref in sr_rec['PBDs']:
+ LOG.debug(_("ISO: ISO, looking to see if it is host local"))
+ pbd_rec = session.get_xenapi().PBD.get_record(pbd_ref)
+ pbd_rec_host = pbd_rec['host']
+ LOG.debug(_("ISO: PBD matching, want %(pbd_rec)s, have %(host)s") %
+ locals())
+ if pbd_rec_host == host:
+ LOG.debug(_("ISO: SR with local PBD"))
+ return sr_ref
+ return None
+
+
def remap_vbd_dev(dev):
"""Return the appropriate location for a plugged-in VBD device
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index b1522729a..1fefd1291 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -235,12 +235,51 @@ class VMOps(object):
raise vm_create_error
- VMHelper.create_vbd(session=self._session, vm_ref=vm_ref,
- vdi_ref=first_vdi_ref, userdevice=0, bootable=True)
+ # Add disks to VM
+ self._attach_disks(instance, disk_image_type, vm_ref, first_vdi_ref,
+ vdis)
+
+ # Alter the image before VM start for, e.g. network injection
+ if FLAGS.flat_injected:
+ VMHelper.preconfigure_instance(self._session, instance,
+ first_vdi_ref, network_info)
+
+ self.create_vifs(vm_ref, instance, network_info)
+ self.inject_network_info(instance, network_info, vm_ref)
+ return vm_ref
+
+ def _attach_disks(self, instance, disk_image_type, vm_ref, first_vdi_ref,
+ vdis):
+ # device 0 reserved for RW disk
+ userdevice = 0
+
+ # DISK_ISO needs two VBDs: the ISO disk and a blank RW disk
+ if disk_image_type == ImageType.DISK_ISO:
+ LOG.debug("detected ISO image type, going to create blank VM for "
+ "install")
+
+ cd_vdi_ref = first_vdi_ref
+ first_vdi_ref = VMHelper.fetch_blank_disk(session=self._session,
+ instance_type_id=instance.instance_type_id)
+
+ VMHelper.create_vbd(session=self._session, vm_ref=vm_ref,
+ vdi_ref=first_vdi_ref, userdevice=userdevice, bootable=False)
+
+ # device 1 reserved for rescue disk and we've used '0'
+ userdevice = 2
+ VMHelper.create_cd_vbd(session=self._session, vm_ref=vm_ref,
+ vdi_ref=cd_vdi_ref, userdevice=userdevice, bootable=True)
+
+ # set user device to next free value
+ userdevice += 1
+ else:
+ VMHelper.create_vbd(session=self._session, vm_ref=vm_ref,
+ vdi_ref=first_vdi_ref, userdevice=userdevice, bootable=True)
+ # set user device to next free value
+ # userdevice 1 is reserved for rescue and we've used '0'
+ userdevice = 2
# Attach any other disks
- # userdevice 1 is reserved for rescue
- userdevice = 2
for vdi in vdis[1:]:
# vdi['vdi_type'] is either 'os' or 'swap', but we don't
# really care what it is right here.
@@ -251,15 +290,6 @@ class VMOps(object):
bootable=False)
userdevice += 1
- # Alter the image before VM start for, e.g. network injection
- if FLAGS.flat_injected:
- VMHelper.preconfigure_instance(self._session, instance,
- first_vdi_ref, network_info)
-
- self.create_vifs(vm_ref, instance, network_info)
- self.inject_network_info(instance, network_info, vm_ref)
- return vm_ref
-
def _spawn(self, instance, vm_ref):
"""Spawn a new instance."""
LOG.debug(_('Starting VM %s...'), vm_ref)