summaryrefslogtreecommitdiffstats
path: root/nova/virt
diff options
context:
space:
mode:
authorWilliam Wolf <throughnothing@gmail.com>2011-07-26 10:33:05 +0000
committerTarmac <>2011-07-26 10:33:05 +0000
commitcb631be09c36d29ddb6e89a647c5161bc43c4aa7 (patch)
tree5be2f5dfaeaee8dbc268f3c2f8cb26d56bd4b78d /nova/virt
parent85522bba82a4139a89915bba99865a50fd9b8f58 (diff)
parent5df221e970d8b060423034fa627735c5c24fce5d (diff)
Merge diablo-3 development from trunk (rev1322)
Diffstat (limited to 'nova/virt')
-rw-r--r--nova/virt/driver.py26
-rw-r--r--nova/virt/fake.py16
-rw-r--r--nova/virt/hyperv.py17
-rw-r--r--nova/virt/libvirt.xml.template24
-rw-r--r--nova/virt/libvirt/connection.py160
-rw-r--r--nova/virt/libvirt/firewall.py19
-rw-r--r--nova/virt/libvirt/netutils.py30
-rw-r--r--nova/virt/libvirt/vif.py134
-rw-r--r--nova/virt/vif.py30
-rw-r--r--nova/virt/vmwareapi/network_utils.py28
-rw-r--r--nova/virt/vmwareapi/vif.py95
-rw-r--r--nova/virt/vmwareapi/vm_util.py38
-rw-r--r--nova/virt/vmwareapi/vmops.py57
-rw-r--r--nova/virt/vmwareapi_conn.py20
-rw-r--r--nova/virt/xenapi/vif.py140
-rw-r--r--nova/virt/xenapi/vm_utils.py183
-rw-r--r--nova/virt/xenapi/vmops.py376
-rw-r--r--nova/virt/xenapi_conn.py31
18 files changed, 1056 insertions, 368 deletions
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index 2c7c0cfcc..34dc5f544 100644
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -61,11 +61,11 @@ class ComputeDriver(object):
"""Return a list of InstanceInfo for all registered VMs"""
raise NotImplementedError()
- def spawn(self, instance, network_info=None, block_device_mapping=None):
+ def spawn(self, instance, network_info, block_device_mapping=None):
"""Launch a VM for the specified instance"""
raise NotImplementedError()
- def destroy(self, instance, cleanup=True):
+ def destroy(self, instance, network_info, cleanup=True):
"""Destroy (shutdown and delete) the specified instance.
The given parameter is an instance of nova.compute.service.Instance,
@@ -81,7 +81,7 @@ class ComputeDriver(object):
"""
raise NotImplementedError()
- def reboot(self, instance):
+ def reboot(self, instance, network_info):
"""Reboot specified VM"""
raise NotImplementedError()
@@ -146,11 +146,11 @@ class ComputeDriver(object):
"""resume the specified instance"""
raise NotImplementedError()
- def rescue(self, instance, callback):
+ def rescue(self, instance, callback, network_info):
"""Rescue the specified instance"""
raise NotImplementedError()
- def unrescue(self, instance, callback):
+ def unrescue(self, instance, callback, network_info):
"""Unrescue the specified instance"""
raise NotImplementedError()
@@ -197,7 +197,7 @@ class ComputeDriver(object):
def reset_network(self, instance):
"""reset networking for specified instance"""
- raise NotImplementedError()
+ pass
def ensure_filtering_rules_for_instance(self, instance_ref):
"""Setting up filtering rules and waiting for its completion.
@@ -224,7 +224,7 @@ class ComputeDriver(object):
"""
raise NotImplementedError()
- def unfilter_instance(self, instance):
+ def unfilter_instance(self, instance, network_info):
"""Stop filtering instance"""
raise NotImplementedError()
@@ -242,10 +242,18 @@ class ComputeDriver(object):
"""Update agent on the VM instance."""
raise NotImplementedError()
- def inject_network_info(self, instance):
+ def inject_network_info(self, instance, nw_info):
"""inject network info for specified instance"""
- raise NotImplementedError()
+ pass
def poll_rescued_instances(self, timeout):
"""Poll for rescued instances"""
raise NotImplementedError()
+
+ def set_host_enabled(self, host, enabled):
+ """Sets the specified host's ability to accept new instances."""
+ raise NotImplementedError()
+
+ def plug_vifs(self, instance, network_info):
+ """Plugs in VIFs to networks."""
+ raise NotImplementedError()
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index f78c29bd0..26bc421c0 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -129,7 +129,7 @@ class FakeConnection(driver.ComputeDriver):
info_list.append(self._map_to_instance_info(instance))
return info_list
- def spawn(self, instance, network_info=None, block_device_mapping=None):
+ def spawn(self, instance, network_info, block_device_mapping=None):
"""
Create a new instance/VM/domain on the virtualization platform.
@@ -167,7 +167,7 @@ class FakeConnection(driver.ComputeDriver):
"""
pass
- def reboot(self, instance):
+ def reboot(self, instance, network_info):
"""
Reboot the specified instance.
@@ -240,13 +240,13 @@ class FakeConnection(driver.ComputeDriver):
"""
pass
- def rescue(self, instance):
+ def rescue(self, instance, callback, network_info):
"""
Rescue the specified instance.
"""
pass
- def unrescue(self, instance):
+ def unrescue(self, instance, callback, network_info):
"""
Unrescue the specified instance.
"""
@@ -293,7 +293,7 @@ class FakeConnection(driver.ComputeDriver):
"""
pass
- def destroy(self, instance):
+ def destroy(self, instance, network_info):
key = instance.name
if key in self.instances:
del self.instances[key]
@@ -499,7 +499,7 @@ class FakeConnection(driver.ComputeDriver):
"""This method is supported only by libvirt."""
return
- def unfilter_instance(self, instance_ref):
+ def unfilter_instance(self, instance_ref, network_info=None):
"""This method is supported only by libvirt."""
raise NotImplementedError('This method is supported only by libvirt.')
@@ -514,3 +514,7 @@ class FakeConnection(driver.ComputeDriver):
def get_host_stats(self, refresh=False):
"""Return fake Host Status of ram, disk, network."""
return self.host_status
+
+ def set_host_enabled(self, host, enabled):
+ """Sets the specified host's ability to accept new instances."""
+ pass
diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py
index 772e7eb59..81c7dea58 100644
--- a/nova/virt/hyperv.py
+++ b/nova/virt/hyperv.py
@@ -139,7 +139,7 @@ class HyperVConnection(driver.ComputeDriver):
return instance_infos
- def spawn(self, instance, network_info=None, block_device_mapping=None):
+ def spawn(self, instance, network_info, block_device_mapping=None):
""" Create a new VM and start it."""
vm = self._lookup(instance.name)
if vm is not None:
@@ -157,7 +157,12 @@ class HyperVConnection(driver.ComputeDriver):
self._create_vm(instance)
self._create_disk(instance['name'], vhdfile)
- self._create_nic(instance['name'], instance['mac_address'])
+
+ mac_address = None
+ if instance['mac_addresses']:
+ mac_address = instance['mac_addresses'][0]['address']
+
+ self._create_nic(instance['name'], mac_address)
LOG.debug(_('Starting VM %s '), instance.name)
self._set_vm_state(instance['name'], 'Enabled')
@@ -363,14 +368,14 @@ class HyperVConnection(driver.ComputeDriver):
wmi_obj.Properties_.Item(prop).Value
return newinst
- def reboot(self, instance):
+ def reboot(self, instance, network_info):
"""Reboot the specified instance."""
vm = self._lookup(instance.name)
if vm is None:
raise exception.InstanceNotFound(instance_id=instance.id)
self._set_vm_state(instance.name, 'Reboot')
- def destroy(self, instance):
+ def destroy(self, instance, network_info):
"""Destroy the VM. Also destroy the associated VHD disk files"""
LOG.debug(_("Got request to destroy vm %s"), instance.name)
vm = self._lookup(instance.name)
@@ -494,3 +499,7 @@ class HyperVConnection(driver.ComputeDriver):
def get_host_stats(self, refresh=False):
"""See xenapi_conn.py implementation."""
pass
+
+ def set_host_enabled(self, host, enabled):
+ """Sets the specified host's ability to accept new instances."""
+ pass
diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template
index e1a683da8..a75636390 100644
--- a/nova/virt/libvirt.xml.template
+++ b/nova/virt/libvirt.xml.template
@@ -82,9 +82,13 @@
</disk>
#end if
#for $vol in $volumes
- <disk type='block'>
+ <disk type='${vol.type}'>
<driver type='raw'/>
+ #if $vol.type == 'network'
+ <source protocol='${vol.protocol}' name='${vol.name}'/>
+ #else
<source dev='${vol.device_path}'/>
+ #end if
<target dev='${vol.mount_device}' bus='${disk_bus}'/>
</disk>
#end for
@@ -92,6 +96,22 @@
#end if
#for $nic in $nics
+ #if $vif_type == 'ethernet'
+ <interface type='ethernet'>
+ <target dev='${nic.name}' />
+ <mac address='${nic.mac_address}' />
+ <script path='${nic.script}' />
+ </interface>
+ #else if $vif_type == '802.1Qbh'
+ <interface type='direct'>
+ <mac address='${nic.mac_address}'/>
+ <source dev='${nic.device_name}' mode='private'/>
+ <virtualport type='802.1Qbh'>
+ <parameters profileid='${nic.profile_name}'/>
+ </virtualport>
+ <model type='virtio'/>
+ </interface>
+ #else
<interface type='bridge'>
<source bridge='${nic.bridge_name}'/>
<mac address='${nic.mac_address}'/>
@@ -107,6 +127,8 @@
#end if
</filterref>
</interface>
+ #end if
+
#end for
<!-- The order is significant here. File must be defined first -->
<serial type="file">
diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py
index fadf77629..96f9c41f9 100644
--- a/nova/virt/libvirt/connection.py
+++ b/nova/virt/libvirt/connection.py
@@ -123,6 +123,11 @@ flags.DEFINE_string('qemu_img', 'qemu-img',
'binary to use for qemu-img commands')
flags.DEFINE_bool('start_guests_on_host_boot', False,
'Whether to restart guests when the host reboots')
+flags.DEFINE_string('libvirt_vif_type', 'bridge',
+ 'Type of VIF to create.')
+flags.DEFINE_string('libvirt_vif_driver',
+ 'nova.virt.libvirt.vif.LibvirtBridgeDriver',
+ 'The libvirt VIF driver to configure the VIFs.')
def get_connection(read_only):
@@ -165,6 +170,7 @@ class LibvirtConnection(driver.ComputeDriver):
fw_class = utils.import_class(FLAGS.firewall_driver)
self.firewall_driver = fw_class(get_connection=self._get_connection)
+ self.vif_driver = utils.import_object(FLAGS.libvirt_vif_driver)
def init_host(self, host):
# Adopt existing VM's running here
@@ -256,7 +262,12 @@ class LibvirtConnection(driver.ComputeDriver):
infos.append(info)
return infos
- def destroy(self, instance, cleanup=True):
+ def plug_vifs(self, instance, network_info):
+ """Plugin VIFs into networks."""
+ for (network, mapping) in network_info:
+ self.vif_driver.plug(instance, network, mapping)
+
+ def destroy(self, instance, network_info, cleanup=True):
instance_name = instance['name']
try:
@@ -300,6 +311,9 @@ class LibvirtConnection(driver.ComputeDriver):
locals())
raise
+ for (network, mapping) in network_info:
+ self.vif_driver.unplug(instance, network, mapping)
+
def _wait_for_destroy():
"""Called at an interval until the VM is gone."""
instance_name = instance['name']
@@ -314,7 +328,8 @@ class LibvirtConnection(driver.ComputeDriver):
timer = utils.LoopingCall(_wait_for_destroy)
timer.start(interval=0.5, now=True)
- self.firewall_driver.unfilter_instance(instance)
+ self.firewall_driver.unfilter_instance(instance,
+ network_info=network_info)
if cleanup:
self._cleanup(instance)
@@ -331,25 +346,24 @@ class LibvirtConnection(driver.ComputeDriver):
if os.path.exists(target):
shutil.rmtree(target)
- @exception.wrap_exception
+ @exception.wrap_exception()
def attach_volume(self, instance_name, device_path, mountpoint):
virt_dom = self._lookup_by_name(instance_name)
mount_device = mountpoint.rpartition("/")[2]
- if device_path.startswith('/dev/'):
+ (type, protocol, name) = \
+ self._get_volume_device_info(vol['device_path'])
+ if type == 'block':
xml = """<disk type='block'>
<driver name='qemu' type='raw'/>
<source dev='%s'/>
<target dev='%s' bus='virtio'/>
</disk>""" % (device_path, mount_device)
- elif ':' in device_path:
- (protocol, name) = device_path.split(':')
+ elif type == 'network':
xml = """<disk type='network'>
<driver name='qemu' type='raw'/>
<source protocol='%s' name='%s'/>
<target dev='%s' bus='virtio'/>
- </disk>""" % (protocol,
- name,
- mount_device)
+ </disk>""" % (protocol, name, mount_device)
else:
raise exception.InvalidDevicePath(path=device_path)
@@ -375,7 +389,7 @@ class LibvirtConnection(driver.ComputeDriver):
if doc is not None:
doc.freeDoc()
- @exception.wrap_exception
+ @exception.wrap_exception()
def detach_volume(self, instance_name, mountpoint):
virt_dom = self._lookup_by_name(instance_name)
mount_device = mountpoint.rpartition("/")[2]
@@ -384,7 +398,7 @@ class LibvirtConnection(driver.ComputeDriver):
raise exception.DiskNotFound(location=mount_device)
virt_dom.detachDevice(xml)
- @exception.wrap_exception
+ @exception.wrap_exception()
def snapshot(self, instance, image_href):
"""Create snapshot from a running VM instance.
@@ -460,8 +474,8 @@ class LibvirtConnection(driver.ComputeDriver):
# Clean up
shutil.rmtree(temp_dir)
- @exception.wrap_exception
- def reboot(self, instance):
+ @exception.wrap_exception()
+ def reboot(self, instance, network_info):
"""Reboot a virtual machine, given an instance reference.
This method actually destroys and re-creates the domain to ensure the
@@ -476,7 +490,8 @@ class LibvirtConnection(driver.ComputeDriver):
# NOTE(itoumsn): self.shutdown() and wait instead of self.destroy() is
# better because we cannot ensure flushing dirty buffers
# in the guest OS. But, in case of KVM, shutdown() does not work...
- self.destroy(instance, False)
+ self.destroy(instance, network_info, cleanup=False)
+ self.plug_vifs(instance, network_info)
self.firewall_driver.setup_basic_filtering(instance)
self.firewall_driver.prepare_instance_filter(instance)
self._create_new_domain(xml)
@@ -501,32 +516,32 @@ class LibvirtConnection(driver.ComputeDriver):
timer = utils.LoopingCall(_wait_for_reboot)
return timer.start(interval=0.5, now=True)
- @exception.wrap_exception
+ @exception.wrap_exception()
def pause(self, instance, callback):
"""Pause VM instance"""
dom = self._lookup_by_name(instance.name)
dom.suspend()
- @exception.wrap_exception
+ @exception.wrap_exception()
def unpause(self, instance, callback):
"""Unpause paused VM instance"""
dom = self._lookup_by_name(instance.name)
dom.resume()
- @exception.wrap_exception
+ @exception.wrap_exception()
def suspend(self, instance, callback):
"""Suspend the specified instance"""
dom = self._lookup_by_name(instance.name)
dom.managedSave(0)
- @exception.wrap_exception
+ @exception.wrap_exception()
def resume(self, instance, callback):
"""resume the specified instance"""
dom = self._lookup_by_name(instance.name)
dom.create()
- @exception.wrap_exception
- def rescue(self, instance):
+ @exception.wrap_exception()
+ def rescue(self, instance, callback, network_info):
"""Loads a VM using rescue images.
A rescue is normally performed when something goes wrong with the
@@ -535,7 +550,7 @@ class LibvirtConnection(driver.ComputeDriver):
data recovery.
"""
- self.destroy(instance, False)
+ self.destroy(instance, network_info, cleanup=False)
xml = self.to_xml(instance, rescue=True)
rescue_images = {'image_id': FLAGS.rescue_image_id,
@@ -563,24 +578,24 @@ class LibvirtConnection(driver.ComputeDriver):
timer = utils.LoopingCall(_wait_for_rescue)
return timer.start(interval=0.5, now=True)
- @exception.wrap_exception
- def unrescue(self, instance):
+ @exception.wrap_exception()
+ def unrescue(self, instance, network_info):
"""Reboot the VM which is being rescued back into primary images.
Because reboot destroys and re-creates instances, unresue should
simply call reboot.
"""
- self.reboot(instance)
+ self.reboot(instance, network_info)
- @exception.wrap_exception
+ @exception.wrap_exception()
def poll_rescued_instances(self, timeout):
pass
# NOTE(ilyaalekseyev): Implementation like in multinics
# for xenapi(tr3buchet)
- @exception.wrap_exception
- def spawn(self, instance, network_info=None, block_device_mapping=None):
+ @exception.wrap_exception()
+ def spawn(self, instance, network_info, block_device_mapping=None):
xml = self.to_xml(instance, False, network_info=network_info,
block_device_mapping=block_device_mapping)
block_device_mapping = block_device_mapping or []
@@ -642,7 +657,7 @@ class LibvirtConnection(driver.ComputeDriver):
LOG.info(_('Contents of file %(fpath)s: %(contents)r') % locals())
return contents
- @exception.wrap_exception
+ @exception.wrap_exception()
def get_console_output(self, instance):
console_log = os.path.join(FLAGS.instances_path, instance['name'],
'console.log')
@@ -663,7 +678,7 @@ class LibvirtConnection(driver.ComputeDriver):
return self._dump_file(fpath)
- @exception.wrap_exception
+ @exception.wrap_exception()
def get_ajax_console(self, instance):
def get_open_port():
start_port, end_port = FLAGS.ajaxterm_portrange.split("-")
@@ -704,7 +719,7 @@ class LibvirtConnection(driver.ComputeDriver):
def get_host_ip_addr(self):
return FLAGS.my_ip
- @exception.wrap_exception
+ @exception.wrap_exception()
def get_vnc_console(self, instance):
def get_vnc_port_for_instance(instance_name):
virt_dom = self._lookup_by_name(instance_name)
@@ -771,8 +786,6 @@ class LibvirtConnection(driver.ComputeDriver):
def _create_image(self, inst, libvirt_xml, suffix='', disk_images=None,
network_info=None, block_device_mapping=None):
block_device_mapping = block_device_mapping or []
- if not network_info:
- network_info = netutils.get_network_info(inst)
if not suffix:
suffix = ''
@@ -881,18 +894,23 @@ class LibvirtConnection(driver.ComputeDriver):
have_injected_networks = True
address = mapping['ips'][0]['ip']
+ netmask = mapping['ips'][0]['netmask']
address_v6 = None
+ gateway_v6 = None
+ netmask_v6 = None
if FLAGS.use_ipv6:
address_v6 = mapping['ip6s'][0]['ip']
+ netmask_v6 = mapping['ip6s'][0]['netmask']
+ gateway_v6 = mapping['gateway6']
net_info = {'name': 'eth%d' % ifc_num,
'address': address,
- 'netmask': network_ref['netmask'],
- 'gateway': network_ref['gateway'],
- 'broadcast': network_ref['broadcast'],
- 'dns': network_ref['dns'],
+ 'netmask': netmask,
+ 'gateway': mapping['gateway'],
+ 'broadcast': mapping['broadcast'],
+ 'dns': mapping['dns'],
'address_v6': address_v6,
- 'gateway_v6': network_ref['gateway_v6'],
- 'netmask_v6': network_ref['netmask_v6']}
+ 'gateway6': gateway_v6,
+ 'netmask_v6': netmask_v6}
nets.append(net_info)
if have_injected_networks:
@@ -926,40 +944,6 @@ class LibvirtConnection(driver.ComputeDriver):
if FLAGS.libvirt_type == 'uml':
utils.execute('sudo', 'chown', 'root', basepath('disk'))
- def _get_nic_for_xml(self, network, mapping):
- # Assume that the gateway also acts as the dhcp server.
- dhcp_server = network['gateway']
- gateway_v6 = network['gateway_v6']
- mac_id = mapping['mac'].replace(':', '')
-
- if FLAGS.allow_project_net_traffic:
- template = "<parameter name=\"%s\"value=\"%s\" />\n"
- net, mask = netutils.get_net_and_mask(network['cidr'])
- values = [("PROJNET", net), ("PROJMASK", mask)]
- if FLAGS.use_ipv6:
- net_v6, prefixlen_v6 = netutils.get_net_and_prefixlen(
- network['cidr_v6'])
- values.extend([("PROJNETV6", net_v6),
- ("PROJMASKV6", prefixlen_v6)])
-
- extra_params = "".join([template % value for value in values])
- else:
- extra_params = "\n"
-
- result = {
- 'id': mac_id,
- 'bridge_name': network['bridge'],
- 'mac_address': mapping['mac'],
- 'ip_address': mapping['ips'][0]['ip'],
- 'dhcp_server': dhcp_server,
- 'extra_params': extra_params,
- }
-
- if gateway_v6:
- result['gateway_v6'] = gateway_v6 + "/128"
-
- return result
-
root_mount_device = 'vda' # FIXME for now. it's hard coded.
local_mount_device = 'vdb' # FIXME for now. it's hard coded.
@@ -971,6 +955,16 @@ class LibvirtConnection(driver.ComputeDriver):
return True
return False
+ @exception.wrap_exception
+ def _get_volume_device_info(self, device_path):
+ if device_path.startswith('/dev/'):
+ return ('block', None, None)
+ elif ':' in device_path:
+ (protocol, name) = device_path.split(':')
+ return ('network', protocol, name)
+ else:
+ raise exception.InvalidDevicePath(path=device_path)
+
def _prepare_xml_info(self, instance, rescue=False, network_info=None,
block_device_mapping=None):
block_device_mapping = block_device_mapping or []
@@ -981,7 +975,7 @@ class LibvirtConnection(driver.ComputeDriver):
nics = []
for (network, mapping) in network_info:
- nics.append(self._get_nic_for_xml(network, mapping))
+ nics.append(self.vif_driver.plug(instance, network, mapping))
# FIXME(vish): stick this in db
inst_type_id = instance['instance_type_id']
inst_type = instance_types.get_instance_type(inst_type_id)
@@ -993,6 +987,9 @@ class LibvirtConnection(driver.ComputeDriver):
for vol in block_device_mapping:
vol['mount_device'] = _strip_dev(vol['mount_device'])
+ (vol['type'], vol['protocol'], vol['name']) = \
+ self._get_volume_device_info(vol['device_path'])
+
ebs_root = self._volume_in_mapping(self.root_mount_device,
block_device_mapping)
if self._volume_in_mapping(self.local_mount_device,
@@ -1010,14 +1007,14 @@ class LibvirtConnection(driver.ComputeDriver):
'rescue': rescue,
'local': local_gb,
'driver_type': driver_type,
+ 'vif_type': FLAGS.libvirt_vif_type,
'nics': nics,
'ebs_root': ebs_root,
'volumes': block_device_mapping}
- if FLAGS.vnc_enabled:
- if FLAGS.libvirt_type != 'lxc':
- xml_info['vncserver_host'] = FLAGS.vncserver_host
- xml_info['vnc_keymap'] = FLAGS.vnc_keymap
+ if FLAGS.vnc_enabled and FLAGS.libvirt_type not in ('lxc', 'uml'):
+ xml_info['vncserver_host'] = FLAGS.vncserver_host
+ xml_info['vnc_keymap'] = FLAGS.vnc_keymap
if not rescue:
if instance['kernel_id']:
xml_info['kernel'] = xml_info['basepath'] + "/kernel"
@@ -1580,9 +1577,10 @@ class LibvirtConnection(driver.ComputeDriver):
timer.f = wait_for_live_migration
timer.start(interval=0.5, now=True)
- def unfilter_instance(self, instance_ref):
+ def unfilter_instance(self, instance_ref, network_info):
"""See comments of same method in firewall_driver."""
- self.firewall_driver.unfilter_instance(instance_ref)
+ self.firewall_driver.unfilter_instance(instance_ref,
+ network_info=network_info)
def update_host_status(self):
"""See xenapi_conn.py implementation."""
@@ -1591,3 +1589,7 @@ class LibvirtConnection(driver.ComputeDriver):
def get_host_stats(self, refresh=False):
"""See xenapi_conn.py implementation."""
pass
+
+ def set_host_enabled(self, host, enabled):
+ """Sets the specified host's ability to accept new instances."""
+ pass
diff --git a/nova/virt/libvirt/firewall.py b/nova/virt/libvirt/firewall.py
index b99f2ffb0..9ce57b6c9 100644
--- a/nova/virt/libvirt/firewall.py
+++ b/nova/virt/libvirt/firewall.py
@@ -46,7 +46,7 @@ class FirewallDriver(object):
At this point, the instance isn't running yet."""
raise NotImplementedError()
- def unfilter_instance(self, instance):
+ def unfilter_instance(self, instance, network_info=None):
"""Stop filtering instance"""
raise NotImplementedError()
@@ -300,9 +300,10 @@ class NWFilterFirewall(FirewallDriver):
# execute in a native thread and block current greenthread until done
tpool.execute(self._conn.nwfilterDefineXML, xml)
- def unfilter_instance(self, instance):
+ def unfilter_instance(self, instance, network_info=None):
"""Clear out the nwfilter rules."""
- network_info = netutils.get_network_info(instance)
+ if not network_info:
+ network_info = netutils.get_network_info(instance)
instance_name = instance.name
for (network, mapping) in network_info:
nic_id = mapping['mac'].replace(':', '')
@@ -542,11 +543,11 @@ class IptablesFirewallDriver(FirewallDriver):
"""No-op. Everything is done in prepare_instance_filter"""
pass
- def unfilter_instance(self, instance):
+ def unfilter_instance(self, instance, network_info=None):
if self.instances.pop(instance['id'], None):
self.remove_filters_for_instance(instance)
self.iptables.apply()
- self.nwfilter.unfilter_instance(instance)
+ self.nwfilter.unfilter_instance(instance, network_info)
else:
LOG.info(_('Attempted to unfilter instance %s which is not '
'filtered'), instance['id'])
@@ -620,7 +621,7 @@ class IptablesFirewallDriver(FirewallDriver):
ipv4_rules += ['-j $provider']
ipv6_rules += ['-j $provider']
- dhcp_servers = [network['gateway'] for (network, _m) in network_info]
+ dhcp_servers = [info['gateway'] for (_n, info) in network_info]
for dhcp_server in dhcp_servers:
ipv4_rules.append('-s %s -p udp --sport 67 --dport 68 '
@@ -637,7 +638,7 @@ class IptablesFirewallDriver(FirewallDriver):
# they're not worth the clutter.
if FLAGS.use_ipv6:
# Allow RA responses
- gateways_v6 = [network['gateway_v6'] for (network, _m) in
+ gateways_v6 = [mapping['gateway6'] for (_n, mapping) in
network_info]
for gateway_v6 in gateways_v6:
ipv6_rules.append(
@@ -645,8 +646,8 @@ class IptablesFirewallDriver(FirewallDriver):
#Allow project network traffic
if FLAGS.allow_project_net_traffic:
- cidrv6s = [network['cidr_v6'] for (network, _m)
- in network_info]
+ cidrv6s = [network['cidr_v6'] for (network, _m) in
+ network_info]
for cidrv6 in cidrv6s:
ipv6_rules.append('-s %s -j ACCEPT' % (cidrv6,))
diff --git a/nova/virt/libvirt/netutils.py b/nova/virt/libvirt/netutils.py
index 0bad84f7c..041eacb2d 100644
--- a/nova/virt/libvirt/netutils.py
+++ b/nova/virt/libvirt/netutils.py
@@ -49,31 +49,36 @@ def get_ip_version(cidr):
def get_network_info(instance):
+ # TODO(tr3buchet): this function needs to go away! network info
+ # MUST be passed down from compute
# TODO(adiantum) If we will keep this function
# we should cache network_info
admin_context = context.get_admin_context()
- ip_addresses = db.fixed_ip_get_all_by_instance(admin_context,
- instance['id'])
+ fixed_ips = db.fixed_ip_get_by_instance(admin_context, instance['id'])
+ vifs = db.virtual_interface_get_by_instance(admin_context, instance['id'])
networks = db.network_get_all_by_instance(admin_context,
instance['id'])
- flavor = db.instance_type_get_by_id(admin_context,
+ flavor = db.instance_type_get(admin_context,
instance['instance_type_id'])
network_info = []
- for network in networks:
- network_ips = [ip for ip in ip_addresses
- if ip['network_id'] == network['id']]
+ for vif in vifs:
+ network = vif['network']
+
+ # 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']]
def ip_dict(ip):
return {
- 'ip': ip['address'],
+ 'ip': ip,
'netmask': network['netmask'],
'enabled': '1'}
def ip6_dict():
prefix = network['cidr_v6']
- mac = instance['mac_address']
+ mac = vif['address']
project_id = instance['project_id']
return {
'ip': ipv6.to_global(prefix, mac, project_id),
@@ -84,11 +89,16 @@ def get_network_info(instance):
'label': network['label'],
'gateway': network['gateway'],
'broadcast': network['broadcast'],
- 'mac': instance['mac_address'],
+ 'mac': vif['address'],
'rxtx_cap': flavor['rxtx_cap'],
- 'dns': [network['dns']],
+ 'dns': [],
'ips': [ip_dict(ip) for ip in network_ips]}
+ if network['dns1']:
+ mapping['dns'].append(network['dns1'])
+ if network['dns2']:
+ mapping['dns'].append(network['dns2'])
+
if FLAGS.use_ipv6:
mapping['ip6s'] = [ip6_dict()]
mapping['gateway6'] = network['gateway_v6']
diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py
new file mode 100644
index 000000000..24d45d1a7
--- /dev/null
+++ b/nova/virt/libvirt/vif.py
@@ -0,0 +1,134 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (C) 2011 Midokura KK
+# Copyright (C) 2011 Nicira, Inc
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""VIF drivers for libvirt."""
+
+from nova import flags
+from nova import log as logging
+from nova.network import linux_net
+from nova.virt.libvirt import netutils
+from nova import utils
+from nova.virt.vif import VIFDriver
+
+LOG = logging.getLogger('nova.virt.libvirt.vif')
+
+FLAGS = flags.FLAGS
+
+flags.DEFINE_string('libvirt_ovs_bridge', 'br-int',
+ 'Name of Integration Bridge used by Open vSwitch')
+
+
+class LibvirtBridgeDriver(VIFDriver):
+ """VIF driver for Linux bridge."""
+
+ def _get_configurations(self, network, mapping):
+ """Get a dictionary of VIF configurations for bridge type."""
+ # Assume that the gateway also acts as the dhcp server.
+ gateway6 = mapping.get('gateway6')
+ mac_id = mapping['mac'].replace(':', '')
+
+ if FLAGS.allow_project_net_traffic:
+ template = "<parameter name=\"%s\"value=\"%s\" />\n"
+ net, mask = netutils.get_net_and_mask(network['cidr'])
+ values = [("PROJNET", net), ("PROJMASK", mask)]
+ if FLAGS.use_ipv6:
+ net_v6, prefixlen_v6 = netutils.get_net_and_prefixlen(
+ network['cidr_v6'])
+ values.extend([("PROJNETV6", net_v6),
+ ("PROJMASKV6", prefixlen_v6)])
+
+ extra_params = "".join([template % value for value in values])
+ else:
+ extra_params = "\n"
+
+ result = {
+ 'id': mac_id,
+ 'bridge_name': network['bridge'],
+ 'mac_address': mapping['mac'],
+ 'ip_address': mapping['ips'][0]['ip'],
+ 'dhcp_server': mapping['dhcp_server'],
+ 'extra_params': extra_params,
+ }
+
+ if gateway6:
+ result['gateway6'] = gateway6 + "/128"
+
+ return result
+
+ def plug(self, instance, network, mapping):
+ """Ensure that the bridge exists, and add VIF to it."""
+ if (not network.get('multi_host') and
+ mapping.get('should_create_bridge')):
+ if mapping.get('should_create_vlan'):
+ LOG.debug(_('Ensuring vlan %(vlan)s and bridge %(bridge)s'),
+ {'vlan': network['vlan'],
+ 'bridge': network['bridge']})
+ linux_net.ensure_vlan_bridge(network['vlan'],
+ network['bridge'],
+ network['bridge_interface'])
+ else:
+ LOG.debug(_("Ensuring bridge %s"), network['bridge'])
+ linux_net.ensure_bridge(network['bridge'],
+ network['bridge_interface'])
+
+ return self._get_configurations(network, mapping)
+
+ def unplug(self, instance, network, mapping):
+ """No manual unplugging required."""
+ pass
+
+
+class LibvirtOpenVswitchDriver(VIFDriver):
+ """VIF driver for Open vSwitch."""
+
+ def plug(self, instance, network, mapping):
+ vif_id = str(instance['id']) + "-" + str(network['id'])
+ dev = "tap-%s" % vif_id
+ iface_id = "nova-" + vif_id
+ if not linux_net._device_exists(dev):
+ utils.execute('sudo', 'ip', 'tuntap', 'add', dev, 'mode', 'tap')
+ utils.execute('sudo', 'ip', 'link', 'set', dev, 'up')
+ utils.execute('sudo', 'ovs-vsctl', '--', '--may-exist', 'add-port',
+ FLAGS.libvirt_ovs_bridge, dev,
+ '--', 'set', 'Interface', dev,
+ "external-ids:iface-id=%s" % iface_id,
+ '--', 'set', 'Interface', dev,
+ "external-ids:iface-status=active",
+ '--', 'set', 'Interface', dev,
+ "external-ids:attached-mac=%s" % mapping['mac'])
+
+ result = {
+ 'script': '',
+ 'name': dev,
+ 'mac_address': mapping['mac']}
+ return result
+
+ def unplug(self, instance, network, mapping):
+ """Unplug the VIF from the network by deleting the port from
+ the bridge."""
+ vif_id = str(instance['id']) + "-" + str(network['id'])
+ dev = "tap-%s" % vif_id
+ try:
+ utils.execute('sudo', 'ovs-vsctl', 'del-port',
+ FLAGS.flat_network_bridge, dev)
+ utils.execute('sudo', 'ip', 'link', 'delete', dev)
+ except:
+ LOG.warning(_("Failed while unplugging vif of instance '%s'"),
+ instance['name'])
+ raise
diff --git a/nova/virt/vif.py b/nova/virt/vif.py
new file mode 100644
index 000000000..b78689957
--- /dev/null
+++ b/nova/virt/vif.py
@@ -0,0 +1,30 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (C) 2011 Midokura KK
+# 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.
+
+"""VIF module common to all virt layers."""
+
+
+class VIFDriver(object):
+ """Abstract class that defines generic interfaces for all VIF drivers."""
+
+ def plug(self, instance, network, mapping):
+ """Plug VIF into network."""
+ raise NotImplementedError()
+
+ def unplug(self, instance, network, mapping):
+ """Unplug VIF from network."""
+ raise NotImplementedError()
diff --git a/nova/virt/vmwareapi/network_utils.py b/nova/virt/vmwareapi/network_utils.py
index e77842535..08e3bf0b1 100644
--- a/nova/virt/vmwareapi/network_utils.py
+++ b/nova/virt/vmwareapi/network_utils.py
@@ -45,10 +45,30 @@ def get_network_with_the_name(session, network_name="vmnet0"):
networks = session._call_method(vim_util,
"get_properties_for_a_collection_of_objects",
"Network", vm_networks, ["summary.name"])
- for network in networks:
- if network.propSet[0].val == network_name:
- return network.obj
- return None
+ network_obj = {}
+ for network in vm_networks:
+ # Get network properties
+ if network._type == 'DistributedVirtualPortgroup':
+ props = session._call_method(vim_util,
+ "get_dynamic_property", network,
+ "DistributedVirtualPortgroup", "config")
+ # NOTE(asomya): This only works on ESXi if the port binding is
+ # set to ephemeral
+ if props.name == network_name:
+ network_obj['type'] = 'DistributedVirtualPortgroup'
+ network_obj['dvpg'] = props.key
+ network_obj['dvsw'] = props.distributedVirtualSwitch.value
+ else:
+ props = session._call_method(vim_util,
+ "get_dynamic_property", network,
+ "Network", "summary.name")
+ if props == network_name:
+ network_obj['type'] = 'Network'
+ network_obj['name'] = network_name
+ if (len(network_obj) > 0):
+ return network_obj
+ else:
+ return None
def get_vswitch_for_vlan_interface(session, vlan_interface):
diff --git a/nova/virt/vmwareapi/vif.py b/nova/virt/vmwareapi/vif.py
new file mode 100644
index 000000000..b3e43b209
--- /dev/null
+++ b/nova/virt/vmwareapi/vif.py
@@ -0,0 +1,95 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 Citrix Systems, Inc.
+# Copyright 2011 OpenStack LLC.
+#
+# 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.
+
+"""VIF drivers for VMWare."""
+
+from nova import db
+from nova import exception
+from nova import flags
+from nova import log as logging
+from nova import utils
+from nova.virt.vif import VIFDriver
+from nova.virt.vmwareapi_conn import VMWareAPISession
+from nova.virt.vmwareapi import network_utils
+
+
+LOG = logging.getLogger("nova.virt.vmwareapi.vif")
+
+FLAGS = flags.FLAGS
+
+
+class VMWareVlanBridgeDriver(VIFDriver):
+ """VIF Driver to setup bridge/VLAN networking using VMWare API."""
+
+ def plug(self, instance, network, mapping):
+ """Create a vlan and bridge unless they already exist."""
+ vlan_num = network['vlan']
+ bridge = network['bridge']
+ bridge_interface = network['bridge_interface']
+
+ # Open vmwareapi session
+ host_ip = FLAGS.vmwareapi_host_ip
+ host_username = FLAGS.vmwareapi_host_username
+ host_password = FLAGS.vmwareapi_host_password
+ if not host_ip or host_username is None or host_password is None:
+ raise Exception(_('Must specify vmwareapi_host_ip, '
+ 'vmwareapi_host_username '
+ 'and vmwareapi_host_password to use '
+ 'connection_type=vmwareapi'))
+ session = VMWareAPISession(host_ip, host_username, host_password,
+ FLAGS.vmwareapi_api_retry_count)
+ vlan_interface = bridge_interface
+ # Check if the vlan_interface physical network adapter exists on the
+ # host.
+ if not network_utils.check_if_vlan_interface_exists(session,
+ vlan_interface):
+ raise exception.NetworkAdapterNotFound(adapter=vlan_interface)
+
+ # Get the vSwitch associated with the Physical Adapter
+ vswitch_associated = network_utils.get_vswitch_for_vlan_interface(
+ session, vlan_interface)
+ if vswitch_associated is None:
+ raise exception.SwicthNotFoundForNetworkAdapter(
+ adapter=vlan_interface)
+ # Check whether bridge already exists and retrieve the the ref of the
+ # network whose name_label is "bridge"
+ network_ref = network_utils.get_network_with_the_name(session, bridge)
+ if network_ref is None:
+ # Create a port group on the vSwitch associated with the
+ # vlan_interface corresponding physical network adapter on the ESX
+ # host.
+ network_utils.create_port_group(session, bridge,
+ vswitch_associated, vlan_num)
+ else:
+ # Get the vlan id and vswitch corresponding to the port group
+ pg_vlanid, pg_vswitch = \
+ network_utils.get_vlanid_and_vswitch_for_portgroup(session,
+ bridge)
+
+ # Check if the vswitch associated is proper
+ if pg_vswitch != vswitch_associated:
+ raise exception.InvalidVLANPortGroup(
+ bridge=bridge, expected=vswitch_associated,
+ actual=pg_vswitch)
+
+ # Check if the vlan id is proper for the port group
+ if pg_vlanid != vlan_num:
+ raise exception.InvalidVLANTag(bridge=bridge, tag=vlan_num,
+ pgroup=pg_vlanid)
+
+ def unplug(self, instance, network, mapping):
+ pass
diff --git a/nova/virt/vmwareapi/vm_util.py b/nova/virt/vmwareapi/vm_util.py
index a2fa7600c..55578dd3c 100644
--- a/nova/virt/vmwareapi/vm_util.py
+++ b/nova/virt/vmwareapi/vm_util.py
@@ -40,7 +40,7 @@ def split_datastore_path(datastore_path):
def get_vm_create_spec(client_factory, instance, data_store_name,
network_name="vmnet0",
- os_type="otherGuest"):
+ os_type="otherGuest", network_ref=None):
"""Builds the VM Create spec."""
config_spec = client_factory.create('ns0:VirtualMachineConfigSpec')
config_spec.name = instance.name
@@ -61,8 +61,12 @@ def get_vm_create_spec(client_factory, instance, data_store_name,
config_spec.numCPUs = int(instance.vcpus)
config_spec.memoryMB = int(instance.memory_mb)
+ mac_address = None
+ if instance['mac_addresses']:
+ mac_address = instance['mac_addresses'][0]['address']
+
nic_spec = create_network_spec(client_factory,
- network_name, instance.mac_address)
+ network_name, mac_address)
device_config_spec = [nic_spec]
@@ -89,7 +93,8 @@ def create_controller_spec(client_factory, key):
return virtual_device_config
-def create_network_spec(client_factory, network_name, mac_address):
+def create_network_spec(client_factory, network_name, mac_address,
+ network_ref=None):
"""
Builds a config spec for the addition of a new network
adapter to the VM.
@@ -101,9 +106,24 @@ def create_network_spec(client_factory, network_name, mac_address):
# Get the recommended card type for the VM based on the guest OS of the VM
net_device = client_factory.create('ns0:VirtualPCNet32')
- backing = \
- client_factory.create('ns0:VirtualEthernetCardNetworkBackingInfo')
- backing.deviceName = network_name
+ # NOTE(asomya): Only works on ESXi if the portgroup binding is set to
+ # ephemeral. Invalid configuration if set to static and the NIC does
+ # not come up on boot if set to dynamic.
+ backing = None
+ if (network_ref['type'] == "DistributedVirtualPortgroup"):
+ backing_name = \
+ 'ns0:VirtualEthernetCardDistributedVirtualPortBackingInfo'
+ backing = \
+ client_factory.create(backing_name)
+ portgroup = \
+ client_factory.create('ns0:DistributedVirtualSwitchPortConnection')
+ portgroup.switchUuid = network_ref['dvsw']
+ portgroup.portgroupKey = network_ref['dvpg']
+ backing.port = portgroup
+ else:
+ backing = \
+ client_factory.create('ns0:VirtualEthernetCardNetworkBackingInfo')
+ backing.deviceName = network_name
connectable_spec = \
client_factory.create('ns0:VirtualDeviceConnectInfo')
@@ -274,9 +294,11 @@ def get_dummy_vm_create_spec(client_factory, name, data_store_name):
return config_spec
-def get_machine_id_change_spec(client_factory, mac, ip_addr, netmask, gateway):
+def get_machine_id_change_spec(client_factory, mac, ip_addr, netmask,
+ gateway, broadcast, dns):
"""Builds the machine id change config spec."""
- machine_id_str = "%s;%s;%s;%s" % (mac, ip_addr, netmask, gateway)
+ machine_id_str = "%s;%s;%s;%s;%s;%s" % (mac, ip_addr, netmask,
+ gateway, broadcast, dns)
virtual_machine_config_spec = \
client_factory.create('ns0:VirtualMachineConfigSpec')
diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py
index 5f76b0df5..7e7d2dac3 100644
--- a/nova/virt/vmwareapi/vmops.py
+++ b/nova/virt/vmwareapi/vmops.py
@@ -31,6 +31,7 @@ from nova import db
from nova import exception
from nova import flags
from nova import log as logging
+from nova import utils
from nova.compute import power_state
from nova.virt.vmwareapi import vim_util
from nova.virt.vmwareapi import vm_util
@@ -38,6 +39,10 @@ from nova.virt.vmwareapi import vmware_images
from nova.virt.vmwareapi import network_utils
FLAGS = flags.FLAGS
+flags.DEFINE_string('vmware_vif_driver',
+ 'nova.virt.vmwareapi.vif.VMWareVlanBridgeDriver',
+ 'The VMWare VIF driver to configure the VIFs.')
+
LOG = logging.getLogger("nova.virt.vmwareapi.vmops")
VMWARE_POWER_STATES = {
@@ -52,6 +57,7 @@ class VMWareVMOps(object):
def __init__(self, session):
"""Initializer."""
self._session = session
+ self._vif_driver = utils.import_object(FLAGS.vmware_vif_driver)
def _wait_with_callback(self, instance_id, task, callback):
"""Waits for the task to finish and does a callback after."""
@@ -83,7 +89,7 @@ class VMWareVMOps(object):
LOG.debug(_("Got total of %s instances") % str(len(lst_vm_names)))
return lst_vm_names
- def spawn(self, instance):
+ def spawn(self, instance, network_info):
"""
Creates a VM instance.
@@ -116,8 +122,10 @@ class VMWareVMOps(object):
net_name)
if network_ref is None:
raise exception.NetworkNotFoundForBridge(bridge=net_name)
+ return network_ref
- _check_if_network_bridge_exists()
+ self.plug_vifs(instance, network_info)
+ network_obj = _check_if_network_bridge_exists()
def _get_datastore_ref():
"""Get the datastore list and choose the first local storage."""
@@ -175,8 +183,10 @@ class VMWareVMOps(object):
vm_folder_mor, res_pool_mor = _get_vmfolder_and_res_pool_mors()
# Get the create vm config spec
- config_spec = vm_util.get_vm_create_spec(client_factory, instance,
- data_store_name, net_name, os_type)
+ config_spec = vm_util.get_vm_create_spec(
+ client_factory, instance,
+ data_store_name, net_name, os_type,
+ network_obj)
def _execute_create_vm():
"""Create VM on ESX host."""
@@ -472,11 +482,14 @@ class VMWareVMOps(object):
_clean_temp_data()
- def reboot(self, instance):
+ def reboot(self, instance, network_info):
"""Reboot a VM instance."""
vm_ref = self._get_vm_ref_from_the_name(instance.name)
if vm_ref is None:
raise exception.InstanceNotFound(instance_id=instance.id)
+
+ self.plug_vifs(instance, network_info)
+
lst_properties = ["summary.guest.toolsStatus", "runtime.powerState",
"summary.guest.toolsRunningStatus"]
props = self._session._call_method(vim_util, "get_object_properties",
@@ -514,7 +527,7 @@ class VMWareVMOps(object):
self._session._wait_for_task(instance.id, reset_task)
LOG.debug(_("Did hard reboot of VM %s") % instance.name)
- def destroy(self, instance):
+ def destroy(self, instance, network_info):
"""
Destroy a VM instance. Steps followed are:
1. Power off the VM, if it is in poweredOn state.
@@ -560,6 +573,8 @@ class VMWareVMOps(object):
LOG.warn(_("In vmwareapi:vmops:destroy, got this exception"
" while un-registering the VM: %s") % str(excep))
+ self._unplug_vifs(instance, network_info)
+
# Delete the folder holding the VM related content on
# the datastore.
try:
@@ -706,19 +721,29 @@ class VMWareVMOps(object):
Set the machine id of the VM for guest tools to pick up and change
the IP.
"""
+ admin_context = context.get_admin_context()
vm_ref = self._get_vm_ref_from_the_name(instance.name)
if vm_ref is None:
raise exception.InstanceNotFound(instance_id=instance.id)
network = db.network_get_by_instance(context.get_admin_context(),
instance['id'])
- mac_addr = instance.mac_address
+ mac_address = None
+ if instance['mac_addresses']:
+ mac_address = instance['mac_addresses'][0]['address']
+
net_mask = network["netmask"]
gateway = network["gateway"]
- ip_addr = db.instance_get_fixed_address(context.get_admin_context(),
- instance['id'])
+ broadcast = network["broadcast"]
+ dns = network["dns"]
+
+ addresses = db.instance_get_fixed_addresses(admin_context,
+ instance['id'])
+ ip_addr = addresses[0] if addresses else None
+
machine_id_chanfge_spec = \
- vm_util.get_machine_id_change_spec(client_factory, mac_addr,
- ip_addr, net_mask, gateway)
+ vm_util.get_machine_id_change_spec(client_factory, mac_address,
+ ip_addr, net_mask, gateway,
+ broadcast, dns)
LOG.debug(_("Reconfiguring VM instance %(name)s to set the machine id "
"with ip - %(ip_addr)s") %
({'name': instance.name,
@@ -778,3 +803,13 @@ class VMWareVMOps(object):
if vm.propSet[0].val == vm_name:
return vm.obj
return None
+
+ def plug_vifs(self, instance, network_info):
+ """Plug VIFs into networks."""
+ for (network, mapping) in network_info:
+ self._vif_driver.plug(instance, network, mapping)
+
+ def _unplug_vifs(self, instance, network_info):
+ """Unplug VIFs from networks."""
+ for (network, mapping) in network_info:
+ self._vif_driver.unplug(instance, network, mapping)
diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py
index 3c6345ec8..ce57847b2 100644
--- a/nova/virt/vmwareapi_conn.py
+++ b/nova/virt/vmwareapi_conn.py
@@ -124,21 +124,21 @@ class VMWareESXConnection(driver.ComputeDriver):
"""List VM instances."""
return self._vmops.list_instances()
- def spawn(self, instance, network_info=None, block_device_mapping=None):
+ def spawn(self, instance, network_info, block_device_mapping=None):
"""Create VM instance."""
- self._vmops.spawn(instance)
+ self._vmops.spawn(instance, network_info)
def snapshot(self, instance, name):
"""Create snapshot from a running VM instance."""
self._vmops.snapshot(instance, name)
- def reboot(self, instance):
+ def reboot(self, instance, network_info):
"""Reboot VM instance."""
- self._vmops.reboot(instance)
+ self._vmops.reboot(instance, network_info)
- def destroy(self, instance):
+ def destroy(self, instance, network_info):
"""Destroy VM instance."""
- self._vmops.destroy(instance)
+ self._vmops.destroy(instance, network_info)
def pause(self, instance, callback):
"""Pause VM instance."""
@@ -190,6 +190,14 @@ class VMWareESXConnection(driver.ComputeDriver):
"""This method is supported only by libvirt."""
return
+ def set_host_enabled(self, host, enabled):
+ """Sets the specified host's ability to accept new instances."""
+ pass
+
+ def plug_vifs(self, instance, network_info):
+ """Plugs in VIFs to networks."""
+ self._vmops.plug_vifs(instance, network_info)
+
class VMWareAPISession(object):
"""
diff --git a/nova/virt/xenapi/vif.py b/nova/virt/xenapi/vif.py
new file mode 100644
index 000000000..527602243
--- /dev/null
+++ b/nova/virt/xenapi/vif.py
@@ -0,0 +1,140 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 Citrix Systems, Inc.
+# Copyright 2011 OpenStack LLC.
+# Copyright (C) 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.
+
+"""VIF drivers for XenAPI."""
+
+from nova import flags
+from nova import log as logging
+from nova.virt.vif import VIFDriver
+from nova.virt.xenapi.network_utils import NetworkHelper
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('xenapi_ovs_integration_bridge', 'xapi1',
+ 'Name of Integration Bridge used by Open vSwitch')
+
+LOG = logging.getLogger("nova.virt.xenapi.vif")
+
+
+class XenAPIBridgeDriver(VIFDriver):
+ """VIF Driver for XenAPI that uses XenAPI to create Networks."""
+
+ def plug(self, xenapi_session, vm_ref, instance, device, network,
+ network_mapping):
+ if network_mapping.get('should_create_vlan'):
+ network_ref = self.ensure_vlan_bridge(xenapi_session, network)
+ else:
+ network_ref = NetworkHelper.find_network_with_bridge(
+ xenapi_session, network['bridge'])
+ rxtx_cap = network_mapping.pop('rxtx_cap')
+ vif_rec = {}
+ vif_rec['device'] = str(device)
+ vif_rec['network'] = network_ref
+ vif_rec['VM'] = vm_ref
+ vif_rec['MAC'] = network_mapping['mac']
+ vif_rec['MTU'] = '1500'
+ vif_rec['other_config'] = {}
+ vif_rec['qos_algorithm_type'] = "ratelimit" if rxtx_cap else ''
+ vif_rec['qos_algorithm_params'] = \
+ {"kbps": str(rxtx_cap * 1024)} if rxtx_cap else {}
+ return vif_rec
+
+ def ensure_vlan_bridge(self, xenapi_session, network):
+ """Ensure that a VLAN bridge exists"""
+
+ vlan_num = network['vlan']
+ bridge = network['bridge']
+ bridge_interface = network['bridge_interface']
+ # Check whether bridge already exists
+ # Retrieve network whose name_label is "bridge"
+ network_ref = NetworkHelper.find_network_with_name_label(
+ xenapi_session, bridge)
+ if network_ref is None:
+ # If bridge does not exists
+ # 1 - create network
+ description = 'network for nova bridge %s' % bridge
+ network_rec = {'name_label': bridge,
+ 'name_description': description,
+ 'other_config': {}}
+ network_ref = xenapi_session.call_xenapi('network.create',
+ network_rec)
+ # 2 - find PIF for VLAN NOTE(salvatore-orlando): using double
+ # quotes inside single quotes as xapi filter only support
+ # tokens in double quotes
+ expr = 'field "device" = "%s" and \
+ field "VLAN" = "-1"' % bridge_interface
+ pifs = xenapi_session.call_xenapi('PIF.get_all_records_where',
+ expr)
+ pif_ref = None
+ # Multiple PIF are ok: we are dealing with a pool
+ if len(pifs) == 0:
+ raise Exception(_('Found no PIF for device %s') % \
+ bridge_interface)
+ for pif_ref in pifs.keys():
+ xenapi_session.call_xenapi('VLAN.create',
+ pif_ref,
+ str(vlan_num),
+ network_ref)
+ else:
+ # Check VLAN tag is appropriate
+ network_rec = xenapi_session.call_xenapi('network.get_record',
+ network_ref)
+ # Retrieve PIFs from network
+ for pif_ref in network_rec['PIFs']:
+ # Retrieve VLAN from PIF
+ pif_rec = xenapi_session.call_xenapi('PIF.get_record',
+ pif_ref)
+ pif_vlan = int(pif_rec['VLAN'])
+ # Raise an exception if VLAN != vlan_num
+ if pif_vlan != vlan_num:
+ raise Exception(_(
+ "PIF %(pif_rec['uuid'])s for network "
+ "%(bridge)s has VLAN id %(pif_vlan)d. "
+ "Expected %(vlan_num)d") % locals())
+
+ return network_ref
+
+ def unplug(self, instance, network, mapping):
+ pass
+
+
+class XenAPIOpenVswitchDriver(VIFDriver):
+ """VIF driver for Open vSwitch with XenAPI."""
+
+ def plug(self, xenapi_session, vm_ref, instance, device, network,
+ network_mapping):
+ # with OVS model, always plug into an OVS integration bridge
+ # that is already created
+ network_ref = NetworkHelper.find_network_with_bridge(xenapi_session,
+ FLAGS.xenapi_ovs_integration_bridge)
+ vif_rec = {}
+ vif_rec['device'] = str(device)
+ vif_rec['network'] = network_ref
+ vif_rec['VM'] = vm_ref
+ vif_rec['MAC'] = network_mapping['mac']
+ vif_rec['MTU'] = '1500'
+ vif_id = "nova-" + str(instance['id']) + "-" + str(network['id'])
+ vif_rec['qos_algorithm_type'] = ""
+ vif_rec['qos_algorithm_params'] = {}
+ # OVS on the hypervisor monitors this key and uses it to
+ # set the iface-id attribute
+ vif_rec['other_config'] = {"nicira-iface-id": vif_id}
+ return vif_rec
+
+ def unplug(self, instance, network, mapping):
+ pass
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index f91958c57..62863c6d8 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -23,6 +23,7 @@ import json
import os
import pickle
import re
+import sys
import tempfile
import time
import urllib
@@ -71,17 +72,51 @@ KERNEL_DIR = '/boot/guest'
class ImageType:
"""
Enumeration class for distinguishing different image types
- 0 - kernel/ramdisk image (goes on dom0's filesystem)
- 1 - disk image (local SR, partitioned by objectstore plugin)
- 2 - raw disk image (local SR, NOT partitioned by plugin)
- 3 - vhd disk image (local SR, NOT inspected by XS, PV assumed for
+ 0 - kernel image (goes on dom0's filesystem)
+ 1 - ramdisk image (goes on dom0's filesystem)
+ 2 - disk image (local SR, partitioned by objectstore plugin)
+ 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)
"""
- KERNEL_RAMDISK = 0
- DISK = 1
- DISK_RAW = 2
- DISK_VHD = 3
+ KERNEL = 0
+ RAMDISK = 1
+ DISK = 2
+ DISK_RAW = 3
+ DISK_VHD = 4
+
+ KERNEL_STR = "kernel"
+ RAMDISK_STR = "ramdisk"
+ DISK_STR = "os"
+ DISK_RAW_STR = "os_raw"
+ DISK_VHD_STR = "vhd"
+
+ @classmethod
+ def to_string(cls, image_type):
+ if image_type == ImageType.KERNEL:
+ return ImageType.KERNEL_STR
+ elif image_type == ImageType.RAMDISK:
+ return ImageType.RAMDISK_STR
+ elif image_type == ImageType.DISK:
+ return ImageType.DISK_STR
+ elif image_type == ImageType.DISK_RAW:
+ return ImageType.DISK_RAW_STR
+ elif image_type == ImageType.DISK_VHD:
+ return ImageType.VHD_STR
+
+ @classmethod
+ def from_string(cls, image_type_str):
+ if image_type_str == ImageType.KERNEL_STR:
+ return ImageType.KERNEL
+ elif image_type == ImageType.RAMDISK_STR:
+ return ImageType.RAMDISK
+ elif image_type == ImageType.DISK_STR:
+ return ImageType.DISK
+ elif image_type == ImageType.DISK_RAW_STR:
+ return ImageType.DISK_RAW
+ elif image_type == ImageType.DISK_VHD_STR:
+ return ImageType.VHD
class VMHelper(HelperBase):
@@ -145,7 +180,6 @@ class VMHelper(HelperBase):
'VCPUs_max': vcpus,
'VCPUs_params': {},
'xenstore_data': {}}
-
# Complete VM configuration record according to the image type
# non-raw/raw with PV kernel/raw in HVM mode
if use_pv_kernel:
@@ -240,26 +274,13 @@ class VMHelper(HelperBase):
raise StorageError(_('Unable to destroy VBD %s') % vbd_ref)
@classmethod
- def create_vif(cls, session, vm_ref, network_ref, mac_address,
- dev, rxtx_cap=0):
- """Create a VIF record. Returns a Deferred that gives the new
- VIF reference."""
- vif_rec = {}
- vif_rec['device'] = str(dev)
- vif_rec['network'] = network_ref
- vif_rec['VM'] = vm_ref
- vif_rec['MAC'] = mac_address
- vif_rec['MTU'] = '1500'
- vif_rec['other_config'] = {}
- vif_rec['qos_algorithm_type'] = "ratelimit" if rxtx_cap else ''
- vif_rec['qos_algorithm_params'] = \
- {"kbps": str(rxtx_cap * 1024)} if rxtx_cap else {}
- LOG.debug(_('Creating VIF for VM %(vm_ref)s,'
- ' network %(network_ref)s.') % locals())
- vif_ref = session.call_xenapi('VIF.create', vif_rec)
- LOG.debug(_('Created VIF %(vif_ref)s for VM %(vm_ref)s,'
- ' network %(network_ref)s.') % locals())
- return vif_ref
+ def destroy_vdi(cls, session, vdi_ref):
+ try:
+ task = session.call_xenapi('Async.VDI.destroy', vdi_ref)
+ session.wait_for_task(task)
+ except cls.XenAPI.Failure, exc:
+ LOG.exception(exc)
+ raise StorageError(_('Unable to destroy VDI %s') % vdi_ref)
@classmethod
def create_vdi(cls, session, sr_ref, name_label, virtual_size, read_only):
@@ -394,12 +415,12 @@ class VMHelper(HelperBase):
"""
LOG.debug(_("Asking xapi to fetch vhd image %(image)s")
% locals())
-
sr_ref = safe_find_sr(session)
- # NOTE(sirp): The Glance plugin runs under Python 2.4 which does not
- # have the `uuid` module. To work around this, we generate the uuids
- # here (under Python 2.6+) and pass them as arguments
+ # NOTE(sirp): The Glance plugin runs under Python 2.4
+ # which does not have the `uuid` module. To work around this,
+ # we generate the uuids here (under Python 2.6+) and
+ # pass them as arguments
uuid_stack = [str(uuid.uuid4()) for i in xrange(2)]
glance_host, glance_port = \
@@ -449,18 +470,20 @@ class VMHelper(HelperBase):
# FIXME(sirp): Since the Glance plugin seems to be required for the
# VHD disk, it may be worth using the plugin for both VHD and RAW and
# 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)
glance_client, image_id = nova.image.get_glance_client(image)
meta, image_file = glance_client.get_image(image_id)
virtual_size = int(meta['size'])
vdi_size = virtual_size
- LOG.debug(_("Size for image %(image)s:%(virtual_size)d") % locals())
-
+ LOG.debug(_("Size for image %(image)s:" +
+ "%(virtual_size)d") % locals())
if image_type == ImageType.DISK:
# Make room for MBR.
vdi_size += MBR_SIZE_BYTES
- elif image_type == ImageType.KERNEL_RAMDISK and \
+ elif image_type in (ImageType.KERNEL, ImageType.RAMDISK) and \
vdi_size > FLAGS.max_kernel_ramdisk_size:
max_size = FLAGS.max_kernel_ramdisk_size
raise exception.Error(
@@ -469,29 +492,45 @@ class VMHelper(HelperBase):
name_label = get_name_label_for_image(image)
vdi_ref = cls.create_vdi(session, sr_ref, name_label, vdi_size, False)
-
- with_vdi_attached_here(session, vdi_ref, False,
- lambda dev:
- _stream_disk(dev, image_type,
- virtual_size, image_file))
- if image_type == ImageType.KERNEL_RAMDISK:
- #we need to invoke a plugin for copying VDI's
- #content into proper path
- LOG.debug(_("Copying VDI %s to /boot/guest on dom0"), vdi_ref)
- fn = "copy_kernel_vdi"
- args = {}
- args['vdi-ref'] = vdi_ref
- #let the plugin copy the correct number of bytes
- args['image-size'] = str(vdi_size)
- task = session.async_call_plugin('glance', fn, args)
- filename = session.wait_for_task(task, instance_id)
- #remove the VDI as it is not needed anymore
- session.get_xenapi().VDI.destroy(vdi_ref)
- LOG.debug(_("Kernel/Ramdisk VDI %s destroyed"), vdi_ref)
- return filename
- else:
+ # From this point we have a VDI on Xen host;
+ # If anything goes wrong, we need to remember its uuid.
+ try:
+ filename = None
vdi_uuid = session.get_xenapi().VDI.get_uuid(vdi_ref)
- return [dict(vdi_type='os', vdi_uuid=vdi_uuid)]
+ with_vdi_attached_here(session, vdi_ref, False,
+ lambda dev:
+ _stream_disk(dev, image_type,
+ virtual_size, image_file))
+ if image_type in (ImageType.KERNEL, ImageType.RAMDISK):
+ # We need to invoke a plugin for copying the
+ # content of the VDI into the proper path.
+ LOG.debug(_("Copying VDI %s to /boot/guest on dom0"), vdi_ref)
+ fn = "copy_kernel_vdi"
+ args = {}
+ args['vdi-ref'] = vdi_ref
+ # Let the plugin copy the correct number of bytes.
+ args['image-size'] = str(vdi_size)
+ task = session.async_call_plugin('glance', fn, args)
+ filename = session.wait_for_task(task, instance_id)
+ # Remove the VDI as it is not needed anymore.
+ session.get_xenapi().VDI.destroy(vdi_ref)
+ LOG.debug(_("Kernel/Ramdisk VDI %s destroyed"), vdi_ref)
+ return [dict(vdi_type=ImageType.to_string(image_type),
+ vdi_uuid=None,
+ file=filename)]
+ else:
+ return [dict(vdi_type=ImageType.to_string(image_type),
+ vdi_uuid=vdi_uuid,
+ file=None)]
+ except (cls.XenAPI.Failure, IOError, OSError) as e:
+ # We look for XenAPI and OS failures.
+ LOG.exception(_("instance %s: Failed to fetch glance image"),
+ instance_id, exc_info=sys.exc_info())
+ e.args = e.args + ([dict(vdi_type=ImageType.
+ to_string(image_type),
+ vdi_uuid=vdi_uuid,
+ file=filename)],)
+ raise e
@classmethod
def determine_disk_image_type(cls, instance):
@@ -506,7 +545,8 @@ class VMHelper(HelperBase):
whether a kernel_id is specified.
"""
def log_disk_format(image_type):
- pretty_format = {ImageType.KERNEL_RAMDISK: 'KERNEL_RAMDISK',
+ pretty_format = {ImageType.KERNEL: 'KERNEL',
+ ImageType.RAMDISK: 'RAMDISK',
ImageType.DISK: 'DISK',
ImageType.DISK_RAW: 'DISK_RAW',
ImageType.DISK_VHD: 'DISK_VHD'}
@@ -519,8 +559,8 @@ class VMHelper(HelperBase):
def determine_from_glance():
glance_disk_format2nova_type = {
'ami': ImageType.DISK,
- 'aki': ImageType.KERNEL_RAMDISK,
- 'ari': ImageType.KERNEL_RAMDISK,
+ 'aki': ImageType.KERNEL,
+ 'ari': ImageType.RAMDISK,
'raw': ImageType.DISK_RAW,
'vhd': ImageType.DISK_VHD}
image_ref = instance.image_ref
@@ -553,7 +593,7 @@ class VMHelper(HelperBase):
image_type):
"""Fetch image from glance based on image type.
- Returns: A single filename if image_type is KERNEL_RAMDISK
+ Returns: A single filename if image_type is KERNEL or RAMDISK
A list of dictionaries that describe VDIs, otherwise
"""
if image_type == ImageType.DISK_VHD:
@@ -568,13 +608,13 @@ class VMHelper(HelperBase):
secret, image_type):
"""Fetch an image from objectstore.
- Returns: A single filename if image_type is KERNEL_RAMDISK
+ Returns: A single filename if image_type is KERNEL or RAMDISK
A list of dictionaries that describe VDIs, otherwise
"""
url = "http://%s:%s/_images/%s/image" % (FLAGS.s3_host, FLAGS.s3_port,
image)
LOG.debug(_("Asking xapi to fetch %(url)s as %(access)s") % locals())
- if image_type == ImageType.KERNEL_RAMDISK:
+ if image_type in (ImageType.KERNEL, ImageType.RAMDISK):
fn = 'get_kernel'
else:
fn = 'get_vdi'
@@ -584,15 +624,20 @@ class VMHelper(HelperBase):
args['password'] = secret
args['add_partition'] = 'false'
args['raw'] = 'false'
- if image_type != ImageType.KERNEL_RAMDISK:
+ if not image_type in (ImageType.KERNEL, ImageType.RAMDISK):
args['add_partition'] = 'true'
if image_type == ImageType.DISK_RAW:
args['raw'] = 'true'
task = session.async_call_plugin('objectstore', fn, args)
- uuid_or_fn = session.wait_for_task(task, instance_id)
- if image_type != ImageType.KERNEL_RAMDISK:
- return [dict(vdi_type='os', vdi_uuid=uuid_or_fn)]
- return uuid_or_fn
+ vdi_uuid = None
+ filename = None
+ if image_type in (ImageType.KERNEL, ImageType.RAMDISK):
+ filename = session.wait_for_task(task, instance_id)
+ else:
+ vdi_uuid = session.wait_for_task(task, instance_id)
+ return [dict(vdi_type=ImageType.to_string(image_type),
+ vdi_uuid=vdi_uuid,
+ file=filename)]
@classmethod
def determine_is_pv(cls, session, instance_id, vdi_ref, disk_image_type,
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 2f4286184..0473abb97 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -24,7 +24,10 @@ import json
import M2Crypto
import os
import pickle
+import random
import subprocess
+import sys
+import time
import uuid
from nova import context
@@ -44,7 +47,14 @@ from nova.virt.xenapi.vm_utils import ImageType
XenAPI = None
LOG = logging.getLogger("nova.virt.xenapi.vmops")
+
FLAGS = flags.FLAGS
+flags.DEFINE_integer('windows_version_timeout', 300,
+ 'number of seconds to wait for windows agent to be '
+ 'fully operational')
+flags.DEFINE_string('xenapi_vif_driver',
+ 'nova.virt.xenapi.vif.XenAPIBridgeDriver',
+ 'The XenAPI VIF driver using XenServer Network APIs.')
def cmp_version(a, b):
@@ -71,6 +81,7 @@ class VMOps(object):
self._session = session
self.poll_rescue_last_ran = None
VMHelper.XenAPI = self.XenAPI
+ self.vif_driver = utils.import_object(FLAGS.xenapi_vif_driver)
def list_instances(self):
"""List VM instances."""
@@ -103,11 +114,12 @@ class VMOps(object):
vm_ref = VMHelper.lookup(self._session, instance.name)
self._start(instance, vm_ref)
- def finish_resize(self, instance, disk_info):
+ def finish_resize(self, instance, disk_info, network_info):
vdi_uuid = self.link_disks(instance, disk_info['base_copy'],
disk_info['cow'])
vm_ref = self._create_vm(instance,
- [dict(vdi_type='os', vdi_uuid=vdi_uuid)])
+ [dict(vdi_type='os', vdi_uuid=vdi_uuid)],
+ network_info)
self.resize_instance(instance, vdi_uuid)
self._spawn(instance, vm_ref)
@@ -130,16 +142,25 @@ class VMOps(object):
disk_image_type)
return vdis
- def spawn(self, instance, network_info=None):
- vdis = self._create_disks(instance)
- vm_ref = self._create_vm(instance, vdis, network_info)
- self._spawn(instance, vm_ref)
+ def spawn(self, instance, network_info):
+ vdis = None
+ try:
+ vdis = self._create_disks(instance)
+ vm_ref = self._create_vm(instance, vdis, network_info)
+ self._spawn(instance, vm_ref)
+ except (self.XenAPI.Failure, OSError, IOError) as spawn_error:
+ LOG.exception(_("instance %s: Failed to spawn"),
+ instance.id, exc_info=sys.exc_info())
+ LOG.debug(_('Instance %s failed to spawn - performing clean-up'),
+ instance.id)
+ self._handle_spawn_error(vdis, spawn_error)
+ raise spawn_error
def spawn_rescue(self, instance):
"""Spawn a rescue instance."""
self.spawn(instance)
- def _create_vm(self, instance, vdis, network_info=None):
+ def _create_vm(self, instance, vdis, network_info):
"""Create VM instance."""
instance_name = instance.name
vm_ref = VMHelper.lookup(self._session, instance_name)
@@ -159,42 +180,64 @@ class VMOps(object):
project = AuthManager().get_project(instance.project_id)
disk_image_type = VMHelper.determine_disk_image_type(instance)
-
kernel = None
- if instance.kernel_id:
- kernel = VMHelper.fetch_image(self._session, instance.id,
- instance.kernel_id, user, project,
- ImageType.KERNEL_RAMDISK)
-
ramdisk = None
- if instance.ramdisk_id:
- ramdisk = VMHelper.fetch_image(self._session, instance.id,
- instance.ramdisk_id, user, project,
- ImageType.KERNEL_RAMDISK)
-
- # Create the VM ref and attach the first disk
- first_vdi_ref = self._session.call_xenapi('VDI.get_by_uuid',
- vdis[0]['vdi_uuid'])
-
- vm_mode = instance.vm_mode and instance.vm_mode.lower()
- if vm_mode == 'pv':
- use_pv_kernel = True
- elif vm_mode in ('hv', 'hvm'):
- use_pv_kernel = False
- vm_mode = 'hvm' # Normalize
- else:
- use_pv_kernel = VMHelper.determine_is_pv(self._session,
- instance.id, first_vdi_ref, disk_image_type,
- instance.os_type)
- vm_mode = use_pv_kernel and 'pv' or 'hvm'
-
- if instance.vm_mode != vm_mode:
- # Update database with normalized (or determined) value
- db.instance_update(context.get_admin_context(),
- instance['id'], {'vm_mode': vm_mode})
-
- vm_ref = VMHelper.create_vm(self._session, instance,
- kernel, ramdisk, use_pv_kernel)
+ try:
+ if instance.kernel_id:
+ kernel = VMHelper.fetch_image(self._session, instance.id,
+ instance.kernel_id, user, project,
+ ImageType.KERNEL)[0]
+ if instance.ramdisk_id:
+ ramdisk = VMHelper.fetch_image(self._session, instance.id,
+ instance.ramdisk_id, user, project,
+ 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'])
+
+ vm_mode = instance.vm_mode and instance.vm_mode.lower()
+ if vm_mode == 'pv':
+ use_pv_kernel = True
+ elif vm_mode in ('hv', 'hvm'):
+ use_pv_kernel = False
+ vm_mode = 'hvm' # Normalize
+ else:
+ use_pv_kernel = VMHelper.determine_is_pv(self._session,
+ instance.id, first_vdi_ref, disk_image_type,
+ instance.os_type)
+ vm_mode = use_pv_kernel and 'pv' or 'hvm'
+
+ if instance.vm_mode != vm_mode:
+ # Update database with normalized (or determined) value
+ db.instance_update(context.get_admin_context(),
+ instance['id'], {'vm_mode': vm_mode})
+ vm_ref = VMHelper.create_vm(self._session, instance,
+ kernel and kernel.get('file', None) or None,
+ ramdisk and ramdisk.get('file', None) or None,
+ use_pv_kernel)
+ except (self.XenAPI.Failure, OSError, IOError) as vm_create_error:
+ # Collect VDI/file resources to clean up;
+ # These resources will be removed by _handle_spawn_error.
+ LOG.exception(_("instance %s: Failed to spawn - " +
+ "Unable to create VM"),
+ instance.id, exc_info=sys.exc_info())
+ last_arg = None
+ resources = []
+
+ if vm_create_error.args:
+ last_arg = vm_create_error.args[-1]
+ if isinstance(last_arg, list):
+ resources = last_arg
+ else:
+ vm_create_error.args = vm_create_error.args + (resources,)
+
+ if kernel:
+ resources.append(kernel)
+ if ramdisk:
+ resources.append(ramdisk)
+
+ raise vm_create_error
+
VMHelper.create_vbd(session=self._session, vm_ref=vm_ref,
vdi_ref=first_vdi_ref, userdevice=0, bootable=True)
@@ -211,17 +254,12 @@ class VMOps(object):
bootable=False)
userdevice += 1
- # TODO(tr3buchet) - check to make sure we have network info, otherwise
- # create it now. This goes away once nova-multi-nic hits.
- if network_info is None:
- network_info = self._get_network_info(instance)
-
# Alter the image before VM start for, e.g. network injection
if FLAGS.xenapi_inject_image:
VMHelper.preconfigure_instance(self._session, instance,
first_vdi_ref, network_info)
- self.create_vifs(vm_ref, network_info)
+ self.create_vifs(vm_ref, instance, network_info)
self.inject_network_info(instance, network_info, vm_ref)
return vm_ref
@@ -247,7 +285,15 @@ class VMOps(object):
'architecture': instance.architecture})
def _check_agent_version():
- version = self.get_agent_version(instance)
+ if instance.os_type == 'windows':
+ # Windows will generally perform a setup process on first boot
+ # that can take a couple of minutes and then reboot. So we
+ # need to be more patient than normal as well as watch for
+ # domid changes
+ version = self.get_agent_version(instance,
+ timeout=FLAGS.windows_version_timeout)
+ else:
+ version = self.get_agent_version(instance)
if not version:
LOG.info(_('No agent version returned by instance'))
return
@@ -298,6 +344,7 @@ class VMOps(object):
_check_agent_version()
_inject_files()
_set_admin_password()
+ self.reset_network(instance, vm_ref)
return True
except Exception, exc:
LOG.warn(exc)
@@ -307,11 +354,49 @@ class VMOps(object):
timer.f = _wait_for_boot
- # call to reset network to configure network from xenstore
- self.reset_network(instance, vm_ref)
-
return timer.start(interval=0.5, now=True)
+ def _handle_spawn_error(self, vdis, spawn_error):
+ # Extract resource list from spawn_error.
+ resources = []
+ if spawn_error.args:
+ last_arg = spawn_error.args[-1]
+ resources = last_arg
+ if vdis:
+ for vdi in vdis:
+ resources.append(dict(vdi_type=vdi['vdi_type'],
+ vdi_uuid=vdi['vdi_uuid'],
+ file=None))
+
+ LOG.debug(_("Resources to remove:%s"), resources)
+ kernel_file = None
+ ramdisk_file = None
+
+ for item in resources:
+ vdi_type = item['vdi_type']
+ vdi_to_remove = item['vdi_uuid']
+ if vdi_to_remove:
+ try:
+ vdi_ref = self._session.call_xenapi('VDI.get_by_uuid',
+ vdi_to_remove)
+ LOG.debug(_('Removing VDI %(vdi_ref)s' +
+ '(uuid:%(vdi_to_remove)s)'), locals())
+ VMHelper.destroy_vdi(self._session, vdi_ref)
+ except self.XenAPI.Failure:
+ # Vdi has already been deleted
+ LOG.debug(_("Skipping VDI destroy for %s"), vdi_to_remove)
+ if item['file']:
+ # There is also a file to remove.
+ if vdi_type == ImageType.KERNEL_STR:
+ kernel_file = item['file']
+ elif vdi_type == ImageType.RAMDISK_STR:
+ ramdisk_file = item['file']
+
+ if kernel_file or ramdisk_file:
+ LOG.debug(_("Removing kernel/ramdisk files from dom0"))
+ self._destroy_kernel_ramdisk_plugin_call(kernel_file,
+ ramdisk_file)
+
def _get_vm_opaque_ref(self, instance_or_vm):
"""
Refactored out the common code of many methods that receive either
@@ -386,7 +471,7 @@ class VMOps(object):
self._session, instance, template_vdi_uuids, image_id)
finally:
if template_vm_ref:
- self._destroy(instance, template_vm_ref,
+ self._destroy(instance, template_vm_ref, None,
shutdown=False, destroy_kernel_ramdisk=False)
logging.debug(_("Finished snapshot and upload for VM %s"), instance)
@@ -502,18 +587,43 @@ class VMOps(object):
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):
+ def get_agent_version(self, instance, timeout=None):
"""Get the version of the agent running on the VM instance."""
- # Send the encrypted password
- transaction_id = str(uuid.uuid4())
- args = {'id': transaction_id}
- resp = self._make_agent_call('version', instance, '', args)
- if resp is None:
- # No response from the agent
- return
- resp_dict = json.loads(resp)
- return resp_dict['message']
+ def _call():
+ # Send the encrypted password
+ transaction_id = str(uuid.uuid4())
+ args = {'id': transaction_id}
+ resp = self._make_agent_call('version', instance, '', args)
+ if resp is None:
+ # No response from the agent
+ return
+ resp_dict = json.loads(resp)
+ # Some old versions of the Windows agent have a trailing \\r\\n
+ # (ie CRLF escaped) for some reason. Strip that off.
+ return resp_dict['message'].replace('\\r\\n', '')
+
+ if timeout:
+ vm_ref = self._get_vm_opaque_ref(instance)
+ vm_rec = self._session.get_xenapi().VM.get_record(vm_ref)
+
+ domid = vm_rec['domid']
+
+ expiration = time.time() + timeout
+ while time.time() < expiration:
+ ret = _call()
+ if ret:
+ return ret
+
+ vm_rec = self._session.get_xenapi().VM.get_record(vm_ref)
+ if vm_rec['domid'] != domid:
+ LOG.info(_('domid changed from %(olddomid)s to '
+ '%(newdomid)s') % {
+ 'olddomid': domid,
+ 'newdomid': vm_rec['domid']})
+ domid = vm_rec['domid']
+ else:
+ return _call()
def agent_update(self, instance, url, md5sum):
"""Update agent on the VM instance."""
@@ -556,9 +666,13 @@ class VMOps(object):
# There was some sort of error; the message will contain
# a description of the error.
raise RuntimeError(resp_dict['message'])
- agent_pub = int(resp_dict['message'])
+ # Some old versions of the Windows agent have a trailing \\r\\n
+ # (ie CRLF escaped) for some reason. Strip that off.
+ agent_pub = int(resp_dict['message'].replace('\\r\\n', ''))
dh.compute_shared(agent_pub)
- enc_pass = dh.encrypt(new_pass)
+ # Some old versions of Linux and Windows agent expect trailing \n
+ # on password to work correctly.
+ enc_pass = dh.encrypt(new_pass + '\n')
# Send the encrypted password
password_transaction_id = str(uuid.uuid4())
password_args = {'id': password_transaction_id, 'enc_pass': enc_pass}
@@ -666,6 +780,16 @@ class VMOps(object):
VMHelper.unplug_vbd(self._session, vbd_ref)
VMHelper.destroy_vbd(self._session, vbd_ref)
+ def _destroy_kernel_ramdisk_plugin_call(self, kernel, ramdisk):
+ args = {}
+ if kernel:
+ args['kernel-file'] = kernel
+ if ramdisk:
+ args['ramdisk-file'] = ramdisk
+ task = self._session.async_call_plugin(
+ 'glance', 'remove_kernel_ramdisk', args)
+ self._session.wait_for_task(task)
+
def _destroy_kernel_ramdisk(self, instance, vm_ref):
"""Three situations can occur:
@@ -695,13 +819,7 @@ class VMOps(object):
(kernel, ramdisk) = VMHelper.lookup_kernel_ramdisk(self._session,
vm_ref)
- LOG.debug(_("Removing kernel/ramdisk files"))
-
- args = {'kernel-file': kernel, 'ramdisk-file': ramdisk}
- task = self._session.async_call_plugin(
- 'glance', 'remove_kernel_ramdisk', args)
- self._session.wait_for_task(task, instance.id)
-
+ self._destroy_kernel_ramdisk_plugin_call(kernel, ramdisk)
LOG.debug(_("kernel/ramdisk files removed"))
def _destroy_vm(self, instance, vm_ref):
@@ -723,7 +841,7 @@ class VMOps(object):
self._session.call_xenapi("Async.VM.destroy", rescue_vm_ref)
- def destroy(self, instance):
+ def destroy(self, instance, network_info):
"""Destroy VM instance.
This is the method exposed by xenapi_conn.destroy(). The rest of the
@@ -733,9 +851,9 @@ class VMOps(object):
instance_id = instance.id
LOG.info(_("Destroying VM for Instance %(instance_id)s") % locals())
vm_ref = VMHelper.lookup(self._session, instance.name)
- return self._destroy(instance, vm_ref, shutdown=True)
+ return self._destroy(instance, vm_ref, network_info, shutdown=True)
- def _destroy(self, instance, vm_ref, shutdown=True,
+ def _destroy(self, instance, vm_ref, network_info, shutdown=True,
destroy_kernel_ramdisk=True):
"""Destroys VM instance by performing:
@@ -757,6 +875,10 @@ class VMOps(object):
self._destroy_kernel_ramdisk(instance, vm_ref)
self._destroy_vm(instance, vm_ref)
+ if network_info:
+ for (network, mapping) in network_info:
+ self.vif_driver.unplug(instance, network, mapping)
+
def _wait_with_callback(self, instance_id, task, callback):
ret = None
try:
@@ -900,76 +1022,44 @@ class VMOps(object):
# TODO: implement this!
return 'http://fakeajaxconsole/fake_url'
- # TODO(tr3buchet) - remove this function after nova multi-nic
- def _get_network_info(self, instance):
- """Creates network info list for instance."""
- admin_context = context.get_admin_context()
- ips = db.fixed_ip_get_all_by_instance(admin_context,
- instance['id'])
- networks = db.network_get_all_by_instance(admin_context,
- instance['id'])
-
- inst_type = db.instance_type_get_by_id(admin_context,
- instance['instance_type_id'])
-
- network_info = []
- for network in networks:
- network_ips = [ip for ip in ips if ip.network_id == network.id]
-
- def ip_dict(ip):
- return {
- "ip": ip.address,
- "netmask": network["netmask"],
- "enabled": "1"}
-
- def ip6_dict():
- return {
- "ip": ipv6.to_global(network['cidr_v6'],
- instance['mac_address'],
- instance['project_id']),
- "netmask": network['netmask_v6'],
- "enabled": "1"}
-
- info = {
- 'label': network['label'],
- 'gateway': network['gateway'],
- 'broadcast': network['broadcast'],
- 'mac': instance.mac_address,
- 'rxtx_cap': inst_type['rxtx_cap'],
- 'dns': [network['dns']],
- 'ips': [ip_dict(ip) for ip in network_ips]}
- if network['cidr_v6']:
- info['ip6s'] = [ip6_dict()]
- if network['gateway_v6']:
- info['gateway6'] = network['gateway_v6']
- network_info.append((network, info))
- return network_info
-
- #TODO{tr3buchet) remove this shim with nova-multi-nic
- def inject_network_info(self, instance, network_info=None, vm_ref=None):
- """
- shim in place which makes inject_network_info work without being
- passed network_info.
- shim goes away after nova-multi-nic
+ def set_host_enabled(self, host, enabled):
+ """Sets the specified host's ability to accept new instances."""
+ args = {"enabled": json.dumps(enabled)}
+ json_resp = self._call_xenhost("set_host_enabled", args)
+ resp = json.loads(json_resp)
+ return resp["status"]
+
+ def _call_xenhost(self, method, arg_dict):
+ """There will be several methods that will need this general
+ handling for interacting with the xenhost plugin, so this abstracts
+ out that behavior.
"""
- if not network_info:
- network_info = self._get_network_info(instance)
- self._inject_network_info(instance, network_info, vm_ref)
+ # Create a task ID as something that won't match any instance ID
+ task_id = random.randint(-80000, -70000)
+ try:
+ task = self._session.async_call_plugin("xenhost", method,
+ args=arg_dict)
+ #args={"params": arg_dict})
+ ret = self._session.wait_for_task(task, task_id)
+ except self.XenAPI.Failure as e:
+ ret = None
+ LOG.error(_("The call to %(method)s returned an error: %(e)s.")
+ % locals())
+ return ret
- def _inject_network_info(self, instance, network_info, vm_ref=None):
+ def inject_network_info(self, instance, network_info, vm_ref=None):
"""
Generate the network info and make calls to place it into the
xenstore and the xenstore param list.
vm_ref can be passed in because it will sometimes be different than
what VMHelper.lookup(session, instance.name) will find (ex: rescue)
"""
- logging.debug(_("injecting network info to xs for vm: |%s|"), vm_ref)
-
if vm_ref:
# this function raises if vm_ref is not a vm_opaque_ref
self._session.get_xenapi().VM.get_record(vm_ref)
else:
vm_ref = VMHelper.lookup(self._session, instance.name)
+ logging.debug(_("injecting network info to xs for vm: |%s|"), vm_ref)
for (network, info) in network_info:
location = 'vm-data/networking/%s' % info['mac'].replace(':', '')
@@ -984,22 +1074,28 @@ class VMOps(object):
# catch KeyError for domid if instance isn't running
pass
- def create_vifs(self, vm_ref, network_info):
+ def create_vifs(self, vm_ref, instance, network_info):
"""Creates vifs for an instance."""
+
logging.debug(_("creating vif(s) for vm: |%s|"), vm_ref)
# this function raises if vm_ref is not a vm_opaque_ref
self._session.get_xenapi().VM.get_record(vm_ref)
for device, (network, info) in enumerate(network_info):
- mac_address = info['mac']
- bridge = network['bridge']
- rxtx_cap = info.pop('rxtx_cap')
- network_ref = \
- NetworkHelper.find_network_with_bridge(self._session,
- bridge)
- VMHelper.create_vif(self._session, vm_ref, network_ref,
- mac_address, device, rxtx_cap)
+ vif_rec = self.vif_driver.plug(self._session,
+ vm_ref, instance, device, network, info)
+ network_ref = vif_rec['network']
+ LOG.debug(_('Creating VIF for VM %(vm_ref)s,' \
+ ' network %(network_ref)s.') % locals())
+ vif_ref = self._session.call_xenapi('VIF.create', vif_rec)
+ LOG.debug(_('Created VIF %(vif_ref)s for VM %(vm_ref)s,'
+ ' network %(network_ref)s.') % locals())
+
+ def plug_vifs(instance, network_info):
+ """Set up VIF networking on the host."""
+ for (network, mapping) in network_info:
+ self.vif_driver.plug(self._session, instance, network, mapping)
def reset_network(self, instance, vm_ref=None):
"""Creates uuid arg to pass to make_agent_call and calls it."""
diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py
index 5fcec1715..7c355a55b 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -194,23 +194,23 @@ class XenAPIConnection(driver.ComputeDriver):
def list_instances_detail(self):
return self._vmops.list_instances_detail()
- def spawn(self, instance, network_info=None, block_device_mapping=None):
+ def spawn(self, instance, network_info, block_device_mapping=None):
"""Create VM instance"""
- self._vmops.spawn(instance)
+ self._vmops.spawn(instance, network_info)
def revert_resize(self, instance):
"""Reverts a resize, powering back on the instance"""
self._vmops.revert_resize(instance)
- def finish_resize(self, instance, disk_info):
+ def finish_resize(self, instance, disk_info, network_info):
"""Completes a resize, turning on the migrated instance"""
- self._vmops.finish_resize(instance, disk_info)
+ self._vmops.finish_resize(instance, disk_info, network_info)
def snapshot(self, instance, image_id):
""" Create snapshot from a running VM instance """
self._vmops.snapshot(instance, image_id)
- def reboot(self, instance):
+ def reboot(self, instance, network_info):
"""Reboot VM instance"""
self._vmops.reboot(instance)
@@ -224,9 +224,9 @@ class XenAPIConnection(driver.ComputeDriver):
"""
self._vmops.inject_file(instance, b64_path, b64_contents)
- def destroy(self, instance):
+ def destroy(self, instance, network_info):
"""Destroy VM instance"""
- self._vmops.destroy(instance)
+ self._vmops.destroy(instance, network_info)
def pause(self, instance, callback):
"""Pause VM instance"""
@@ -249,11 +249,11 @@ class XenAPIConnection(driver.ComputeDriver):
"""resume the specified instance"""
self._vmops.resume(instance, callback)
- def rescue(self, instance, callback):
+ def rescue(self, instance, callback, network_info):
"""Rescue the specified instance"""
self._vmops.rescue(instance, callback)
- def unrescue(self, instance, callback):
+ def unrescue(self, instance, callback, network_info):
"""Unrescue the specified instance"""
self._vmops.unrescue(instance, callback)
@@ -265,9 +265,12 @@ class XenAPIConnection(driver.ComputeDriver):
"""reset networking for specified instance"""
self._vmops.reset_network(instance)
- def inject_network_info(self, instance):
+ def inject_network_info(self, instance, network_info):
"""inject network info for specified instance"""
- self._vmops.inject_network_info(instance)
+ self._vmops.inject_network_info(instance, network_info)
+
+ def plug_vifs(self, instance_ref, network_info):
+ self._vmops.plug_vifs(instance_ref, network_info)
def get_info(self, instance_id):
"""Return data about VM instance"""
@@ -322,7 +325,7 @@ class XenAPIConnection(driver.ComputeDriver):
"""This method is supported only by libvirt."""
return
- def unfilter_instance(self, instance_ref):
+ def unfilter_instance(self, instance_ref, network_info):
"""This method is supported only by libvirt."""
raise NotImplementedError('This method is supported only by libvirt.')
@@ -336,6 +339,10 @@ class XenAPIConnection(driver.ComputeDriver):
True, run the update first."""
return self.HostState.get_host_stats(refresh=refresh)
+ def set_host_enabled(self, host, enabled):
+ """Sets the specified host's ability to accept new instances."""
+ return self._vmops.set_host_enabled(host, enabled)
+
class XenAPISession(object):
"""The session to invoke XenAPI SDK calls"""