summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorAndy Smith <code@term.ie>2011-01-14 17:48:48 -0800
committerAndy Smith <code@term.ie>2011-01-14 17:48:48 -0800
commit073336d206e124f7bebbe8a239193a8727fef7ed (patch)
tree45d2ef9bab084f5e0a63192a73936d321f7e7bc1 /nova
parent731126b299da757588656fa72b291ca4da96b5fe (diff)
parent34ceed1ce114ab01eca06eced00a204ae71dc3db (diff)
downloadnova-073336d206e124f7bebbe8a239193a8727fef7ed.tar.gz
nova-073336d206e124f7bebbe8a239193a8727fef7ed.tar.xz
nova-073336d206e124f7bebbe8a239193a8727fef7ed.zip
merge from upstream
Diffstat (limited to 'nova')
-rw-r--r--nova/api/ec2/cloud.py63
-rw-r--r--nova/api/openstack/servers.py9
-rw-r--r--nova/auth/manager.py2
-rw-r--r--nova/compute/api.py144
-rw-r--r--nova/compute/disk.py205
-rw-r--r--nova/compute/manager.py33
-rw-r--r--nova/db/api.py16
-rw-r--r--nova/db/sqlalchemy/api.py27
-rw-r--r--nova/db/sqlalchemy/models.py8
-rw-r--r--nova/exception.py4
-rw-r--r--nova/network/linux_net.py83
-rw-r--r--nova/network/manager.py34
-rw-r--r--nova/test.py99
-rw-r--r--nova/tests/test_api.py66
-rw-r--r--nova/tests/test_cloud.py15
-rw-r--r--nova/tests/test_compute.py7
-rw-r--r--nova/tests/test_log.py8
-rw-r--r--nova/tests/test_network.py22
-rw-r--r--nova/tests/test_twistd.py2
-rw-r--r--nova/tests/test_xenapi.py27
-rw-r--r--nova/utils.py36
-rw-r--r--nova/virt/disk.py186
-rw-r--r--nova/virt/fake.py21
-rw-r--r--nova/virt/libvirt.xml.template17
-rw-r--r--nova/virt/libvirt_conn.py377
-rw-r--r--nova/virt/xenapi/vmops.py199
-rw-r--r--nova/virt/xenapi_conn.py7
-rw-r--r--nova/volume/driver.py49
28 files changed, 1179 insertions, 587 deletions
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 4b971c3d8..d1aec23be 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -26,16 +26,17 @@ import base64
import datetime
import IPy
import os
+import urllib
from nova import compute
from nova import context
+
from nova import crypto
from nova import db
from nova import exception
from nova import flags
from nova import log as logging
from nova import network
-from nova import rpc
from nova import utils
from nova import volume
from nova.compute import instance_types
@@ -73,17 +74,13 @@ def _gen_key(context, user_id, key_name):
def ec2_id_to_id(ec2_id):
- """Convert an ec2 ID (i-[base 36 number]) to an instance id (int)"""
- return int(ec2_id[2:], 36)
+ """Convert an ec2 ID (i-[base 16 number]) to an instance id (int)"""
+ return int(ec2_id.split('-')[-1], 16)
-def id_to_ec2_id(instance_id):
- """Convert an instance ID (int) to an ec2 ID (i-[base 36 number])"""
- digits = []
- while instance_id != 0:
- instance_id, remainder = divmod(instance_id, 36)
- digits.append('0123456789abcdefghijklmnopqrstuvwxyz'[remainder])
- return "i-%s" % ''.join(reversed(digits))
+def id_to_ec2_id(instance_id, template='i-%08x'):
+ """Convert an instance ID (int) to an ec2 ID (i-[base 16 number])"""
+ return template % instance_id
class CloudController(object):
@@ -135,15 +132,6 @@ class CloudController(object):
result[key] = [line]
return result
- def _trigger_refresh_security_group(self, context, security_group):
- nodes = set([instance['host'] for instance in security_group.instances
- if instance['host'] is not None])
- for node in nodes:
- rpc.cast(context,
- '%s.%s' % (FLAGS.compute_topic, node),
- {"method": "refresh_security_group",
- "args": {"security_group_id": security_group.id}})
-
def _get_availability_zone_by_host(self, context, host):
services = db.service_get_all_by_host(context, host)
if len(services) > 0:
@@ -381,6 +369,7 @@ class CloudController(object):
values['group_id'] = source_security_group['id']
elif cidr_ip:
# If this fails, it throws an exception. This is what we want.
+ cidr_ip = urllib.unquote(cidr_ip).decode()
IPy.IP(cidr_ip)
values['cidr'] = cidr_ip
else:
@@ -526,13 +515,7 @@ class CloudController(object):
# instance_id is passed in as a list of instances
ec2_id = instance_id[0]
instance_id = ec2_id_to_id(ec2_id)
- instance_ref = self.compute_api.get(context, instance_id=instance_id)
- output = rpc.call(context,
- '%s.%s' % (FLAGS.compute_topic,
- instance_ref['host']),
- {"method": "get_console_output",
- "args": {"instance_id": instance_ref['id']}})
-
+ output = self.compute_api.get_console_output(context, instance_id=instance_id)
now = datetime.datetime.utcnow()
return {"InstanceId": ec2_id,
"Timestamp": now,
@@ -544,6 +527,8 @@ class CloudController(object):
return self.compute_api.get_ajax_console(context, internal_id)
def describe_volumes(self, context, volume_id=None, **kwargs):
+ if volume_id:
+ volume_id = [ec2_id_to_id(x) for x in volume_id]
volumes = self.volume_api.get_all(context)
# NOTE(vish): volume_id is an optional list of volume ids to filter by.
volumes = [self._format_volume(context, v) for v in volumes
@@ -559,7 +544,7 @@ class CloudController(object):
instance_data = '%s[%s]' % (instance_ec2_id,
volume['instance']['host'])
v = {}
- v['volumeId'] = volume['id']
+ v['volumeId'] = id_to_ec2_id(volume['id'], 'vol-%08x')
v['status'] = volume['status']
v['size'] = volume['size']
v['availabilityZone'] = volume['availability_zone']
@@ -577,7 +562,8 @@ class CloudController(object):
'device': volume['mountpoint'],
'instanceId': instance_ec2_id,
'status': 'attached',
- 'volume_id': volume['ec2_id']}]
+ 'volumeId': id_to_ec2_id(volume['id'],
+ 'vol-%08x')}]
else:
v['attachmentSet'] = [{}]
@@ -596,10 +582,12 @@ class CloudController(object):
return {'volumeSet': [self._format_volume(context, dict(volume))]}
def delete_volume(self, context, volume_id, **kwargs):
+ volume_id = ec2_id_to_id(volume_id)
self.volume_api.delete(context, volume_id=volume_id)
return True
def update_volume(self, context, volume_id, **kwargs):
+ volume_id = ec2_id_to_id(volume_id)
updatable_fields = ['display_name', 'display_description']
changes = {}
for field in updatable_fields:
@@ -610,6 +598,8 @@ class CloudController(object):
return True
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
+ volume_id = ec2_id_to_id(volume_id)
+ instance_id = ec2_id_to_id(instance_id)
LOG.audit(_("Attach volume %s to instance %s at %s"), volume_id,
instance_id, device, context=context)
self.compute_api.attach_volume(context,
@@ -619,12 +609,13 @@ class CloudController(object):
volume = self.volume_api.get(context, volume_id)
return {'attachTime': volume['attach_time'],
'device': volume['mountpoint'],
- 'instanceId': instance_id,
+ 'instanceId': id_to_ec2_id(instance_id),
'requestId': context.request_id,
'status': volume['attach_status'],
- 'volumeId': volume_id}
+ 'volumeId': id_to_ec2_id(volume_id, 'vol-%08x')}
def detach_volume(self, context, volume_id, **kwargs):
+ volume_id = ec2_id_to_id(volume_id)
LOG.audit(_("Detach volume %s"), volume_id, context=context)
volume = self.volume_api.get(context, volume_id)
instance = self.compute_api.detach_volume(context, volume_id=volume_id)
@@ -633,7 +624,7 @@ class CloudController(object):
'instanceId': id_to_ec2_id(instance['id']),
'requestId': context.request_id,
'status': volume['attach_status'],
- 'volumeId': volume_id}
+ 'volumeId': id_to_ec2_id(volume_id, 'vol-%08x')}
def _convert_to_set(self, lst, label):
if lst == None or lst == []:
@@ -645,6 +636,10 @@ class CloudController(object):
def describe_instances(self, context, **kwargs):
return self._format_describe_instances(context, **kwargs)
+ def describe_instances_v6(self, context, **kwargs):
+ kwargs['use_v6'] = True
+ return self._format_describe_instances(context, **kwargs)
+
def _format_describe_instances(self, context, **kwargs):
return {'reservationSet': self._format_instances(context, **kwargs)}
@@ -684,10 +679,16 @@ class CloudController(object):
if instance['fixed_ip']['floating_ips']:
fixed = instance['fixed_ip']
floating_addr = fixed['floating_ips'][0]['address']
+ if instance['fixed_ip']['network'] and 'use_v6' in kwargs:
+ i['dnsNameV6'] = utils.to_global_ipv6(
+ instance['fixed_ip']['network']['cidr_v6'],
+ instance['mac_address'])
+
i['privateDnsName'] = fixed_addr
i['publicDnsName'] = floating_addr
i['dnsName'] = i['publicDnsName'] or i['privateDnsName']
i['keyName'] = instance['key_name']
+
if context.user.is_admin():
i['keyName'] = '%s (%s, %s)' % (i['keyName'],
instance['project_id'],
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 29af82533..8cbcebed2 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -165,15 +165,18 @@ class Controller(wsgi.Controller):
if not inst_dict:
return faults.Fault(exc.HTTPUnprocessableEntity())
+ ctxt = req.environ['nova.context']
update_dict = {}
if 'adminPass' in inst_dict['server']:
update_dict['admin_pass'] = inst_dict['server']['adminPass']
+ try:
+ self.compute_api.set_admin_password(ctxt, id)
+ except exception.TimeoutException, e:
+ return exc.HTTPRequestTimeout()
if 'name' in inst_dict['server']:
update_dict['display_name'] = inst_dict['server']['name']
-
try:
- self.compute_api.update(req.environ['nova.context'], id,
- **update_dict)
+ self.compute_api.update(ctxt, id, **update_dict)
except exception.NotFound:
return faults.Fault(exc.HTTPNotFound())
return exc.HTTPNoContent()
diff --git a/nova/auth/manager.py b/nova/auth/manager.py
index 6fb9b522f..de18a3838 100644
--- a/nova/auth/manager.py
+++ b/nova/auth/manager.py
@@ -721,7 +721,7 @@ class AuthManager(object):
if project is None:
project = user.id
pid = Project.safe_id(project)
- return self.__generate_rc(user.access, user.secret, pid, use_dmz)
+ return self.__generate_rc(user, pid, use_dmz)
@staticmethod
def __generate_rc(user, pid, use_dmz=True, host=None):
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 896c2a7a6..cc85ec691 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -21,6 +21,7 @@ Handles all requests relating to instances (guest vms).
"""
import datetime
+import re
import time
from nova import db
@@ -304,10 +305,8 @@ class API(base.Base):
host = instance['host']
if host:
- rpc.cast(context,
- self.db.queue_get_for(context, FLAGS.compute_topic, host),
- {"method": "terminate_instance",
- "args": {"instance_id": instance_id}})
+ self._cast_compute_message('terminate_instance', context,
+ instance_id, host)
else:
self.db.instance_destroy(context, instance_id)
@@ -336,50 +335,46 @@ class API(base.Base):
project_id)
return self.db.instance_get_all(context)
+ def _cast_compute_message(self, method, context, instance_id, host=None):
+ """Generic handler for RPC casts to compute."""
+ if not host:
+ instance = self.get(context, instance_id)
+ host = instance['host']
+ queue = self.db.queue_get_for(context, FLAGS.compute_topic, host)
+ kwargs = {'method': method, 'args': {'instance_id': instance_id}}
+ rpc.cast(context, queue, kwargs)
+
+ def _call_compute_message(self, method, context, instance_id, host=None):
+ """Generic handler for RPC calls to compute."""
+ if not host:
+ instance = self.get(context, instance_id)
+ host = instance["host"]
+ queue = self.db.queue_get_for(context, FLAGS.compute_topic, host)
+ kwargs = {"method": method, "args": {"instance_id": instance_id}}
+ return rpc.call(context, queue, kwargs)
+
def snapshot(self, context, instance_id, name):
"""Snapshot the given instance."""
- instance = self.get(context, instance_id)
- host = instance['host']
- rpc.cast(context,
- self.db.queue_get_for(context, FLAGS.compute_topic, host),
- {"method": "snapshot_instance",
- "args": {"instance_id": instance_id, "name": name}})
+ self._cast_compute_message('snapshot_instance', context, instance_id)
def reboot(self, context, instance_id):
"""Reboot the given instance."""
- instance = self.get(context, instance_id)
- host = instance['host']
- rpc.cast(context,
- self.db.queue_get_for(context, FLAGS.compute_topic, host),
- {"method": "reboot_instance",
- "args": {"instance_id": instance_id}})
+ self._cast_compute_message('reboot_instance', context, instance_id)
def pause(self, context, instance_id):
"""Pause the given instance."""
- instance = self.get(context, instance_id)
- host = instance['host']
- rpc.cast(context,
- self.db.queue_get_for(context, FLAGS.compute_topic, host),
- {"method": "pause_instance",
- "args": {"instance_id": instance_id}})
+ self._cast_compute_message('pause_instance', context, instance_id)
def unpause(self, context, instance_id):
"""Unpause the given instance."""
- instance = self.get(context, instance_id)
- host = instance['host']
- rpc.cast(context,
- self.db.queue_get_for(context, FLAGS.compute_topic, host),
- {"method": "unpause_instance",
- "args": {"instance_id": instance_id}})
+ self._cast_compute_message('unpause_instance', context, instance_id)
def get_diagnostics(self, context, instance_id):
"""Retrieve diagnostics for the given instance."""
- instance = self.get(context, instance_id)
- host = instance["host"]
- return rpc.call(context,
- self.db.queue_get_for(context, FLAGS.compute_topic, host),
- {"method": "get_diagnostics",
- "args": {"instance_id": instance_id}})
+ return self._call_compute_message(
+ "get_diagnostics",
+ context,
+ instance_id)
def get_actions(self, context, instance_id):
"""Retrieve actions for the given instance."""
@@ -387,89 +382,54 @@ class API(base.Base):
def suspend(self, context, instance_id):
"""suspend the instance with instance_id"""
- instance = self.get(context, instance_id)
- host = instance['host']
- rpc.cast(context,
- self.db.queue_get_for(context, FLAGS.compute_topic, host),
- {"method": "suspend_instance",
- "args": {"instance_id": instance_id}})
+ self._cast_compute_message('suspend_instance', context, instance_id)
def resume(self, context, instance_id):
"""resume the instance with instance_id"""
- instance = self.get(context, instance_id)
- host = instance['host']
- rpc.cast(context,
- self.db.queue_get_for(context, FLAGS.compute_topic, host),
- {"method": "resume_instance",
- "args": {"instance_id": instance_id}})
+ self._cast_compute_message('resume_instance', context, instance_id)
def rescue(self, context, instance_id):
"""Rescue the given instance."""
- instance = self.get(context, instance_id)
- host = instance['host']
- rpc.cast(context,
- self.db.queue_get_for(context, FLAGS.compute_topic, host),
- {"method": "rescue_instance",
- "args": {"instance_id": instance_id}})
+ self._cast_compute_message('rescue_instance', context, instance_id)
def unrescue(self, context, instance_id):
"""Unrescue the given instance."""
- instance = self.get(context, instance_id)
- host = instance['host']
- rpc.cast(context,
- self.db.queue_get_for(context, FLAGS.compute_topic, host),
- {"method": "unrescue_instance",
- "args": {"instance_id": instance['id']}})
+ self._cast_compute_message('unrescue_instance', context, instance_id)
+
+ def set_admin_password(self, context, instance_id):
+ """Set the root/admin password for the given instance."""
+ self._cast_compute_message('set_admin_password', context, instance_id)
def get_ajax_console(self, context, instance_id):
"""Get a url to an AJAX Console"""
-
instance = self.get(context, instance_id)
-
- output = rpc.call(context,
- '%s.%s' % (FLAGS.compute_topic,
- instance['host']),
- {'method': 'get_ajax_console',
- 'args': {'instance_id': instance['id']}})
-
+ output = self._call_compute_message('get_ajax_console',
+ context,
+ instance_id)
rpc.cast(context, '%s' % FLAGS.ajax_console_proxy_topic,
{'method': 'authorize_ajax_console',
'args': {'token': output['token'], 'host': output['host'],
'port': output['port']}})
-
return {'url': '%s?token=%s' % (FLAGS.ajax_console_proxy_url,
output['token'])}
- def lock(self, context, instance_id):
- """
- lock the instance with instance_id
+ def get_console_output(self, context, instance_id):
+ """Get console output for an an instance"""
+ return self._call_compute_message('get_console_output',
+ context,
+ instance_id)
- """
- instance = self.get_instance(context, instance_id)
- host = instance['host']
- rpc.cast(context,
- self.db.queue_get_for(context, FLAGS.compute_topic, host),
- {"method": "lock_instance",
- "args": {"instance_id": instance['id']}})
+ def lock(self, context, instance_id):
+ """lock the instance with instance_id"""
+ self._cast_compute_message('lock_instance', context, instance_id)
def unlock(self, context, instance_id):
- """
- unlock the instance with instance_id
-
- """
- instance = self.get_instance(context, instance_id)
- host = instance['host']
- rpc.cast(context,
- self.db.queue_get_for(context, FLAGS.compute_topic, host),
- {"method": "unlock_instance",
- "args": {"instance_id": instance['id']}})
+ """unlock the instance with instance_id"""
+ self._cast_compute_message('unlock_instance', context, instance_id)
def get_lock(self, context, instance_id):
- """
- return the boolean state of (instance with instance_id)'s lock
-
- """
- instance = self.get_instance(context, instance_id)
+ """return the boolean state of (instance with instance_id)'s lock"""
+ instance = self.get(context, instance_id)
return instance['locked']
def attach_volume(self, context, instance_id, volume_id, device):
diff --git a/nova/compute/disk.py b/nova/compute/disk.py
deleted file mode 100644
index 741499294..000000000
--- a/nova/compute/disk.py
+++ /dev/null
@@ -1,205 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# 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.
-"""
-Utility methods to resize, repartition, and modify disk images.
-
-Includes injection of SSH PGP keys into authorized_keys file.
-
-"""
-
-import os
-import tempfile
-
-from nova import exception
-from nova import flags
-from nova import log as logging
-
-
-LOG = logging.getLogger('nova.compute.disk')
-FLAGS = flags.FLAGS
-flags.DEFINE_integer('minimum_root_size', 1024 * 1024 * 1024 * 10,
- 'minimum size in bytes of root partition')
-flags.DEFINE_integer('block_size', 1024 * 1024 * 256,
- 'block_size to use for dd')
-
-
-def partition(infile, outfile, local_bytes=0, resize=True,
- local_type='ext2', execute=None):
- """
- Turns a partition (infile) into a bootable drive image (outfile).
-
- The first 63 sectors (0-62) of the resulting image is a master boot record.
- Infile becomes the first primary partition.
- If local bytes is specified, a second primary partition is created and
- formatted as ext2.
-
- ::
-
- In the diagram below, dashes represent drive sectors.
- +-----+------. . .-------+------. . .------+
- | 0 a| b c|d e|
- +-----+------. . .-------+------. . .------+
- | mbr | primary partiton | local partition |
- +-----+------. . .-------+------. . .------+
-
- """
- sector_size = 512
- file_size = os.path.getsize(infile)
- if resize and file_size < FLAGS.minimum_root_size:
- last_sector = FLAGS.minimum_root_size / sector_size - 1
- execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d'
- % (infile, last_sector, sector_size))
- execute('e2fsck -fp %s' % infile, check_exit_code=False)
- execute('resize2fs %s' % infile)
- file_size = FLAGS.minimum_root_size
- elif file_size % sector_size != 0:
- LOG.warn(_("Input partition size not evenly divisible by"
- " sector size: %d / %d"), file_size, sector_size)
- primary_sectors = file_size / sector_size
- if local_bytes % sector_size != 0:
- LOG.warn(_("Bytes for local storage not evenly divisible"
- " by sector size: %d / %d"), local_bytes, sector_size)
- local_sectors = local_bytes / sector_size
-
- mbr_last = 62 # a
- primary_first = mbr_last + 1 # b
- primary_last = primary_first + primary_sectors - 1 # c
- local_first = primary_last + 1 # d
- local_last = local_first + local_sectors - 1 # e
- last_sector = local_last # e
-
- # create an empty file
- execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d'
- % (outfile, mbr_last, sector_size))
-
- # make mbr partition
- execute('parted --script %s mklabel msdos' % outfile)
-
- # append primary file
- execute('dd if=%s of=%s bs=%s conv=notrunc,fsync oflag=append'
- % (infile, outfile, FLAGS.block_size))
-
- # make primary partition
- execute('parted --script %s mkpart primary %ds %ds'
- % (outfile, primary_first, primary_last))
-
- if local_bytes > 0:
- # make the file bigger
- execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d'
- % (outfile, last_sector, sector_size))
- # make and format local partition
- execute('parted --script %s mkpartfs primary %s %ds %ds'
- % (outfile, local_type, local_first, local_last))
-
-
-def extend(image, size, execute):
- file_size = os.path.getsize(image)
- if file_size >= size:
- return
- return execute('truncate -s size %s' % (image,))
-
-
-def inject_data(image, key=None, net=None, partition=None, execute=None):
- """Injects a ssh key and optionally net data into a disk image.
-
- it will mount the image as a fully partitioned disk and attempt to inject
- into the specified partition number.
-
- If partition is not specified it mounts the image as a single partition.
-
- """
- out, err = execute('sudo losetup --find --show %s' % image)
- if err:
- raise exception.Error(_('Could not attach image to loopback: %s')
- % err)
- device = out.strip()
- try:
- if not partition is None:
- # create partition
- out, err = execute('sudo kpartx -a %s' % device)
- if err:
- raise exception.Error(_('Failed to load partition: %s') % err)
- mapped_device = '/dev/mapper/%sp%s' % (device.split('/')[-1],
- partition)
- else:
- mapped_device = device
-
- # We can only loopback mount raw images. If the device isn't there,
- # it's normally because it's a .vmdk or a .vdi etc
- if not os.path.exists(mapped_device):
- raise exception.Error('Mapped device was not found (we can'
- ' only inject raw disk images): %s' %
- mapped_device)
-
- # Configure ext2fs so that it doesn't auto-check every N boots
- out, err = execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device)
-
- tmpdir = tempfile.mkdtemp()
- try:
- # mount loopback to dir
- out, err = execute(
- 'sudo mount %s %s' % (mapped_device, tmpdir))
- if err:
- raise exception.Error(_('Failed to mount filesystem: %s')
- % err)
-
- try:
- if key:
- # inject key file
- _inject_key_into_fs(key, tmpdir, execute=execute)
- if net:
- _inject_net_into_fs(net, tmpdir, execute=execute)
- finally:
- # unmount device
- execute('sudo umount %s' % mapped_device)
- finally:
- # remove temporary directory
- execute('rmdir %s' % tmpdir)
- if not partition is None:
- # remove partitions
- execute('sudo kpartx -d %s' % device)
- finally:
- # remove loopback
- execute('sudo losetup --detach %s' % device)
-
-
-def _inject_key_into_fs(key, fs, execute=None):
- """Add the given public ssh key to root's authorized_keys.
-
- key is an ssh key string.
- fs is the path to the base of the filesystem into which to inject the key.
- """
- sshdir = os.path.join(fs, 'root', '.ssh')
- execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter
- execute('sudo chown root %s' % sshdir)
- execute('sudo chmod 700 %s' % sshdir)
- keyfile = os.path.join(sshdir, 'authorized_keys')
- execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n')
-
-
-def _inject_net_into_fs(net, fs, execute=None):
- """Inject /etc/network/interfaces into the filesystem rooted at fs.
-
- net is the contents of /etc/network/interfaces.
- """
- netdir = os.path.join(os.path.join(fs, 'etc'), 'network')
- execute('sudo mkdir -p %s' % netdir) # existing dir doesn't matter
- execute('sudo chown root:root %s' % netdir)
- execute('sudo chmod 755 %s' % netdir)
- netfile = os.path.join(netdir, 'interfaces')
- execute('sudo tee %s' % netfile, net)
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 6b2fc4adb..613ee45f6 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -35,6 +35,8 @@ terminating it.
"""
import datetime
+import random
+import string
import logging
import socket
import functools
@@ -54,6 +56,8 @@ flags.DEFINE_string('compute_driver', 'nova.virt.connection.get_connection',
'Driver to use for controlling virtualization')
flags.DEFINE_string('stub_network', False,
'Stub network related code')
+flags.DEFINE_integer('password_length', 12,
+ 'Length of generated admin passwords')
flags.DEFINE_string('console_host', socket.gethostname(),
'Console proxy host to use to connect to instances on'
'this host.')
@@ -311,6 +315,35 @@ class ComputeManager(manager.Manager):
@exception.wrap_exception
@checks_instance_lock
+ def set_admin_password(self, context, instance_id, new_pass=None):
+ """Set the root/admin password for an instance on this server."""
+ context = context.elevated()
+ instance_ref = self.db.instance_get(context, instance_id)
+ if instance_ref['state'] != power_state.RUNNING:
+ logging.warn('trying to reset the password on a non-running '
+ 'instance: %s (state: %s expected: %s)',
+ instance_ref['id'],
+ instance_ref['state'],
+ power_state.RUNNING)
+
+ logging.debug('instance %s: setting admin password',
+ instance_ref['name'])
+ if new_pass is None:
+ # Generate a random password
+ new_pass = self._generate_password(FLAGS.password_length)
+
+ self.driver.set_admin_password(instance_ref, new_pass)
+ self._update_state(context, instance_id)
+
+ def _generate_password(self, length=20):
+ """Generate a random sequence of letters and digits
+ to be used as a password.
+ """
+ chrs = string.letters + string.digits
+ return "".join([random.choice(chrs) for i in xrange(length)])
+
+ @exception.wrap_exception
+ @checks_instance_lock
def rescue_instance(self, context, instance_id):
"""Rescue an instance on this server."""
context = context.elevated()
diff --git a/nova/db/api.py b/nova/db/api.py
index 1f81ef145..f9d561587 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -42,6 +42,10 @@ flags.DEFINE_string('db_backend', 'sqlalchemy',
'The backend to use for db')
flags.DEFINE_boolean('enable_new_services', True,
'Services to be added to the available pool on create')
+flags.DEFINE_string('instance_name_template', 'instance-%08x',
+ 'Template string to be used to generate instance names')
+flags.DEFINE_string('volume_name_template', 'volume-%08x',
+ 'Template string to be used to generate instance names')
IMPL = utils.LazyPluggable(FLAGS['db_backend'],
@@ -295,6 +299,10 @@ def fixed_ip_get_instance(context, address):
return IMPL.fixed_ip_get_instance(context, address)
+def fixed_ip_get_instance_v6(context, address):
+ return IMPL.fixed_ip_get_instance_v6(context, address)
+
+
def fixed_ip_get_network(context, address):
"""Get a network for a fixed ip by address."""
return IMPL.fixed_ip_get_network(context, address)
@@ -353,6 +361,10 @@ def instance_get_fixed_address(context, instance_id):
return IMPL.instance_get_fixed_address(context, instance_id)
+def instance_get_fixed_address_v6(context, instance_id):
+ return IMPL.instance_get_fixed_address_v6(context, instance_id)
+
+
def instance_get_floating_address(context, instance_id):
"""Get the first floating ip address of an instance."""
return IMPL.instance_get_floating_address(context, instance_id)
@@ -548,6 +560,10 @@ def project_get_network(context, project_id, associate=True):
return IMPL.project_get_network(context, project_id)
+def project_get_network_v6(context, project_id):
+ return IMPL.project_get_network_v6(context, project_id)
+
+
###################
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 516727487..3ba69af9e 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -606,6 +606,17 @@ def fixed_ip_get_instance(context, address):
return fixed_ip_ref.instance
+@require_context
+def fixed_ip_get_instance_v6(context, address):
+ session = get_session()
+ mac = utils.to_mac(address)
+
+ result = session.query(models.Instance
+ ).filter_by(mac_address=mac
+ ).first()
+ return result
+
+
@require_admin_context
def fixed_ip_get_network(context, address):
fixed_ip_ref = fixed_ip_get_by_address(context, address)
@@ -795,6 +806,17 @@ def instance_get_fixed_address(context, instance_id):
@require_context
+def instance_get_fixed_address_v6(context, instance_id):
+ session = get_session()
+ with session.begin():
+ instance_ref = instance_get(context, instance_id, session=session)
+ network_ref = network_get_by_instance(context, instance_id)
+ prefix = network_ref.cidr_v6
+ mac = instance_ref.mac_address
+ return utils.to_global_ipv6(prefix, mac)
+
+
+@require_context
def instance_get_floating_address(context, instance_id):
session = get_session()
with session.begin():
@@ -1131,6 +1153,11 @@ def project_get_network(context, project_id, associate=True):
return result
+@require_context
+def project_get_network_v6(context, project_id):
+ return project_get_network(context, project_id)
+
+
###################
diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py
index e8fb1f439..bf5e48b04 100644
--- a/nova/db/sqlalchemy/models.py
+++ b/nova/db/sqlalchemy/models.py
@@ -175,7 +175,7 @@ class Instance(BASE, NovaBase):
@property
def name(self):
- return "instance-%08x" % self.id
+ return FLAGS.instance_name_template % self.id
admin_pass = Column(String(255))
user_id = Column(String(255))
@@ -262,7 +262,7 @@ class Volume(BASE, NovaBase):
@property
def name(self):
- return "volume-%08x" % self.id
+ return FLAGS.volume_name_template % self.id
user_id = Column(String(255))
project_id = Column(String(255))
@@ -417,6 +417,10 @@ class Network(BASE, NovaBase):
injected = Column(Boolean, default=False)
cidr = Column(String(255), unique=True)
+ cidr_v6 = Column(String(255), unique=True)
+
+ ra_server = Column(String(255))
+
netmask = Column(String(255))
bridge = Column(String(255))
gateway = Column(String(255))
diff --git a/nova/exception.py b/nova/exception.py
index 7680e534a..ecd814e5d 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -76,6 +76,10 @@ class InvalidInputException(Error):
pass
+class TimeoutException(Error):
+ pass
+
+
def wrap_exception(f):
def _wrap(*args, **kw):
try:
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index 3743fc7e8..891e9bc1d 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -50,6 +50,7 @@ flags.DEFINE_string('routing_source_ip', '$my_ip',
'Public IP of network host')
flags.DEFINE_bool('use_nova_chains', False,
'use the nova_ routing chains instead of default')
+
flags.DEFINE_string('dns_server', None,
'if set, uses specific dns server for dnsmasq')
flags.DEFINE_string('dmz_cidr', '10.128.0.0/24',
@@ -196,6 +197,10 @@ def ensure_bridge(bridge, interface, net_attrs=None):
net_attrs['gateway'],
net_attrs['broadcast'],
net_attrs['netmask']))
+ if(FLAGS.use_ipv6):
+ _execute("sudo ifconfig %s add %s up" % \
+ (bridge,
+ net_attrs['cidr_v6']))
else:
_execute("sudo ifconfig %s up" % bridge)
if FLAGS.use_nova_chains:
@@ -262,6 +267,50 @@ def update_dhcp(context, network_id):
_execute(command, addl_env=env)
+def update_ra(context, network_id):
+ network_ref = db.network_get(context, network_id)
+
+ conffile = _ra_file(network_ref['bridge'], 'conf')
+ with open(conffile, 'w') as f:
+ conf_str = """
+interface %s
+{
+ AdvSendAdvert on;
+ MinRtrAdvInterval 3;
+ MaxRtrAdvInterval 10;
+ prefix %s
+ {
+ AdvOnLink on;
+ AdvAutonomous on;
+ };
+};
+""" % (network_ref['bridge'], network_ref['cidr_v6'])
+ f.write(conf_str)
+
+ # Make sure radvd can actually read it (it setuid()s to "nobody")
+ os.chmod(conffile, 0644)
+
+ pid = _ra_pid_for(network_ref['bridge'])
+
+ # if radvd is already running, then tell it to reload
+ if pid:
+ out, _err = _execute('cat /proc/%d/cmdline'
+ % pid, check_exit_code=False)
+ if conffile in out:
+ try:
+ _execute('sudo kill -HUP %d' % pid)
+ return
+ except Exception as exc: # pylint: disable-msg=W0703
+ LOG.debug(_("Hupping radvd threw %s"), exc)
+ else:
+ LOG.debug(_("Pid %d is stale, relaunching radvd"), pid)
+ command = _ra_cmd(network_ref)
+ _execute(command)
+ db.network_update(context, network_id,
+ {"ra_server":
+ utils.get_my_linklocal(network_ref['bridge'])})
+
+
def _host_dhcp(fixed_ip_ref):
"""Return a host string for an address"""
instance_ref = fixed_ip_ref['instance']
@@ -323,6 +372,15 @@ def _dnsmasq_cmd(net):
return ''.join(cmd)
+def _ra_cmd(net):
+ """Builds radvd command"""
+ cmd = ['sudo -E radvd',
+# ' -u nobody',
+ ' -C %s' % _ra_file(net['bridge'], 'conf'),
+ ' -p %s' % _ra_file(net['bridge'], 'pid')]
+ return ''.join(cmd)
+
+
def _stop_dnsmasq(network):
"""Stops the dnsmasq instance for a given network"""
pid = _dnsmasq_pid_for(network)
@@ -344,6 +402,16 @@ def _dhcp_file(bridge, kind):
kind))
+def _ra_file(bridge, kind):
+ """Return path to a pid or conf file for a bridge"""
+
+ if not os.path.exists(FLAGS.networks_path):
+ os.makedirs(FLAGS.networks_path)
+ return os.path.abspath("%s/nova-ra-%s.%s" % (FLAGS.networks_path,
+ bridge,
+ kind))
+
+
def _dnsmasq_pid_for(bridge):
"""Returns the pid for prior dnsmasq instance for a bridge
@@ -357,3 +425,18 @@ def _dnsmasq_pid_for(bridge):
if os.path.exists(pid_file):
with open(pid_file, 'r') as f:
return int(f.read())
+
+
+def _ra_pid_for(bridge):
+ """Returns the pid for prior radvd instance for a bridge
+
+ Returns None if no pid file exists
+
+ If machine has rebooted pid might be incorrect (caller should check)
+ """
+
+ pid_file = _ra_file(bridge, 'pid')
+
+ if os.path.exists(pid_file):
+ with open(pid_file, 'r') as f:
+ return int(f.read())
diff --git a/nova/network/manager.py b/nova/network/manager.py
index c75ecc671..a92bd3f99 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -82,6 +82,7 @@ flags.DEFINE_integer('network_size', 256,
flags.DEFINE_string('floating_range', '4.4.4.0/24',
'Floating IP address block')
flags.DEFINE_string('fixed_range', '10.0.0.0/8', 'Fixed IP address block')
+flags.DEFINE_string('fixed_range_v6', 'fd00::/48', 'Fixed IPv6 address block')
flags.DEFINE_integer('cnt_vpn_clients', 5,
'Number of addresses reserved for vpn clients')
flags.DEFINE_string('network_driver', 'nova.network.linux_net',
@@ -90,6 +91,9 @@ flags.DEFINE_bool('update_dhcp_on_disassociate', False,
'Whether to update dhcp when fixed_ip is disassociated')
flags.DEFINE_integer('fixed_ip_disassociate_timeout', 600,
'Seconds after which a deallocated ip is disassociated')
+
+flags.DEFINE_bool('use_ipv6', True,
+ 'use the ipv6')
flags.DEFINE_string('network_host', socket.gethostname(),
'Network host to use for ip allocation in flat modes')
flags.DEFINE_bool('fake_call', False,
@@ -235,8 +239,8 @@ class NetworkManager(manager.Manager):
"""Get the network host for the current context."""
raise NotImplementedError()
- def create_networks(self, context, num_networks, network_size,
- *args, **kwargs):
+ def create_networks(self, context, cidr, num_networks, network_size,
+ cidr_v6, *args, **kwargs):
"""Create networks based on parameters."""
raise NotImplementedError()
@@ -321,9 +325,11 @@ class FlatManager(NetworkManager):
pass
def create_networks(self, context, cidr, num_networks, network_size,
- *args, **kwargs):
+ cidr_v6, *args, **kwargs):
"""Create networks based on parameters."""
fixed_net = IPy.IP(cidr)
+ fixed_net_v6 = IPy.IP(cidr_v6)
+ significant_bits_v6 = 64
for index in range(num_networks):
start = index * network_size
significant_bits = 32 - int(math.log(network_size, 2))
@@ -336,7 +342,13 @@ class FlatManager(NetworkManager):
net['gateway'] = str(project_net[1])
net['broadcast'] = str(project_net.broadcast())
net['dhcp_start'] = str(project_net[2])
+
+ if(FLAGS.use_ipv6):
+ cidr_v6 = "%s/%s" % (fixed_net_v6[0], significant_bits_v6)
+ net['cidr_v6'] = cidr_v6
+
network_ref = self.db.network_create_safe(context, net)
+
if network_ref:
self._create_fixed_ips(context, network_ref['id'])
@@ -482,12 +494,16 @@ class VlanManager(NetworkManager):
network_ref['bridge'])
def create_networks(self, context, cidr, num_networks, network_size,
- vlan_start, vpn_start):
+ vlan_start, vpn_start, cidr_v6):
"""Create networks based on parameters."""
fixed_net = IPy.IP(cidr)
+ fixed_net_v6 = IPy.IP(cidr_v6)
+ network_size_v6 = 1 << 64
+ significant_bits_v6 = 64
for index in range(num_networks):
vlan = vlan_start + index
start = index * network_size
+ start_v6 = index * network_size_v6
significant_bits = 32 - int(math.log(network_size, 2))
cidr = "%s/%s" % (fixed_net[start], significant_bits)
project_net = IPy.IP(cidr)
@@ -500,6 +516,13 @@ class VlanManager(NetworkManager):
net['dhcp_start'] = str(project_net[3])
net['vlan'] = vlan
net['bridge'] = 'br%s' % vlan
+ if(FLAGS.use_ipv6):
+ cidr_v6 = "%s/%s" % (
+ fixed_net_v6[start_v6],
+ significant_bits_v6
+ )
+ net['cidr_v6'] = cidr_v6
+
# NOTE(vish): This makes ports unique accross the cloud, a more
# robust solution would be to make them unique per ip
net['vpn_public_port'] = vpn_start + index
@@ -538,6 +561,7 @@ class VlanManager(NetworkManager):
self.driver.ensure_vlan_bridge(network_ref['vlan'],
network_ref['bridge'],
network_ref)
+
# NOTE(vish): only ensure this forward if the address hasn't been set
# manually.
if address == FLAGS.vpn_ip:
@@ -546,6 +570,8 @@ class VlanManager(NetworkManager):
network_ref['vpn_private_address'])
if not FLAGS.fake_network:
self.driver.update_dhcp(context, network_id)
+ if(FLAGS.use_ipv6):
+ self.driver.update_ra(context, network_id)
@property
def _bottom_reserved_ips(self):
diff --git a/nova/test.py b/nova/test.py
index db5826c04..881baccd5 100644
--- a/nova/test.py
+++ b/nova/test.py
@@ -23,14 +23,10 @@ and some black magic for inline callbacks.
"""
import datetime
-import sys
-import time
import unittest
import mox
import stubout
-from twisted.internet import defer
-from twisted.trial import unittest as trial_unittest
from nova import context
from nova import db
@@ -74,7 +70,8 @@ class TestCase(unittest.TestCase):
FLAGS.fixed_range,
5, 16,
FLAGS.vlan_start,
- FLAGS.vpn_start)
+ FLAGS.vpn_start,
+ FLAGS.fixed_range_v6)
# emulate some of the mox stuff, we can't use the metaclass
# because it screws with our generators
@@ -139,95 +136,3 @@ class TestCase(unittest.TestCase):
_wrapped.func_name = self.originalAttach.func_name
rpc.Consumer.attach_to_eventlet = _wrapped
-
-
-class TrialTestCase(trial_unittest.TestCase):
- """Test case base class for all unit tests"""
- def setUp(self):
- """Run before each test method to initialize test environment"""
- super(TrialTestCase, self).setUp()
- # NOTE(vish): We need a better method for creating fixtures for tests
- # now that we have some required db setup for the system
- # to work properly.
- self.start = datetime.datetime.utcnow()
- ctxt = context.get_admin_context()
- if db.network_count(ctxt) != 5:
- network_manager.VlanManager().create_networks(ctxt,
- FLAGS.fixed_range,
- 5, 16,
- FLAGS.vlan_start,
- FLAGS.vpn_start)
-
- # emulate some of the mox stuff, we can't use the metaclass
- # because it screws with our generators
- self.mox = mox.Mox()
- self.stubs = stubout.StubOutForTesting()
- self.flag_overrides = {}
- self.injected = []
- self._original_flags = FLAGS.FlagValuesDict()
-
- def tearDown(self):
- """Runs after each test method to finalize/tear down test
- environment."""
- try:
- self.mox.UnsetStubs()
- self.stubs.UnsetAll()
- self.stubs.SmartUnsetAll()
- self.mox.VerifyAll()
- # NOTE(vish): Clean up any ips associated during the test.
- ctxt = context.get_admin_context()
- db.fixed_ip_disassociate_all_by_timeout(ctxt, FLAGS.host,
- self.start)
- db.network_disassociate_all(ctxt)
- for x in self.injected:
- try:
- x.stop()
- except AssertionError:
- pass
-
- if FLAGS.fake_rabbit:
- fakerabbit.reset_all()
-
- db.security_group_destroy_all(ctxt)
- super(TrialTestCase, self).tearDown()
- finally:
- self.reset_flags()
-
- def flags(self, **kw):
- """Override flag variables for a test"""
- for k, v in kw.iteritems():
- if k in self.flag_overrides:
- self.reset_flags()
- raise Exception(
- 'trying to override already overriden flag: %s' % k)
- self.flag_overrides[k] = getattr(FLAGS, k)
- setattr(FLAGS, k, v)
-
- def reset_flags(self):
- """Resets all flag variables for the test. Runs after each test"""
- FLAGS.Reset()
- for k, v in self._original_flags.iteritems():
- setattr(FLAGS, k, v)
-
- def run(self, result=None):
- test_method = getattr(self, self._testMethodName)
- setattr(self,
- self._testMethodName,
- self._maybeInlineCallbacks(test_method, result))
- rv = super(TrialTestCase, self).run(result)
- setattr(self, self._testMethodName, test_method)
- return rv
-
- def _maybeInlineCallbacks(self, func, result):
- def _wrapped():
- g = func()
- if isinstance(g, defer.Deferred):
- return g
- if not hasattr(g, 'send'):
- return defer.succeed(g)
-
- inlined = defer.inlineCallbacks(func)
- d = inlined()
- return d
- _wrapped.func_name = func.func_name
- return _wrapped
diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py
index 8b289603a..17789c25c 100644
--- a/nova/tests/test_api.py
+++ b/nova/tests/test_api.py
@@ -265,6 +265,72 @@ class ApiEc2TestCase(test.TestCase):
return
+ def test_authorize_revoke_security_group_cidr_v6(self):
+ """
+ Test that we can add and remove CIDR based rules
+ to a security group for IPv6
+ """
+ self.expect_http()
+ self.mox.ReplayAll()
+ user = self.manager.create_user('fake', 'fake', 'fake')
+ project = self.manager.create_project('fake', 'fake', 'fake')
+
+ # At the moment, you need both of these to actually be netadmin
+ self.manager.add_role('fake', 'netadmin')
+ project.add_role('fake', 'netadmin')
+
+ security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd")
+ for x in range(random.randint(4, 8)))
+
+ group = self.ec2.create_security_group(security_group_name,
+ 'test group')
+
+ self.expect_http()
+ self.mox.ReplayAll()
+ group.connection = self.ec2
+
+ group.authorize('tcp', 80, 81, '::/0')
+
+ self.expect_http()
+ self.mox.ReplayAll()
+
+ rv = self.ec2.get_all_security_groups()
+ # I don't bother checkng that we actually find it here,
+ # because the create/delete unit test further up should
+ # be good enough for that.
+ for group in rv:
+ if group.name == security_group_name:
+ self.assertEquals(len(group.rules), 1)
+ self.assertEquals(int(group.rules[0].from_port), 80)
+ self.assertEquals(int(group.rules[0].to_port), 81)
+ self.assertEquals(len(group.rules[0].grants), 1)
+ self.assertEquals(str(group.rules[0].grants[0]), '::/0')
+
+ self.expect_http()
+ self.mox.ReplayAll()
+ group.connection = self.ec2
+
+ group.revoke('tcp', 80, 81, '::/0')
+
+ self.expect_http()
+ self.mox.ReplayAll()
+
+ self.ec2.delete_security_group(security_group_name)
+
+ self.expect_http()
+ self.mox.ReplayAll()
+ group.connection = self.ec2
+
+ rv = self.ec2.get_all_security_groups()
+
+ self.assertEqual(len(rv), 1)
+ self.assertEqual(rv[0].name, 'default')
+
+ self.manager.delete_project(project)
+ self.manager.delete_user(user)
+
+ return
+
def test_authorize_revoke_security_group_foreign_group(self):
"""
Test that we can grant and revoke another security group access
diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py
index ec2a36fe8..771b1fcc0 100644
--- a/nova/tests/test_cloud.py
+++ b/nova/tests/test_cloud.py
@@ -129,10 +129,13 @@ class CloudTestCase(test.TestCase):
vol2 = db.volume_create(self.context, {})
result = self.cloud.describe_volumes(self.context)
self.assertEqual(len(result['volumeSet']), 2)
+ volume_id = cloud.id_to_ec2_id(vol2['id'], 'vol-%08x')
result = self.cloud.describe_volumes(self.context,
- volume_id=[vol2['id']])
+ volume_id=[volume_id])
self.assertEqual(len(result['volumeSet']), 1)
- self.assertEqual(result['volumeSet'][0]['volumeId'], vol2['id'])
+ self.assertEqual(
+ cloud.ec2_id_to_id(result['volumeSet'][0]['volumeId']),
+ vol2['id'])
db.volume_destroy(self.context, vol1['id'])
db.volume_destroy(self.context, vol2['id'])
@@ -391,7 +394,8 @@ class CloudTestCase(test.TestCase):
def test_update_of_volume_display_fields(self):
vol = db.volume_create(self.context, {})
- self.cloud.update_volume(self.context, vol['id'],
+ self.cloud.update_volume(self.context,
+ cloud.id_to_ec2_id(vol['id'], 'vol-%08x'),
display_name='c00l v0lum3')
vol = db.volume_get(self.context, vol['id'])
self.assertEqual('c00l v0lum3', vol['display_name'])
@@ -399,8 +403,9 @@ class CloudTestCase(test.TestCase):
def test_update_of_volume_wont_update_private_fields(self):
vol = db.volume_create(self.context, {})
- self.cloud.update_volume(self.context, vol['id'],
- mountpoint='/not/here')
+ self.cloud.update_volume(self.context,
+ cloud.id_to_ec2_id(vol['id'], 'vol-%08x'),
+ mountpoint='/not/here')
vol = db.volume_get(self.context, vol['id'])
self.assertEqual(None, vol['mountpoint'])
db.volume_destroy(self.context, vol['id'])
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index 53070ef3c..f50f5755f 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -155,6 +155,13 @@ class ComputeTestCase(test.TestCase):
self.compute.reboot_instance(self.context, instance_id)
self.compute.terminate_instance(self.context, instance_id)
+ def test_set_admin_password(self):
+ """Ensure instance can have its admin password set"""
+ instance_id = self._create_instance()
+ self.compute.run_instance(self.context, instance_id)
+ self.compute.set_admin_password(self.context, instance_id)
+ self.compute.terminate_instance(self.context, instance_id)
+
def test_snapshot(self):
"""Ensure instance can be snapshotted"""
instance_id = self._create_instance()
diff --git a/nova/tests/test_log.py b/nova/tests/test_log.py
index beb1d97cf..868a5ead3 100644
--- a/nova/tests/test_log.py
+++ b/nova/tests/test_log.py
@@ -9,7 +9,7 @@ def _fake_context():
return context.RequestContext(1, 1)
-class RootLoggerTestCase(test.TrialTestCase):
+class RootLoggerTestCase(test.TestCase):
def setUp(self):
super(RootLoggerTestCase, self).setUp()
self.log = log.logging.root
@@ -46,7 +46,7 @@ class RootLoggerTestCase(test.TrialTestCase):
self.assert_(True) # didn't raise exception
-class NovaFormatterTestCase(test.TrialTestCase):
+class NovaFormatterTestCase(test.TestCase):
def setUp(self):
super(NovaFormatterTestCase, self).setUp()
self.flags(logging_context_format_string="HAS CONTEXT "\
@@ -78,7 +78,7 @@ class NovaFormatterTestCase(test.TrialTestCase):
self.assertEqual("NOCTXT: baz --DBG\n", self.stream.getvalue())
-class NovaLoggerTestCase(test.TrialTestCase):
+class NovaLoggerTestCase(test.TestCase):
def setUp(self):
super(NovaLoggerTestCase, self).setUp()
self.flags(default_log_levels=["nova-test=AUDIT"], verbose=False)
@@ -96,7 +96,7 @@ class NovaLoggerTestCase(test.TrialTestCase):
self.assertEqual(log.AUDIT, l.level)
-class VerboseLoggerTestCase(test.TrialTestCase):
+class VerboseLoggerTestCase(test.TestCase):
def setUp(self):
super(VerboseLoggerTestCase, self).setUp()
self.flags(default_log_levels=["nova.test=AUDIT"], verbose=True)
diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py
index 349e20f84..00f9323f3 100644
--- a/nova/tests/test_network.py
+++ b/nova/tests/test_network.py
@@ -96,6 +96,28 @@ class NetworkTestCase(test.TestCase):
self.context.project_id = self.projects[project_num].id
self.network.deallocate_fixed_ip(self.context, address)
+ def test_private_ipv6(self):
+ """Make sure ipv6 is OK"""
+ if FLAGS.use_ipv6:
+ instance_ref = self._create_instance(0)
+ address = self._create_address(0, instance_ref['id'])
+ network_ref = db.project_get_network(
+ context.get_admin_context(),
+ self.context.project_id)
+ address_v6 = db.instance_get_fixed_address_v6(
+ context.get_admin_context(),
+ instance_ref['id'])
+ self.assertEqual(instance_ref['mac_address'],
+ utils.to_mac(address_v6))
+ instance_ref2 = db.fixed_ip_get_instance_v6(
+ context.get_admin_context(),
+ address_v6)
+ self.assertEqual(instance_ref['id'], instance_ref2['id'])
+ self.assertEqual(address_v6,
+ utils.to_global_ipv6(
+ network_ref['cidr_v6'],
+ instance_ref['mac_address']))
+
def test_public_network_association(self):
"""Makes sure that we can allocaate a public ip"""
# TODO(vish): better way of adding floating ips
diff --git a/nova/tests/test_twistd.py b/nova/tests/test_twistd.py
index 75007b9c8..ff8627c3b 100644
--- a/nova/tests/test_twistd.py
+++ b/nova/tests/test_twistd.py
@@ -28,7 +28,7 @@ from nova import test
FLAGS = flags.FLAGS
-class TwistdTestCase(test.TrialTestCase):
+class TwistdTestCase(test.TestCase):
def setUp(self):
super(TwistdTestCase, self).setUp()
self.Options = twistd.WrapTwistedOptions(twistd.TwistdServerOptions)
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py
index ec9462ada..261ee0fde 100644
--- a/nova/tests/test_xenapi.py
+++ b/nova/tests/test_xenapi.py
@@ -31,6 +31,7 @@ from nova.compute import power_state
from nova.virt import xenapi_conn
from nova.virt.xenapi import fake as xenapi_fake
from nova.virt.xenapi import volume_utils
+from nova.virt.xenapi.vmops import SimpleDH
from nova.tests.db import fakes as db_fakes
from nova.tests.xenapi import stubs
@@ -262,3 +263,29 @@ class XenAPIVMTestCase(test.TestCase):
instance = db.instance_create(values)
self.conn.spawn(instance)
return instance
+
+
+class XenAPIDiffieHellmanTestCase(test.TestCase):
+ """
+ Unit tests for Diffie-Hellman code
+ """
+ def setUp(self):
+ super(XenAPIDiffieHellmanTestCase, self).setUp()
+ self.alice = SimpleDH()
+ self.bob = SimpleDH()
+
+ def test_shared(self):
+ alice_pub = self.alice.get_public()
+ bob_pub = self.bob.get_public()
+ alice_shared = self.alice.compute_shared(bob_pub)
+ bob_shared = self.bob.compute_shared(alice_pub)
+ self.assertEquals(alice_shared, bob_shared)
+
+ def test_encryption(self):
+ msg = "This is a top-secret message"
+ enc = self.alice.encrypt(msg)
+ dec = self.bob.decrypt(enc)
+ self.assertEquals(dec, msg)
+
+ def tearDown(self):
+ super(XenAPIDiffieHellmanTestCase, self).tearDown()
diff --git a/nova/utils.py b/nova/utils.py
index 3fefc80fc..fdbe81c0c 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -31,6 +31,8 @@ import struct
import sys
import time
from xml.sax import saxutils
+import re
+import netaddr
from eventlet import event
from eventlet import greenthread
@@ -201,6 +203,40 @@ def last_octet(address):
return int(address.split(".")[-1])
+def get_my_linklocal(interface):
+ try:
+ if_str = execute("ip -f inet6 -o addr show %s" % interface)
+ condition = "\s+inet6\s+([0-9a-f:]+/\d+)\s+scope\s+link"
+ links = [re.search(condition, x) for x in if_str[0].split('\n')]
+ address = [w.group(1) for w in links if w is not None]
+ if address[0] is not None:
+ return address[0]
+ else:
+ return 'fe00::'
+ except IndexError as ex:
+ LOG.warn(_("Couldn't get Link Local IP of %s :%s"), interface, ex)
+ except ProcessExecutionError as ex:
+ LOG.warn(_("Couldn't get Link Local IP of %s :%s"), interface, ex)
+ except:
+ return 'fe00::'
+
+
+def to_global_ipv6(prefix, mac):
+ mac64 = netaddr.EUI(mac).eui64().words
+ int_addr = int(''.join(['%02x' % i for i in mac64]), 16)
+ mac64_addr = netaddr.IPAddress(int_addr)
+ maskIP = netaddr.IPNetwork(prefix).ip
+ return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).format()
+
+
+def to_mac(ipv6_address):
+ address = netaddr.IPAddress(ipv6_address)
+ mask1 = netaddr.IPAddress("::ffff:ffff:ffff:ffff")
+ mask2 = netaddr.IPAddress("::0200:0:0:0")
+ mac64 = netaddr.EUI(int(address & mask1 ^ mask2)).words
+ return ":".join(["%02x" % i for i in mac64[0:3] + mac64[5:8]])
+
+
def utcnow():
"""Overridable version of datetime.datetime.utcnow."""
if utcnow.override_time:
diff --git a/nova/virt/disk.py b/nova/virt/disk.py
new file mode 100644
index 000000000..c5565abfa
--- /dev/null
+++ b/nova/virt/disk.py
@@ -0,0 +1,186 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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.
+"""
+Utility methods to resize, repartition, and modify disk images.
+
+Includes injection of SSH PGP keys into authorized_keys file.
+
+"""
+
+import os
+import tempfile
+import time
+
+from nova import exception
+from nova import flags
+from nova import log as logging
+from nova import utils
+
+
+LOG = logging.getLogger('nova.compute.disk')
+FLAGS = flags.FLAGS
+flags.DEFINE_integer('minimum_root_size', 1024 * 1024 * 1024 * 10,
+ 'minimum size in bytes of root partition')
+flags.DEFINE_integer('block_size', 1024 * 1024 * 256,
+ 'block_size to use for dd')
+
+
+def extend(image, size):
+ """Increase image to size"""
+ file_size = os.path.getsize(image)
+ if file_size >= size:
+ return
+ utils.execute('truncate -s %s %s' % (size, image))
+ # NOTE(vish): attempts to resize filesystem
+ utils.execute('e2fsck -fp %s' % image, check_exit_code=False)
+ utils.execute('resize2fs %s' % image, check_exit_code=False)
+
+
+def inject_data(image, key=None, net=None, partition=None, nbd=False):
+ """Injects a ssh key and optionally net data into a disk image.
+
+ it will mount the image as a fully partitioned disk and attempt to inject
+ into the specified partition number.
+
+ If partition is not specified it mounts the image as a single partition.
+
+ """
+ device = _link_device(image, nbd)
+ try:
+ if not partition is None:
+ # create partition
+ out, err = utils.execute('sudo kpartx -a %s' % device)
+ if err:
+ raise exception.Error(_('Failed to load partition: %s') % err)
+ mapped_device = '/dev/mapper/%sp%s' % (device.split('/')[-1],
+ partition)
+ else:
+ mapped_device = device
+
+ # We can only loopback mount raw images. If the device isn't there,
+ # it's normally because it's a .vmdk or a .vdi etc
+ if not os.path.exists(mapped_device):
+ raise exception.Error('Mapped device was not found (we can'
+ ' only inject raw disk images): %s' %
+ mapped_device)
+
+ # Configure ext2fs so that it doesn't auto-check every N boots
+ out, err = utils.execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device)
+
+ tmpdir = tempfile.mkdtemp()
+ try:
+ # mount loopback to dir
+ out, err = utils.execute(
+ 'sudo mount %s %s' % (mapped_device, tmpdir))
+ if err:
+ raise exception.Error(_('Failed to mount filesystem: %s')
+ % err)
+
+ try:
+ if key:
+ # inject key file
+ _inject_key_into_fs(key, tmpdir)
+ if net:
+ _inject_net_into_fs(net, tmpdir)
+ finally:
+ # unmount device
+ utils.execute('sudo umount %s' % mapped_device)
+ finally:
+ # remove temporary directory
+ utils.execute('rmdir %s' % tmpdir)
+ if not partition is None:
+ # remove partitions
+ utils.execute('sudo kpartx -d %s' % device)
+ finally:
+ _unlink_device(device, nbd)
+
+
+def _link_device(image, nbd):
+ """Link image to device using loopback or nbd"""
+ if nbd:
+ device = _allocate_device()
+ utils.execute('sudo qemu-nbd -c %s %s' % (device, image))
+ # NOTE(vish): this forks into another process, so give it a chance
+ # to set up before continuuing
+ for i in xrange(10):
+ if os.path.exists("/sys/block/%s/pid" % os.path.basename(device)):
+ return device
+ time.sleep(1)
+ raise exception.Error(_('nbd device %s did not show up') % device)
+ else:
+ out, err = utils.execute('sudo losetup --find --show %s' % image)
+ if err:
+ raise exception.Error(_('Could not attach image to loopback: %s')
+ % err)
+ return out.strip()
+
+
+def _unlink_device(device, nbd):
+ """Unlink image from device using loopback or nbd"""
+ if nbd:
+ utils.execute('sudo qemu-nbd -d %s' % device)
+ _free_device(device)
+ else:
+ utils.execute('sudo losetup --detach %s' % device)
+
+
+_DEVICES = ['/dev/nbd%s' % i for i in xrange(16)]
+
+
+def _allocate_device():
+ # NOTE(vish): This assumes no other processes are allocating nbd devices.
+ # It may race cause a race condition if multiple
+ # workers are running on a given machine.
+ while True:
+ if not _DEVICES:
+ raise exception.Error(_('No free nbd devices'))
+ device = _DEVICES.pop()
+ if not os.path.exists("/sys/block/%s/pid" % os.path.basename(device)):
+ break
+ return device
+
+
+def _free_device(device):
+ _DEVICES.append(device)
+
+
+def _inject_key_into_fs(key, fs):
+ """Add the given public ssh key to root's authorized_keys.
+
+ key is an ssh key string.
+ fs is the path to the base of the filesystem into which to inject the key.
+ """
+ sshdir = os.path.join(fs, 'root', '.ssh')
+ utils.execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter
+ utils.execute('sudo chown root %s' % sshdir)
+ utils.execute('sudo chmod 700 %s' % sshdir)
+ keyfile = os.path.join(sshdir, 'authorized_keys')
+ utils.execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n')
+
+
+def _inject_net_into_fs(net, fs):
+ """Inject /etc/network/interfaces into the filesystem rooted at fs.
+
+ net is the contents of /etc/network/interfaces.
+ """
+ netdir = os.path.join(os.path.join(fs, 'etc'), 'network')
+ utils.execute('sudo mkdir -p %s' % netdir) # existing dir doesn't matter
+ utils.execute('sudo chown root:root %s' % netdir)
+ utils.execute('sudo chmod 755 %s' % netdir)
+ netfile = os.path.join(netdir, 'interfaces')
+ utils.execute('sudo tee %s' % netfile, net)
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index 9186d885e..a57a8f43b 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -98,7 +98,7 @@ class FakeConnection(object):
the new instance.
The work will be done asynchronously. This function returns a
- Deferred that allows the caller to detect when it is complete.
+ task that allows the caller to detect when it is complete.
Once this successfully completes, the instance should be
running (power_state.RUNNING).
@@ -122,7 +122,7 @@ class FakeConnection(object):
The second parameter is the name of the snapshot.
The work will be done asynchronously. This function returns a
- Deferred that allows the caller to detect when it is complete.
+ task that allows the caller to detect when it is complete.
"""
pass
@@ -134,7 +134,20 @@ class FakeConnection(object):
and so the instance is being specified as instance.name.
The work will be done asynchronously. This function returns a
- Deferred that allows the caller to detect when it is complete.
+ task that allows the caller to detect when it is complete.
+ """
+ pass
+
+ def set_admin_password(self, instance, new_pass):
+ """
+ Set the root password on the specified instance.
+
+ The first parameter is an instance of nova.compute.service.Instance,
+ and so the instance is being specified as instance.name. The second
+ parameter is the value of the new password.
+
+ The work will be done asynchronously. This function returns a
+ task that allows the caller to detect when it is complete.
"""
pass
@@ -182,7 +195,7 @@ class FakeConnection(object):
and so the instance is being specified as instance.name.
The work will be done asynchronously. This function returns a
- Deferred that allows the caller to detect when it is complete.
+ task that allows the caller to detect when it is complete.
"""
del self.instances[instance.name]
diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template
index 2eb7d9488..de06a1eb0 100644
--- a/nova/virt/libvirt.xml.template
+++ b/nova/virt/libvirt.xml.template
@@ -7,13 +7,13 @@
#set $disk_bus = 'uml'
<type>uml</type>
<kernel>/usr/bin/linux</kernel>
- <root>/dev/ubda1</root>
+ <root>/dev/ubda</root>
#else
#if $type == 'xen'
#set $disk_prefix = 'sd'
#set $disk_bus = 'scsi'
<type>linux</type>
- <root>/dev/xvda1</root>
+ <root>/dev/xvda</root>
#else
#set $disk_prefix = 'vd'
#set $disk_bus = 'virtio'
@@ -28,7 +28,7 @@
#if $type == 'xen'
<cmdline>ro</cmdline>
#else
- <cmdline>root=/dev/vda1 console=ttyS0</cmdline>
+ <cmdline>root=/dev/vda console=ttyS0</cmdline>
#end if
#if $getVar('ramdisk', None)
<initrd>${ramdisk}</initrd>
@@ -46,18 +46,28 @@
<devices>
#if $getVar('rescue', False)
<disk type='file'>
+ <driver type='${driver_type}'/>
<source file='${basepath}/rescue-disk'/>
<target dev='${disk_prefix}a' bus='${disk_bus}'/>
</disk>
<disk type='file'>
+ <driver type='${driver_type}'/>
<source file='${basepath}/disk'/>
<target dev='${disk_prefix}b' bus='${disk_bus}'/>
</disk>
#else
<disk type='file'>
+ <driver type='${driver_type}'/>
<source file='${basepath}/disk'/>
<target dev='${disk_prefix}a' bus='${disk_bus}'/>
</disk>
+ #if $getVar('local', False)
+ <disk type='file'>
+ <driver type='${driver_type}'/>
+ <source file='${basepath}/local'/>
+ <target dev='${disk_prefix}b' bus='${disk_bus}'/>
+ </disk>
+ #end if
#end if
<interface type='bridge'>
<source bridge='${bridge_name}'/>
@@ -66,6 +76,7 @@
<filterref filter="nova-instance-${name}">
<parameter name="IP" value="${ip_address}" />
<parameter name="DHCPSERVER" value="${dhcp_server}" />
+ <parameter name="RASERVER" value="${ra_server}" />
#if $getVar('extra_params', False)
${extra_params}
#end if
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index bd863b3a2..073a8e5bb 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -58,9 +58,9 @@ from nova import log as logging
from nova import utils
#from nova.api import context
from nova.auth import manager
-from nova.compute import disk
from nova.compute import instance_types
from nova.compute import power_state
+from nova.virt import disk
from nova.virt import images
libvirt = None
@@ -91,6 +91,9 @@ flags.DEFINE_string('libvirt_uri',
flags.DEFINE_bool('allow_project_net_traffic',
True,
'Whether to allow in project network traffic')
+flags.DEFINE_bool('use_cow_images',
+ True,
+ 'Whether to use cow images')
flags.DEFINE_string('ajaxterm_portrange',
'10000-12000',
'Range of ports that ajaxterm should randomly try to bind')
@@ -127,6 +130,16 @@ def _get_net_and_mask(cidr):
return str(net.net()), str(net.netmask())
+def _get_net_and_prefixlen(cidr):
+ net = IPy.IP(cidr)
+ return str(net.net()), str(net.prefixlen())
+
+
+def _get_ip_version(cidr):
+ net = IPy.IP(cidr)
+ return int(net.version())
+
+
class LibvirtConnection(object):
def __init__(self, read_only):
@@ -197,40 +210,29 @@ class LibvirtConnection(object):
pass
# If the instance is already terminated, we're still happy
- done = event.Event()
-
# We'll save this for when we do shutdown,
# instead of destroy - but destroy returns immediately
timer = utils.LoopingCall(f=None)
- def _wait_for_shutdown():
+ while True:
try:
state = self.get_info(instance['name'])['state']
db.instance_set_state(context.get_admin_context(),
instance['id'], state)
if state == power_state.SHUTDOWN:
- timer.stop()
+ break
except Exception:
db.instance_set_state(context.get_admin_context(),
instance['id'],
power_state.SHUTDOWN)
- timer.stop()
+ break
- timer.f = _wait_for_shutdown
- timer_done = timer.start(interval=0.5, now=True)
+ self.firewall_driver.unfilter_instance(instance)
- # NOTE(termie): this is strictly superfluous (we could put the
- # cleanup code in the timer), but this emulates the
- # previous model so I am keeping it around until
- # everything has been vetted a bit
- def _wait_for_timer():
- timer_done.wait()
- if cleanup:
- self._cleanup(instance)
- done.send()
+ if cleanup:
+ self._cleanup(instance)
- greenthread.spawn(_wait_for_timer)
- return done
+ return True
def _cleanup(self, instance):
target = os.path.join(FLAGS.instances_path, instance['name'])
@@ -383,7 +385,6 @@ class LibvirtConnection(object):
instance['id'],
power_state.NOSTATE,
'launching')
-
self.nwfilter.setup_basic_filtering(instance)
self.firewall_driver.prepare_instance_filter(instance)
self._create_image(instance, xml)
@@ -491,19 +492,57 @@ class LibvirtConnection(object):
subprocess.Popen(cmd, shell=True)
return {'token': token, 'host': host, 'port': port}
+ def _cache_image(self, fn, target, fname, cow=False, *args, **kwargs):
+ """Wrapper for a method that creates an image that caches the image.
+
+ This wrapper will save the image into a common store and create a
+ copy for use by the hypervisor.
+
+ The underlying method should specify a kwarg of target representing
+ where the image will be saved.
+
+ fname is used as the filename of the base image. The filename needs
+ to be unique to a given image.
+
+ If cow is True, it will make a CoW image instead of a copy.
+ """
+ if not os.path.exists(target):
+ base_dir = os.path.join(FLAGS.instances_path, '_base')
+ if not os.path.exists(base_dir):
+ os.mkdir(base_dir)
+ os.chmod(base_dir, 0777)
+ base = os.path.join(base_dir, fname)
+ if not os.path.exists(base):
+ fn(target=base, *args, **kwargs)
+ if cow:
+ utils.execute('qemu-img create -f qcow2 -o '
+ 'cluster_size=2M,backing_file=%s %s'
+ % (base, target))
+ else:
+ utils.execute('cp %s %s' % (base, target))
+
+ def _fetch_image(self, target, image_id, user, project, size=None):
+ """Grab image and optionally attempt to resize it"""
+ images.fetch(image_id, target, user, project)
+ if size:
+ disk.extend(target, size)
+
+ def _create_local(self, target, local_gb):
+ """Create a blank image of specified size"""
+ utils.execute('truncate %s -s %dG' % (target, local_gb))
+ # TODO(vish): should we format disk by default?
+
def _create_image(self, inst, libvirt_xml, prefix='', disk_images=None):
# syntactic nicety
- basepath = lambda fname = '', prefix = prefix: os.path.join(
- FLAGS.instances_path,
- inst['name'],
- prefix + fname)
+ def basepath(fname='', prefix=prefix):
+ return os.path.join(FLAGS.instances_path,
+ inst['name'],
+ prefix + fname)
# ensure directories exist and are writable
utils.execute('mkdir -p %s' % basepath(prefix=''))
utils.execute('chmod 0777 %s' % basepath(prefix=''))
- # TODO(termie): these are blocking calls, it would be great
- # if they weren't.
LOG.info(_('instance %s: Creating image'), inst['name'])
f = open(basepath('libvirt.xml'), 'w')
f.write(libvirt_xml)
@@ -520,23 +559,44 @@ class LibvirtConnection(object):
disk_images = {'image_id': inst['image_id'],
'kernel_id': inst['kernel_id'],
'ramdisk_id': inst['ramdisk_id']}
- if not os.path.exists(basepath('disk')):
- images.fetch(inst.image_id, basepath('disk-raw'), user,
- project)
-
- if inst['kernel_id']:
- if not os.path.exists(basepath('kernel')):
- images.fetch(inst['kernel_id'], basepath('kernel'),
- user, project)
- if inst['ramdisk_id']:
- if not os.path.exists(basepath('ramdisk')):
- images.fetch(inst['ramdisk_id'], basepath('ramdisk'),
- user, project)
-
- def execute(cmd, process_input=None, check_exit_code=True):
- return utils.execute(cmd=cmd,
- process_input=process_input,
- check_exit_code=check_exit_code)
+
+ if disk_images['kernel_id']:
+ self._cache_image(fn=self._fetch_image,
+ target=basepath('kernel'),
+ fname=disk_images['kernel_id'],
+ image_id=disk_images['kernel_id'],
+ user=user,
+ project=project)
+ if disk_images['ramdisk_id']:
+ self._cache_image(fn=self._fetch_image,
+ target=basepath('ramdisk'),
+ fname=disk_images['ramdisk_id'],
+ image_id=disk_images['ramdisk_id'],
+ user=user,
+ project=project)
+
+ root_fname = disk_images['image_id']
+ size = FLAGS.minimum_root_size
+ if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-':
+ size = None
+ root_fname += "_sm"
+
+ self._cache_image(fn=self._fetch_image,
+ target=basepath('disk'),
+ fname=root_fname,
+ cow=FLAGS.use_cow_images,
+ image_id=disk_images['image_id'],
+ user=user,
+ project=project,
+ size=size)
+ type_data = instance_types.INSTANCE_TYPES[inst['instance_type']]
+
+ if type_data['local_gb']:
+ self._cache_image(fn=self._create_local,
+ target=basepath('local'),
+ fname="local_%s" % type_data['local_gb'],
+ cow=FLAGS.use_cow_images,
+ local_gb=type_data['local_gb'])
# For now, we assume that if we're not using a kernel, we're using a
# partitioned disk image where the target partition is the first
@@ -552,12 +612,16 @@ class LibvirtConnection(object):
if network_ref['injected']:
admin_context = context.get_admin_context()
address = db.instance_get_fixed_address(admin_context, inst['id'])
+ ra_server = network_ref['ra_server']
+ if not ra_server:
+ ra_server = "fd00::"
with open(FLAGS.injected_network_template) as f:
net = f.read() % {'address': address,
'netmask': network_ref['netmask'],
'gateway': network_ref['gateway'],
'broadcast': network_ref['broadcast'],
- 'dns': network_ref['dns']}
+ 'dns': network_ref['dns'],
+ 'ra_server': ra_server}
if key or net:
if key:
LOG.info(_('instance %s: injecting key into image %s'),
@@ -566,34 +630,15 @@ class LibvirtConnection(object):
LOG.info(_('instance %s: injecting net into image %s'),
inst['name'], inst.image_id)
try:
- disk.inject_data(basepath('disk-raw'), key, net,
+ disk.inject_data(basepath('disk'), key, net,
partition=target_partition,
- execute=execute)
+ nbd=FLAGS.use_cow_images)
except Exception as e:
# This could be a windows image, or a vmdk format disk
LOG.warn(_('instance %s: ignoring error injecting data'
' into image %s (%s)'),
inst['name'], inst.image_id, e)
- if inst['kernel_id']:
- if os.path.exists(basepath('disk')):
- utils.execute('rm -f %s' % basepath('disk'))
-
- local_bytes = (instance_types.INSTANCE_TYPES[inst.instance_type]
- ['local_gb']
- * 1024 * 1024 * 1024)
-
- resize = True
- if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-':
- resize = False
-
- if inst['kernel_id']:
- disk.partition(basepath('disk-raw'), basepath('disk'),
- local_bytes, resize, execute=execute)
- else:
- os.rename(basepath('disk-raw'), basepath('disk'))
- disk.extend(basepath('disk'), local_bytes, execute=execute)
-
if FLAGS.libvirt_type == 'uml':
utils.execute('sudo chown root %s' % basepath('disk'))
@@ -612,15 +657,36 @@ class LibvirtConnection(object):
instance['id'])
# Assume that the gateway also acts as the dhcp server.
dhcp_server = network['gateway']
-
+ ra_server = network['ra_server']
+ if not ra_server:
+ ra_server = 'fd00::'
if FLAGS.allow_project_net_traffic:
- net, mask = _get_net_and_mask(network['cidr'])
- extra_params = ("<parameter name=\"PROJNET\" "
+ if FLAGS.use_ipv6:
+ net, mask = _get_net_and_mask(network['cidr'])
+ net_v6, prefixlen_v6 = _get_net_and_prefixlen(
+ network['cidr_v6'])
+ extra_params = ("<parameter name=\"PROJNET\" "
"value=\"%s\" />\n"
"<parameter name=\"PROJMASK\" "
- "value=\"%s\" />\n") % (net, mask)
+ "value=\"%s\" />\n"
+ "<parameter name=\"PROJNETV6\" "
+ "value=\"%s\" />\n"
+ "<parameter name=\"PROJMASKV6\" "
+ "value=\"%s\" />\n") % \
+ (net, mask, net_v6, prefixlen_v6)
+ else:
+ net, mask = _get_net_and_mask(network['cidr'])
+ extra_params = ("<parameter name=\"PROJNET\" "
+ "value=\"%s\" />\n"
+ "<parameter name=\"PROJMASK\" "
+ "value=\"%s\" />\n") % \
+ (net, mask)
else:
extra_params = "\n"
+ if FLAGS.use_cow_images:
+ driver_type = 'qcow2'
+ else:
+ driver_type = 'raw'
xml_info = {'type': FLAGS.libvirt_type,
'name': instance['name'],
@@ -632,8 +698,11 @@ class LibvirtConnection(object):
'mac_address': instance['mac_address'],
'ip_address': ip_address,
'dhcp_server': dhcp_server,
+ 'ra_server': ra_server,
'extra_params': extra_params,
- 'rescue': rescue}
+ 'rescue': rescue,
+ 'local': instance_type['local_gb'],
+ 'driver_type': driver_type}
if not rescue:
if instance['kernel_id']:
xml_info['kernel'] = xml_info['basepath'] + "/kernel"
@@ -787,6 +856,10 @@ class FirewallDriver(object):
At this point, the instance isn't running yet."""
raise NotImplementedError()
+ def unfilter_instance(self, instance):
+ """Stop filtering instance"""
+ raise NotImplementedError()
+
def apply_instance_filter(self, instance):
"""Apply instance filter.
@@ -889,6 +962,15 @@ class NWFilterFirewall(FirewallDriver):
</rule>
</filter>'''
+ def nova_ra_filter(self):
+ return '''<filter name='nova-allow-ra-server' chain='root'>
+ <uuid>d707fa71-4fb5-4b27-9ab7-ba5ca19c8804</uuid>
+ <rule action='accept' direction='inout'
+ priority='100'>
+ <icmpv6 srcipaddr='$RASERVER'/>
+ </rule>
+ </filter>'''
+
def setup_basic_filtering(self, instance):
"""Set up basic filtering (MAC, IP, and ARP spoofing protection)"""
logging.info('called setup_basic_filtering in nwfilter')
@@ -913,13 +995,17 @@ class NWFilterFirewall(FirewallDriver):
['no-mac-spoofing',
'no-ip-spoofing',
'no-arp-spoofing',
- 'allow-dhcp-server']))
+ 'allow-dhcp-server'
+ ]))
self._define_filter(self.nova_base_ipv4_filter)
self._define_filter(self.nova_base_ipv6_filter)
self._define_filter(self.nova_dhcp_filter)
+ self._define_filter(self.nova_ra_filter)
self._define_filter(self.nova_vpn_filter)
if FLAGS.allow_project_net_traffic:
self._define_filter(self.nova_project_filter)
+ if FLAGS.use_ipv6:
+ self._define_filter(self.nova_project_filter_v6)
self.static_filters_configured = True
@@ -951,13 +1037,13 @@ class NWFilterFirewall(FirewallDriver):
def nova_base_ipv6_filter(self):
retval = "<filter name='nova-base-ipv6' chain='ipv6'>"
- for protocol in ['tcp', 'udp', 'icmp']:
+ for protocol in ['tcp-ipv6', 'udp-ipv6', 'icmpv6']:
for direction, action, priority in [('out', 'accept', 399),
('in', 'drop', 400)]:
retval += """<rule action='%s' direction='%s' priority='%d'>
- <%s-ipv6 />
+ <%s />
</rule>""" % (action, direction,
- priority, protocol)
+ priority, protocol)
retval += '</filter>'
return retval
@@ -970,20 +1056,33 @@ class NWFilterFirewall(FirewallDriver):
retval += '</filter>'
return retval
+ def nova_project_filter_v6(self):
+ retval = "<filter name='nova-project-v6' chain='ipv6'>"
+ for protocol in ['tcp-ipv6', 'udp-ipv6', 'icmpv6']:
+ retval += """<rule action='accept' direction='inout'
+ priority='200'>
+ <%s srcipaddr='$PROJNETV6'
+ srcipmask='$PROJMASKV6' />
+ </rule>""" % (protocol)
+ retval += '</filter>'
+ return retval
+
def _define_filter(self, xml):
if callable(xml):
xml = xml()
-
# execute in a native thread and block current greenthread until done
tpool.execute(self._conn.nwfilterDefineXML, xml)
+ def unfilter_instance(self, instance):
+ # Nothing to do
+ pass
+
def prepare_instance_filter(self, instance):
"""
Creates an NWFilter for the given instance. In the process,
it makes sure the filters for the security groups as well as
the base filter are all in place.
"""
-
if instance['image_id'] == FLAGS.vpn_image_id:
base_filter = 'nova-vpn'
else:
@@ -995,11 +1094,15 @@ class NWFilterFirewall(FirewallDriver):
instance_secgroup_filter_children = ['nova-base-ipv4',
'nova-base-ipv6',
'nova-allow-dhcp-server']
+ if FLAGS.use_ipv6:
+ instance_secgroup_filter_children += ['nova-allow-ra-server']
ctxt = context.get_admin_context()
if FLAGS.allow_project_net_traffic:
instance_filter_children += ['nova-project']
+ if FLAGS.use_ipv6:
+ instance_filter_children += ['nova-project-v6']
for security_group in db.security_group_get_by_instance(ctxt,
instance['id']):
@@ -1027,12 +1130,19 @@ class NWFilterFirewall(FirewallDriver):
security_group = db.security_group_get(context.get_admin_context(),
security_group_id)
rule_xml = ""
+ v6protocol = {'tcp': 'tcp-ipv6', 'udp': 'udp-ipv6', 'icmp': 'icmpv6'}
for rule in security_group.rules:
rule_xml += "<rule action='accept' direction='in' priority='300'>"
if rule.cidr:
- net, mask = _get_net_and_mask(rule.cidr)
- rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
- (rule.protocol, net, mask)
+ version = _get_ip_version(rule.cidr)
+ if(FLAGS.use_ipv6 and version == 6):
+ net, prefixlen = _get_net_and_prefixlen(rule.cidr)
+ rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
+ (v6protocol[rule.protocol], net, prefixlen)
+ else:
+ net, mask = _get_net_and_mask(rule.cidr)
+ rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
+ (rule.protocol, net, mask)
if rule.protocol in ['tcp', 'udp']:
rule_xml += "dstportstart='%s' dstportend='%s' " % \
(rule.from_port, rule.to_port)
@@ -1047,8 +1157,11 @@ class NWFilterFirewall(FirewallDriver):
rule_xml += '/>\n'
rule_xml += "</rule>\n"
- xml = "<filter name='nova-secgroup-%s' chain='ipv4'>%s</filter>" % \
- (security_group_id, rule_xml,)
+ xml = "<filter name='nova-secgroup-%s' " % security_group_id
+ if(FLAGS.use_ipv6):
+ xml += "chain='root'>%s</filter>" % rule_xml
+ else:
+ xml += "chain='ipv4'>%s</filter>" % rule_xml
return xml
def _instance_filter_name(self, instance):
@@ -1058,17 +1171,25 @@ class NWFilterFirewall(FirewallDriver):
class IptablesFirewallDriver(FirewallDriver):
def __init__(self, execute=None):
self.execute = execute or utils.execute
- self.instances = set()
+ self.instances = {}
def apply_instance_filter(self, instance):
"""No-op. Everything is done in prepare_instance_filter"""
pass
def remove_instance(self, instance):
- self.instances.remove(instance)
+ if instance['id'] in self.instances:
+ del self.instances[instance['id']]
+ else:
+ LOG.info(_('Attempted to unfilter instance %s which is not '
+ 'filtered'), instance['id'])
def add_instance(self, instance):
- self.instances.add(instance)
+ self.instances[instance['id']] = instance
+
+ def unfilter_instance(self, instance):
+ self.remove_instance(instance)
+ self.apply_ruleset()
def prepare_instance_filter(self, instance):
self.add_instance(instance)
@@ -1077,11 +1198,17 @@ class IptablesFirewallDriver(FirewallDriver):
def apply_ruleset(self):
current_filter, _ = self.execute('sudo iptables-save -t filter')
current_lines = current_filter.split('\n')
- new_filter = self.modify_rules(current_lines)
+ new_filter = self.modify_rules(current_lines, 4)
self.execute('sudo iptables-restore',
process_input='\n'.join(new_filter))
-
- def modify_rules(self, current_lines):
+ if(FLAGS.use_ipv6):
+ current_filter, _ = self.execute('sudo ip6tables-save -t filter')
+ current_lines = current_filter.split('\n')
+ new_filter = self.modify_rules(current_lines, 6)
+ self.execute('sudo ip6tables-restore',
+ process_input='\n'.join(new_filter))
+
+ def modify_rules(self, current_lines, ip_version=4):
ctxt = context.get_admin_context()
# Remove any trace of nova rules.
new_filter = filter(lambda l: 'nova-' not in l, current_lines)
@@ -1095,18 +1222,22 @@ class IptablesFirewallDriver(FirewallDriver):
if not new_filter[rules_index].startswith(':'):
break
- our_chains = [':nova-ipv4-fallback - [0:0]']
- our_rules = ['-A nova-ipv4-fallback -j DROP']
+ our_chains = [':nova-fallback - [0:0]']
+ our_rules = ['-A nova-fallback -j DROP']
our_chains += [':nova-local - [0:0]']
our_rules += ['-A FORWARD -j nova-local']
- security_groups = set()
+ security_groups = {}
# Add our chains
# First, we add instance chains and rules
- for instance in self.instances:
+ for instance_id in self.instances:
+ instance = self.instances[instance_id]
chain_name = self._instance_chain_name(instance)
- ip_address = self._ip_for_instance(instance)
+ if(ip_version == 4):
+ ip_address = self._ip_for_instance(instance)
+ elif(ip_version == 6):
+ ip_address = self._ip_for_instance_v6(instance)
our_chains += [':%s - [0:0]' % chain_name]
@@ -1126,40 +1257,54 @@ class IptablesFirewallDriver(FirewallDriver):
for security_group in \
db.security_group_get_by_instance(ctxt,
instance['id']):
- security_groups.add(security_group)
+ security_groups[security_group['id']] = security_group
- sg_chain_name = self._security_group_chain_name(security_group)
+ sg_chain_name = self._security_group_chain_name(
+ security_group['id'])
our_rules += ['-A %s -j %s' % (chain_name, sg_chain_name)]
- # Allow DHCP responses
- dhcp_server = self._dhcp_server_for_instance(instance)
- our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68' %
- (chain_name, dhcp_server)]
+ if(ip_version == 4):
+ # Allow DHCP responses
+ dhcp_server = self._dhcp_server_for_instance(instance)
+ our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68' %
+ (chain_name, dhcp_server)]
+ elif(ip_version == 6):
+ # Allow RA responses
+ ra_server = self._ra_server_for_instance(instance)
+ our_rules += ['-A %s -s %s -p icmpv6' %
+ (chain_name, ra_server)]
# If nothing matches, jump to the fallback chain
- our_rules += ['-A %s -j nova-ipv4-fallback' % (chain_name,)]
+ our_rules += ['-A %s -j nova-fallback' % (chain_name,)]
# then, security group chains and rules
- for security_group in security_groups:
- chain_name = self._security_group_chain_name(security_group)
+ for security_group_id in security_groups:
+ chain_name = self._security_group_chain_name(security_group_id)
our_chains += [':%s - [0:0]' % chain_name]
rules = \
db.security_group_rule_get_by_security_group(ctxt,
- security_group['id'])
+ security_group_id)
for rule in rules:
logging.info('%r', rule)
- args = ['-A', chain_name, '-p', rule.protocol]
- if rule.cidr:
- args += ['-s', rule.cidr]
- else:
+ if not rule.cidr:
# Eventually, a mechanism to grant access for security
# groups will turn up here. It'll use ipsets.
continue
+ version = _get_ip_version(rule.cidr)
+ if version != ip_version:
+ continue
+
+ protocol = rule.protocol
+ if version == 6 and rule.protocol == 'icmp':
+ protocol = 'icmpv6'
+
+ args = ['-A', chain_name, '-p', protocol, '-s', rule.cidr]
+
if rule.protocol in ['udp', 'tcp']:
if rule.from_port == rule.to_port:
args += ['--dport', '%s' % (rule.from_port,)]
@@ -1179,7 +1324,12 @@ class IptablesFirewallDriver(FirewallDriver):
icmp_type_arg += '/%s' % icmp_code
if icmp_type_arg:
- args += ['-m', 'icmp', '--icmp-type', icmp_type_arg]
+ if(ip_version == 4):
+ args += ['-m', 'icmp', '--icmp-type',
+ icmp_type_arg]
+ elif(ip_version == 6):
+ args += ['-m', 'icmp6', '--icmpv6-type',
+ icmp_type_arg]
args += ['-j ACCEPT']
our_rules += [' '.join(args)]
@@ -1195,8 +1345,8 @@ class IptablesFirewallDriver(FirewallDriver):
def refresh_security_group_rules(self, security_group):
self.apply_ruleset()
- def _security_group_chain_name(self, security_group):
- return 'nova-sg-%s' % (security_group['id'],)
+ def _security_group_chain_name(self, security_group_id):
+ return 'nova-sg-%s' % (security_group_id,)
def _instance_chain_name(self, instance):
return 'nova-inst-%s' % (instance['id'],)
@@ -1205,7 +1355,16 @@ class IptablesFirewallDriver(FirewallDriver):
return db.instance_get_fixed_address(context.get_admin_context(),
instance['id'])
+ def _ip_for_instance_v6(self, instance):
+ return db.instance_get_fixed_address_v6(context.get_admin_context(),
+ instance['id'])
+
def _dhcp_server_for_instance(self, instance):
network = db.project_get_network(context.get_admin_context(),
instance['project_id'])
return network['gateway']
+
+ def _ra_server_for_instance(self, instance):
+ network = db.project_get_network(context.get_admin_context(),
+ instance['project_id'])
+ return network['ra_server']
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 7aebb502f..6e359ef82 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -20,6 +20,11 @@ Management class for VM-related functions (spawn, reboot, etc).
"""
import json
+import M2Crypto
+import os
+import subprocess
+import tempfile
+import uuid
from nova import db
from nova import context
@@ -127,12 +132,31 @@ class VMOps(object):
"""Refactored out the common code of many methods that receive either
a vm name or a vm instance, and want a vm instance in return.
"""
+ vm = None
try:
- instance_name = instance_or_vm.name
- vm = VMHelper.lookup(self._session, instance_name)
- except AttributeError:
- # A vm opaque ref was passed
- vm = instance_or_vm
+ if instance_or_vm.startswith("OpaqueRef:"):
+ # Got passed an opaque ref; return it
+ return instance_or_vm
+ else:
+ # Must be the instance name
+ instance_name = instance_or_vm
+ except (AttributeError, KeyError):
+ # Note the the KeyError will only happen with fakes.py
+ # Not a string; must be an ID or a vm instance
+ if isinstance(instance_or_vm, (int, long)):
+ ctx = context.get_admin_context()
+ try:
+ instance_obj = db.instance_get_by_id(ctx, instance_or_vm)
+ instance_name = instance_obj.name
+ except exception.NotFound:
+ # The unit tests screw this up, as they use an integer for
+ # the vm name. I'd fix that up, but that's a matter for
+ # another bug report. So for now, just try with the passed
+ # value
+ instance_name = instance_or_vm
+ else:
+ instance_name = instance_or_vm.name
+ vm = VMHelper.lookup(self._session, instance_name)
if vm is None:
raise Exception(_('Instance not present %s') % instance_name)
return vm
@@ -189,6 +213,44 @@ class VMOps(object):
task = self._session.call_xenapi('Async.VM.clean_reboot', vm)
self._session.wait_for_task(instance.id, task)
+ def set_admin_password(self, instance, new_pass):
+ """Set the root/admin password on the VM instance. This is done via
+ an agent running on the VM. Communication between nova and the agent
+ is done via writing xenstore records. Since communication is done over
+ the XenAPI RPC calls, we need to encrypt the password. We're using a
+ simple Diffie-Hellman class instead of the more advanced one in
+ M2Crypto for compatibility with the agent code.
+ """
+ # Need to uniquely identify this request.
+ transaction_id = str(uuid.uuid4())
+ # The simple Diffie-Hellman class is used to manage key exchange.
+ dh = SimpleDH()
+ args = {'id': transaction_id, 'pub': str(dh.get_public())}
+ resp = self._make_agent_call('key_init', instance, '', args)
+ if resp is None:
+ # No response from the agent
+ return
+ resp_dict = json.loads(resp)
+ # Successful return code from key_init is 'D0'
+ if resp_dict['returncode'] != 'D0':
+ # 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'])
+ dh.compute_shared(agent_pub)
+ enc_pass = dh.encrypt(new_pass)
+ # Send the encrypted password
+ args['enc_pass'] = enc_pass
+ resp = self._make_agent_call('password', instance, '', args)
+ if resp is None:
+ # No response from the agent
+ return
+ resp_dict = json.loads(resp)
+ # Successful return code from password is '0'
+ if resp_dict['returncode'] != '0':
+ raise RuntimeError(resp_dict['message'])
+ return resp_dict['message']
+
def destroy(self, instance):
"""Destroy VM instance"""
vm = VMHelper.lookup(self._session, instance.name)
@@ -246,30 +308,19 @@ class VMOps(object):
def suspend(self, instance, callback):
"""suspend the specified instance"""
- instance_name = instance.name
- vm = VMHelper.lookup(self._session, instance_name)
- if vm is None:
- raise Exception(_("suspend: instance not present %s") %
- instance_name)
+ vm = self._get_vm_opaque_ref(instance)
task = self._session.call_xenapi('Async.VM.suspend', vm)
self._wait_with_callback(instance.id, task, callback)
def resume(self, instance, callback):
"""resume the specified instance"""
- instance_name = instance.name
- vm = VMHelper.lookup(self._session, instance_name)
- if vm is None:
- raise Exception(_("resume: instance not present %s") %
- instance_name)
+ vm = self._get_vm_opaque_ref(instance)
task = self._session.call_xenapi('Async.VM.resume', vm, False, True)
self._wait_with_callback(instance.id, task, callback)
- def get_info(self, instance_id):
+ def get_info(self, instance):
"""Return data about VM instance"""
- vm = VMHelper.lookup(self._session, instance_id)
- if vm is None:
- raise exception.NotFound(_('Instance not'
- ' found %s') % instance_id)
+ vm = self._get_vm_opaque_ref(instance)
rec = self._session.get_xenapi().VM.get_record(vm)
return VMHelper.compile_info(rec)
@@ -333,22 +384,34 @@ class VMOps(object):
return self._make_plugin_call('xenstore.py', method=method, vm=vm,
path=path, addl_args=addl_args)
+ def _make_agent_call(self, method, vm, path, addl_args={}):
+ """Abstracts out the interaction with the agent xenapi plugin."""
+ return self._make_plugin_call('agent', method=method, vm=vm,
+ path=path, addl_args=addl_args)
+
def _make_plugin_call(self, plugin, method, vm, path, addl_args={}):
"""Abstracts out the process of calling a method of a xenapi plugin.
Any errors raised by the plugin will in turn raise a RuntimeError here.
"""
+ instance_id = vm.id
vm = self._get_vm_opaque_ref(vm)
rec = self._session.get_xenapi().VM.get_record(vm)
args = {'dom_id': rec['domid'], 'path': path}
args.update(addl_args)
- # If the 'testing_mode' attribute is set, add that to the args.
- if getattr(self, 'testing_mode', False):
- args['testing_mode'] = 'true'
try:
task = self._session.async_call_plugin(plugin, method, args)
- ret = self._session.wait_for_task(0, task)
+ ret = self._session.wait_for_task(instance_id, task)
except self.XenAPI.Failure, e:
- raise RuntimeError("%s" % e.details[-1])
+ ret = None
+ err_trace = e.details[-1]
+ err_msg = err_trace.splitlines()[-1]
+ strargs = str(args)
+ if 'TIMEOUT:' in err_msg:
+ LOG.error(_('TIMEOUT: The call to %(method)s timed out. '
+ 'VM id=%(instance_id)s; args=%(strargs)s') % locals())
+ else:
+ LOG.error(_('The call to %(method)s returned an error: %(e)s. '
+ 'VM id=%(instance_id)s; args=%(strargs)s') % locals())
return ret
def add_to_xenstore(self, vm, path, key, value):
@@ -460,3 +523,89 @@ class VMOps(object):
"""Removes all data from the xenstore parameter record for this VM."""
self.write_to_param_xenstore(instance_or_vm, {})
########################################################################
+
+
+def _runproc(cmd):
+ pipe = subprocess.PIPE
+ return subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe,
+ stderr=pipe, close_fds=True)
+
+
+class SimpleDH(object):
+ """This class wraps all the functionality needed to implement
+ basic Diffie-Hellman-Merkle key exchange in Python. It features
+ intelligent defaults for the prime and base numbers needed for the
+ calculation, while allowing you to supply your own. It requires that
+ the openssl binary be installed on the system on which this is run,
+ as it uses that to handle the encryption and decryption. If openssl
+ is not available, a RuntimeError will be raised.
+ """
+ def __init__(self, prime=None, base=None, secret=None):
+ """You can specify the values for prime and base if you wish;
+ otherwise, reasonable default values will be used.
+ """
+ if prime is None:
+ self._prime = 162259276829213363391578010288127
+ else:
+ self._prime = prime
+ if base is None:
+ self._base = 5
+ else:
+ self._base = base
+ self._shared = self._public = None
+
+ self._dh = M2Crypto.DH.set_params(
+ self.dec_to_mpi(self._prime),
+ self.dec_to_mpi(self._base))
+ self._dh.gen_key()
+ self._public = self.mpi_to_dec(self._dh.pub)
+
+ def get_public(self):
+ return self._public
+
+ def compute_shared(self, other):
+ self._shared = self.bin_to_dec(
+ self._dh.compute_key(self.dec_to_mpi(other)))
+ return self._shared
+
+ def mpi_to_dec(self, mpi):
+ bn = M2Crypto.m2.mpi_to_bn(mpi)
+ hexval = M2Crypto.m2.bn_to_hex(bn)
+ dec = int(hexval, 16)
+ return dec
+
+ def bin_to_dec(self, binval):
+ bn = M2Crypto.m2.bin_to_bn(binval)
+ hexval = M2Crypto.m2.bn_to_hex(bn)
+ dec = int(hexval, 16)
+ return dec
+
+ def dec_to_mpi(self, dec):
+ bn = M2Crypto.m2.dec_to_bn('%s' % dec)
+ mpi = M2Crypto.m2.bn_to_mpi(bn)
+ return mpi
+
+ def _run_ssl(self, text, which):
+ base_cmd = ('cat %(tmpfile)s | openssl enc -aes-128-cbc '
+ '-a -pass pass:%(shared)s -nosalt %(dec_flag)s')
+ if which.lower()[0] == 'd':
+ dec_flag = ' -d'
+ else:
+ dec_flag = ''
+ fd, tmpfile = tempfile.mkstemp()
+ os.close(fd)
+ file(tmpfile, 'w').write(text)
+ shared = self._shared
+ cmd = base_cmd % locals()
+ proc = _runproc(cmd)
+ proc.wait()
+ err = proc.stderr.read()
+ if err:
+ raise RuntimeError(_('OpenSSL error: %s') % err)
+ return proc.stdout.read()
+
+ def encrypt(self, text):
+ return self._run_ssl(text, 'enc')
+
+ def decrypt(self, text):
+ return self._run_ssl(text, 'dec')
diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py
index 45d0738a5..689844f34 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -149,6 +149,10 @@ class XenAPIConnection(object):
"""Reboot VM instance"""
self._vmops.reboot(instance)
+ def set_admin_password(self, instance, new_pass):
+ """Set the root/admin password on the VM instance"""
+ self._vmops.set_admin_password(instance, new_pass)
+
def destroy(self, instance):
"""Destroy VM instance"""
self._vmops.destroy(instance)
@@ -266,7 +270,8 @@ class XenAPISession(object):
def _poll_task(self, id, task, done):
"""Poll the given XenAPI task, and fire the given action if we
- get a result."""
+ get a result.
+ """
try:
name = self._session.xenapi.task.get_name_label(task)
status = self._session.xenapi.task.get_status(task)
diff --git a/nova/volume/driver.py b/nova/volume/driver.py
index 44bfeaf0c..71fe18a40 100644
--- a/nova/volume/driver.py
+++ b/nova/volume/driver.py
@@ -371,3 +371,52 @@ class RBDDriver(VolumeDriver):
def undiscover_volume(self, volume):
"""Undiscover volume on a remote host"""
pass
+
+
+class SheepdogDriver(VolumeDriver):
+ """Executes commands relating to Sheepdog Volumes"""
+
+ def check_for_setup_error(self):
+ """Returns an error if prerequisites aren't met"""
+ try:
+ (out, err) = self._execute("collie cluster info")
+ if not out.startswith('running'):
+ raise exception.Error(_("Sheepdog is not working: %s") % out)
+ except exception.ProcessExecutionError:
+ raise exception.Error(_("Sheepdog is not working"))
+
+ def create_volume(self, volume):
+ """Creates a sheepdog volume"""
+ if int(volume['size']) == 0:
+ sizestr = '100M'
+ else:
+ sizestr = '%sG' % volume['size']
+ self._try_execute("qemu-img create sheepdog:%s %s" %
+ (volume['name'], sizestr))
+
+ def delete_volume(self, volume):
+ """Deletes a logical volume"""
+ self._try_execute("collie vdi delete %s" % volume['name'])
+
+ def local_path(self, volume):
+ return "sheepdog:%s" % volume['name']
+
+ def ensure_export(self, context, volume):
+ """Safely and synchronously recreates an export for a logical volume"""
+ pass
+
+ def create_export(self, context, volume):
+ """Exports the volume"""
+ pass
+
+ def remove_export(self, context, volume):
+ """Removes an export for a logical volume"""
+ pass
+
+ def discover_volume(self, volume):
+ """Discover volume on a remote host"""
+ return "sheepdog:%s" % volume['name']
+
+ def undiscover_volume(self, volume):
+ """Undiscover volume on a remote host"""
+ pass