Merge trunk
This commit is contained in:
@@ -83,9 +83,10 @@ if [ "$CMD" == "install" ]; then
|
||||
sudo /etc/init.d/iscsitarget restart
|
||||
sudo modprobe kvm
|
||||
sudo /etc/init.d/libvirt-bin restart
|
||||
sudo modprobe nbd
|
||||
sudo apt-get install -y python-twisted python-sqlalchemy python-mox python-greenlet python-carrot
|
||||
sudo apt-get install -y python-daemon python-eventlet python-gflags python-tornado python-ipy
|
||||
sudo apt-get install -y python-libvirt python-libxml2 python-routes
|
||||
sudo apt-get install -y python-daemon python-eventlet python-gflags python-ipy
|
||||
sudo apt-get install -y python-libvirt python-libxml2 python-routes python-cheetah
|
||||
if [ "$USE_MYSQL" == 1 ]; then
|
||||
cat <<MYSQL_PRESEED | debconf-set-selections
|
||||
mysql-server-5.1 mysql-server/root_password password $MYSQL_PASS
|
||||
|
||||
@@ -73,17 +73,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):
|
||||
@@ -541,6 +537,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
|
||||
@@ -556,7 +554,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']
|
||||
@@ -574,7 +572,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'] = [{}]
|
||||
|
||||
@@ -593,10 +592,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)
|
||||
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:
|
||||
@@ -607,18 +608,21 @@ 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 instacne %s at %s"), volume_id,
|
||||
instance_id, device, context=context)
|
||||
self.compute_api.attach_volume(context, instance_id, volume_id, device)
|
||||
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)
|
||||
@@ -627,7 +631,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 == []:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
@@ -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'],
|
||||
|
||||
@@ -169,7 +169,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))
|
||||
@@ -256,7 +256,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))
|
||||
|
||||
@@ -126,10 +126,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'])
|
||||
|
||||
@@ -385,7 +388,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'])
|
||||
@@ -393,8 +397,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'])
|
||||
|
||||
186
nova/virt/disk.py
Normal file
186
nova/virt/disk.py
Normal file
@@ -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)
|
||||
@@ -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}'/>
|
||||
|
||||
@@ -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')
|
||||
@@ -197,40 +200,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'])
|
||||
@@ -491,19 +483,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 +550,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)
|
||||
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)
|
||||
|
||||
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)
|
||||
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
|
||||
@@ -566,34 +617,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'))
|
||||
|
||||
@@ -621,6 +653,10 @@ class LibvirtConnection(object):
|
||||
"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'],
|
||||
@@ -633,7 +669,9 @@ class LibvirtConnection(object):
|
||||
'ip_address': ip_address,
|
||||
'dhcp_server': dhcp_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 +825,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.
|
||||
|
||||
@@ -977,6 +1019,10 @@ class NWFilterFirewall(FirewallDriver):
|
||||
# execute in a native thread and block current greenthread until done
|
||||
tpool.execute(self._conn.nwfilterDefineXML, xml)
|
||||
|
||||
def unfilter_instance(self, instance):
|
||||
# Nothing to do
|
||||
pass
|
||||
|
||||
def prepare_instance_filter(self, instance):
|
||||
"""
|
||||
Creates an NWFilter for the given instance. In the process,
|
||||
@@ -1058,17 +1104,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)
|
||||
@@ -1101,10 +1155,11 @@ class IptablesFirewallDriver(FirewallDriver):
|
||||
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)
|
||||
|
||||
@@ -1126,9 +1181,10 @@ 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)]
|
||||
|
||||
@@ -1141,13 +1197,13 @@ class IptablesFirewallDriver(FirewallDriver):
|
||||
our_rules += ['-A %s -j nova-ipv4-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)
|
||||
@@ -1195,8 +1251,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'],)
|
||||
|
||||
Reference in New Issue
Block a user