initial commit of openvz driver broken out from nova code base
This commit is contained in:
parent
7480cf1365
commit
9e67e5a0fc
3
.gitignore
vendored
3
.gitignore
vendored
@ -33,3 +33,6 @@ nosetests.xml
|
||||
.mr.developer.cfg
|
||||
.project
|
||||
.pydevproject
|
||||
|
||||
# Pycharm
|
||||
.idea
|
||||
|
261
etc/nova/rootwrap.d/openvz.filters
Normal file
261
etc/nova/rootwrap.d/openvz.filters
Normal file
@ -0,0 +1,261 @@
|
||||
# nova-rootwrap command filters for compute nodes
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
[Filters]
|
||||
# nova/virt/disk/mount/api.py: 'kpartx', '-a', device
|
||||
# nova/virt/disk/mount/api.py: 'kpartx', '-d', device
|
||||
kpartx: CommandFilter, /sbin/kpartx, root
|
||||
|
||||
# nova/virt/xenapi/vm_utils.py: tune2fs, -O ^has_journal, part_path
|
||||
# nova/virt/xenapi/vm_utils.py: tune2fs, -j, partition_path
|
||||
tune2fs: CommandFilter, /sbin/tune2fs, root
|
||||
|
||||
# nova/virt/disk/mount/api.py: 'mount', mapped_device
|
||||
# nova/virt/disk/api.py: 'mount', '-o', 'bind', src, target
|
||||
# nova/virt/xenapi/vm_utils.py: 'mount', '-t', 'ext2,ext3,ext4,reiserfs'..
|
||||
# nova/virt/configdrive.py: 'mount', device, mountdir
|
||||
# nova/virt/libvirt/volume.py: 'mount', '-t', 'sofs' ...
|
||||
# nova/virt/openvz/utils.py: 'mount', '-o', 'defaults' ...
|
||||
mount: CommandFilter, /bin/mount, root
|
||||
|
||||
# nova/virt/disk/mount/api.py: 'umount', mapped_device
|
||||
# nova/virt/disk/api.py: 'umount' target
|
||||
# nova/virt/xenapi/vm_utils.py: 'umount', dev_path
|
||||
# nova/virt/configdrive.py: 'umount', mountdir
|
||||
# nova/virt/openvz/utils.py: 'umount'
|
||||
umount: CommandFilter, /bin/umount, root
|
||||
|
||||
# nova/virt/disk/mount/nbd.py: 'qemu-nbd', '-c', device, image
|
||||
# nova/virt/disk/mount/nbd.py: 'qemu-nbd', '-d', device
|
||||
qemu-nbd: CommandFilter, /usr/bin/qemu-nbd, root
|
||||
|
||||
# nova/virt/disk/mount/loop.py: 'losetup', '--find', '--show', image
|
||||
# nova/virt/disk/mount/loop.py: 'losetup', '--detach', device
|
||||
losetup: CommandFilter, /sbin/losetup, root
|
||||
|
||||
# nova/virt/disk/vfs/localfs.py: 'tee', canonpath
|
||||
tee: CommandFilter, /usr/bin/tee, root
|
||||
|
||||
# nova/virt/disk/vfs/localfs.py: 'mkdir', canonpath
|
||||
mkdir: CommandFilter, /bin/mkdir, root
|
||||
|
||||
# nova/virt/disk/vfs/localfs.py: 'chown'
|
||||
# nova/virt/libvirt/connection.py: 'chown', os.getuid( console_log
|
||||
# nova/virt/libvirt/connection.py: 'chown', os.getuid( console_log
|
||||
# nova/virt/libvirt/connection.py: 'chown', 'root', basepath('disk')
|
||||
# nova/utils.py: 'chown', owner_uid, path
|
||||
chown: CommandFilter, /bin/chown, root
|
||||
|
||||
# nova/virt/disk/vfs/localfs.py: 'chmod'
|
||||
chmod: CommandFilter, /bin/chmod, root
|
||||
|
||||
# nova/virt/libvirt/vif.py: 'ip', 'tuntap', 'add', dev, 'mode', 'tap'
|
||||
# nova/virt/libvirt/vif.py: 'ip', 'link', 'set', dev, 'up'
|
||||
# nova/virt/libvirt/vif.py: 'ip', 'link', 'delete', dev
|
||||
# nova/network/linux_net.py: 'ip', 'addr', 'add', str(floating_ip)+'/32'i..
|
||||
# nova/network/linux_net.py: 'ip', 'addr', 'del', str(floating_ip)+'/32'..
|
||||
# nova/network/linux_net.py: 'ip', 'addr', 'add', '169.254.169.254/32',..
|
||||
# nova/network/linux_net.py: 'ip', 'addr', 'show', 'dev', dev, 'scope',..
|
||||
# nova/network/linux_net.py: 'ip', 'addr', 'del/add', ip_params, dev)
|
||||
# nova/network/linux_net.py: 'ip', 'addr', 'del', params, fields[-1]
|
||||
# nova/network/linux_net.py: 'ip', 'addr', 'add', params, bridge
|
||||
# nova/network/linux_net.py: 'ip', '-f', 'inet6', 'addr', 'change', ..
|
||||
# nova/network/linux_net.py: 'ip', 'link', 'set', 'dev', dev, 'promisc',..
|
||||
# nova/network/linux_net.py: 'ip', 'link', 'add', 'link', bridge_if ...
|
||||
# nova/network/linux_net.py: 'ip', 'link', 'set', interface, address,..
|
||||
# nova/network/linux_net.py: 'ip', 'link', 'set', interface, 'up'
|
||||
# nova/network/linux_net.py: 'ip', 'link', 'set', bridge, 'up'
|
||||
# nova/network/linux_net.py: 'ip', 'addr', 'show', 'dev', interface, ..
|
||||
# nova/network/linux_net.py: 'ip', 'link', 'set', dev, address, ..
|
||||
# nova/network/linux_net.py: 'ip', 'link', 'set', dev, 'up'
|
||||
ip: CommandFilter, /sbin/ip, root
|
||||
|
||||
# nova/virt/libvirt/vif.py: 'tunctl', '-b', '-t', dev
|
||||
# nova/network/linux_net.py: 'tunctl', '-b', '-t', dev
|
||||
tunctl: CommandFilter, tunctl, root
|
||||
|
||||
# nova/virt/libvirt/vif.py: 'ovs-vsctl', ...
|
||||
# nova/virt/libvirt/vif.py: 'ovs-vsctl', 'del-port', ...
|
||||
# nova/network/linux_net.py: 'ovs-vsctl', ....
|
||||
ovs-vsctl: CommandFilter, /usr/bin/ovs-vsctl, root
|
||||
|
||||
# nova/network/linux_net.py: 'ovs-ofctl', ....
|
||||
ovs-ofctl: CommandFilter, /usr/bin/ovs-ofctl, root
|
||||
|
||||
# nova/virt/libvirt/connection.py: 'dd', if=%s % virsh_output, ...
|
||||
dd: CommandFilter, /bin/dd, root
|
||||
|
||||
# nova/virt/xenapi/volume_utils.py: 'iscsiadm', '-m', ...
|
||||
iscsiadm: CommandFilter, iscsiadm, root
|
||||
|
||||
# nova/virt/libvirt/volume.py: 'aoe-revalidate', aoedev
|
||||
# nova/virt/libvirt/volume.py: 'aoe-discover'
|
||||
aoe-revalidate: CommandFilter, /usr/sbin/aoe-revalidate, root
|
||||
aoe-discover: CommandFilter, /usr/sbin/aoe-discover, root
|
||||
|
||||
# nova/virt/xenapi/vm_utils.py: parted, --script, ...
|
||||
# nova/virt/xenapi/vm_utils.py: 'parted', '--script', dev_path, ..*.
|
||||
parted: CommandFilter, parted, root
|
||||
|
||||
# nova/virt/xenapi/vm_utils.py: 'pygrub', '-qn', dev_path
|
||||
pygrub: CommandFilter, /usr/bin/pygrub, root
|
||||
|
||||
# nova/virt/xenapi/vm_utils.py: fdisk %(dev_path)s
|
||||
# nova/virt/openvz/volume.py: fdisk %(dev_path)s
|
||||
fdisk: CommandFilter, /sbin/fdisk, root
|
||||
|
||||
# nova/virt/xenapi/vm_utils.py: e2fsck, -f, -p, partition_path
|
||||
# nova/virt/disk/api.py: e2fsck, -f, -p, image
|
||||
e2fsck: CommandFilter, /sbin/e2fsck, root
|
||||
|
||||
# nova/virt/xenapi/vm_utils.py: resize2fs, partition_path
|
||||
# nova/virt/disk/api.py: resize2fs, image
|
||||
resize2fs: CommandFilter, /sbin/resize2fs, root
|
||||
|
||||
# nova/network/linux_net.py: 'ip[6]tables-save' % (cmd, '-t', ...
|
||||
iptables-save: CommandFilter, iptables-save, root
|
||||
ip6tables-save: CommandFilter, ip6tables-save, root
|
||||
|
||||
# nova/network/linux_net.py: 'ip[6]tables-restore' % (cmd,)
|
||||
iptables-restore: CommandFilter, iptables-restore, root
|
||||
ip6tables-restore: CommandFilter, ip6tables-restore, root
|
||||
|
||||
# nova/network/linux_net.py: 'arping', '-U', floating_ip, '-A', '-I', ...
|
||||
# nova/network/linux_net.py: 'arping', '-U', network_ref['dhcp_server'],..
|
||||
arping: CommandFilter, arping, root
|
||||
|
||||
# nova/network/linux_net.py: 'route', '-n'
|
||||
# nova/network/linux_net.py: 'route', 'del', 'default', 'gw'
|
||||
# nova/network/linux_net.py: 'route', 'add', 'default', 'gw'
|
||||
# nova/network/linux_net.py: 'route', '-n'
|
||||
# nova/network/linux_net.py: 'route', 'del', 'default', 'gw', old_gw, ..
|
||||
# nova/network/linux_net.py: 'route', 'add', 'default', 'gw', old_gateway
|
||||
route: CommandFilter, /sbin/route, root
|
||||
|
||||
# nova/network/linux_net.py: 'dhcp_release', dev, address, mac_address
|
||||
dhcp_release: CommandFilter, /usr/bin/dhcp_release, root
|
||||
|
||||
# nova/network/linux_net.py: 'kill', '-9', pid
|
||||
# nova/network/linux_net.py: 'kill', '-HUP', pid
|
||||
kill_dnsmasq: KillFilter, root, /usr/sbin/dnsmasq, -9, -HUP
|
||||
|
||||
# nova/network/linux_net.py: 'kill', pid
|
||||
kill_radvd: KillFilter, root, /usr/sbin/radvd
|
||||
|
||||
# nova/network/linux_net.py: dnsmasq call
|
||||
dnsmasq: DnsmasqFilter, /usr/sbin/dnsmasq, root
|
||||
dnsmasq_deprecated: DeprecatedDnsmasqFilter, /usr/sbin/dnsmasq, root
|
||||
|
||||
# nova/network/linux_net.py: 'radvd', '-C', '%s' % _ra_file(dev, 'conf'..
|
||||
radvd: CommandFilter, /usr/sbin/radvd, root
|
||||
|
||||
# nova/network/linux_net.py: 'brctl', 'addbr', bridge
|
||||
# nova/network/linux_net.py: 'brctl', 'setfd', bridge, 0
|
||||
# nova/network/linux_net.py: 'brctl', 'stp', bridge, 'off'
|
||||
# nova/network/linux_net.py: 'brctl', 'addif', bridge, interface
|
||||
brctl: CommandFilter, brctl, root
|
||||
|
||||
# nova/virt/libvirt/utils.py: 'mkswap'
|
||||
# nova/virt/xenapi/vm_utils.py: 'mkswap'
|
||||
mkswap: CommandFilter, /sbin/mkswap, root
|
||||
|
||||
# nova/virt/xenapi/vm_utils.py: 'mkfs'
|
||||
mkfs: CommandFilter, /sbin/mkfs, root
|
||||
|
||||
# nova/virt/libvirt/utils.py: 'qemu-img'
|
||||
qemu-img: CommandFilter, /usr/bin/qemu-img, root
|
||||
|
||||
# nova/virt/disk/vfs/localfs.py: 'readlink', '-e'
|
||||
readlink: CommandFilter, readlink, root
|
||||
|
||||
# nova/virt/disk/api.py: 'touch', target
|
||||
touch: CommandFilter, /usr/bin/touch, root
|
||||
|
||||
# nova/virt/disk/api.py:
|
||||
mkfs.ext3: CommandFilter, /sbin/mkfs.ext3, root
|
||||
mkfs.ntfs: CommandFilter, /sbin/mkfs.ntfs, root
|
||||
|
||||
# nova/virt/libvirt/connection.py:
|
||||
read_initiator: ReadFileFilter, /etc/iscsi/initiatorname.iscsi
|
||||
|
||||
# nova/virt/libvirt/connection.py:
|
||||
lvremove: CommandFilter, /sbin/lvremove, root
|
||||
|
||||
# nova/virt/libvirt/utils.py:
|
||||
lvcreate: CommandFilter, /sbin/lvcreate, root
|
||||
|
||||
# nova/virt/libvirt/utils.py:
|
||||
lvs: CommandFilter, /sbin/lvs, root
|
||||
|
||||
# nova/virt/libvirt/utils.py:
|
||||
vgs: CommandFilter, /sbin/vgs, root
|
||||
|
||||
# nova/virt/baremetal/volume_driver.py: 'tgtadm', '--lld', 'iscsi', ...
|
||||
tgtadm: CommandFilter, /usr/sbin/tgtadm, root
|
||||
|
||||
# nova/utils.py:read_file_as_root: 'cat', file_path
|
||||
# (called from nova/virt/disk/vfs/localfs.py:VFSLocalFS.read_file)
|
||||
read_passwd: RegExpFilter, cat, root, cat, (/var|/usr)?/tmp/openstack-vfs-localfs[^/]+/etc/passwd
|
||||
read_shadow: RegExpFilter, cat, root, cat, (/var|/usr)?/tmp/openstack-vfs-localfs[^/]+/etc/shadow
|
||||
|
||||
# nova/virt/libvirt/volume.py: 'multipath' '-R'
|
||||
multipath: CommandFilter, /sbin/multipath, root
|
||||
|
||||
# nova/virt/libvirt/utils.py:
|
||||
systool: CommandFilter, /usr/bin/systool, root
|
||||
|
||||
# nova/virt/libvirt/volume.py:
|
||||
sginfo: CommandFilter, /usr/bin/sginfo, root
|
||||
sg_scan: CommandFilter, /usr/bin/sg_scan, root
|
||||
|
||||
# nova/virt/xenapi/vm_utils.py:
|
||||
xenstore-read: CommandFilter, /usr/bin/xenstore-read, root
|
||||
|
||||
# nova/virt/baremetal/tilera.py: '/usr/sbin/rpc.mountd'
|
||||
rpc.mountd: CommandFilter, /usr/sbin/rpc.mountd, root
|
||||
|
||||
# Rackspace Openvz starts here
|
||||
# nova/compute/manager.py: 'blockdev', '--getsize64', host_device
|
||||
blockdev: CommandFilter, /sbin/blockdev, root
|
||||
|
||||
# nova/virt/openvz_conn.py: '/usr/sbin/vzlist'
|
||||
vzlist: CommandFilter, /usr/sbin/vzlist, root
|
||||
|
||||
# nova/virt/openvz_conn.py: '/usr/sbin/vzctl'
|
||||
vzctl: CommandFilter, /usr/sbin/vzctl, root
|
||||
|
||||
# nova/virt/openvz_conn.py: '/usr/sbin/arping'
|
||||
arping_usrsbin: CommandFilter, /usr/sbin/arping, root
|
||||
|
||||
# nova/virt/openvz_conn.py: '/bin/rm'
|
||||
rm: CommandFilter, /bin/rm, root
|
||||
|
||||
# nova/virt/openvz_conn.py:
|
||||
cpuinfo: ReadFileFilter, /proc/cpuinfo
|
||||
|
||||
# nova/virt/openvz_conn.py:
|
||||
meminfo: ReadFileFilter, /proc/meminfo
|
||||
|
||||
# nova/virt/openvz_conn.py: '/usr/sbin/vzcpucheck'
|
||||
vzcpucheck: CommandFilter, /usr/sbin/vzcpucheck, root
|
||||
|
||||
# nova/virt/openvz_conn.py: '/bin/rmdir'
|
||||
rmdir: CommandFilter, /bin/rmdir, root
|
||||
|
||||
# nova/virt/openvz/volume_drivers/iscsi.py: '/usr/bin/iscsiadm'
|
||||
iscsiadm_usrsbin: CommandFilter, /usr/bin/iscsiadm, root
|
||||
|
||||
# nova/virt/openvz/utils.py: '/sbin/blkid'
|
||||
blkid: CommandFilter, /sbin/blkid, root
|
||||
|
||||
# nova/virt/openvz/network_plugins/tc.py
|
||||
tc: CommandFilter, /sbin/tc, root
|
||||
|
||||
# nova/virt/openvz/volume.py
|
||||
ls: CommandFilter, /bin/ls, root
|
||||
|
||||
# nova/virt/openvz/volume.py
|
||||
sfdisk: CommandFilter, /sbin/sfdisk, root
|
||||
|
||||
# nova/virt/openvz/volume.py
|
||||
mknod: CommandFilter, /bin/mknod, root
|
20
nova/virt/openvz/__init__.py
Normal file
20
nova/virt/openvz/__init__.py
Normal file
@ -0,0 +1,20 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from nova.virt.openvz import driver
|
||||
|
||||
OpenVzDriver = driver.OpenVzDriver
|
1915
nova/virt/openvz/driver.py
Normal file
1915
nova/virt/openvz/driver.py
Normal file
File diff suppressed because it is too large
Load Diff
221
nova/virt/openvz/file.py
Normal file
221
nova/virt/openvz/file.py
Normal file
@ -0,0 +1,221 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
A driver specific to OpenVz as the support for Ovz in libvirt
|
||||
is sketchy at best.
|
||||
"""
|
||||
|
||||
from nova import exception
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.virt.openvz import utils as ovz_utils
|
||||
import os
|
||||
from oslo.config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
LOG = logging.getLogger('nova.virt.openvz.file')
|
||||
|
||||
|
||||
class OVZFile(object):
|
||||
"""
|
||||
This is a generic file class for wrapping up standard file operations that
|
||||
may need to run on files associated with OpenVz
|
||||
"""
|
||||
def __init__(self, filename, permissions):
|
||||
self.filename = os.path.abspath(filename)
|
||||
self.contents = []
|
||||
self.permissions = permissions
|
||||
|
||||
def __enter__(self):
|
||||
"""
|
||||
This may feel dirty but we need to be able to read and write files
|
||||
as a non priviledged user so we need to change the permissions to
|
||||
be more open for our file operations and then secure them once we
|
||||
are done.
|
||||
"""
|
||||
if not self.exists():
|
||||
self.make_path()
|
||||
self.touch()
|
||||
|
||||
self.set_permissions(666)
|
||||
|
||||
def __exit__(self, _type, value, tb):
|
||||
if self.exists():
|
||||
self.set_permissions(self.permissions)
|
||||
|
||||
def read(self):
|
||||
"""
|
||||
Open the file for reading only and read it's contents into the instance
|
||||
attribute self.contents
|
||||
"""
|
||||
try:
|
||||
with open(self.filename, 'r') as fh:
|
||||
self.contents = fh.read().split('\n')
|
||||
except Exception as err:
|
||||
LOG.error(_('Output from open: %s') % err)
|
||||
raise exception.FileNotFound(
|
||||
_('Failed to read %s') % self.filename)
|
||||
|
||||
def write(self):
|
||||
"""
|
||||
Because self.contents may or may not get manipulated throughout the
|
||||
process, this is a method used to dump the contents of self.contents
|
||||
back into the file that this object represents.
|
||||
"""
|
||||
LOG.debug(_('File contents before write: %s') %
|
||||
'\n'.join(self.contents))
|
||||
try:
|
||||
with open(self.filename, 'w') as fh:
|
||||
fh.writelines('\n'.join(self.contents) + '\n')
|
||||
except Exception as err:
|
||||
LOG.error(_('Output from open: %s') % err)
|
||||
raise exception.FileNotFound(
|
||||
_('Failed to write %s') % self.filename)
|
||||
|
||||
def touch(self):
|
||||
"""
|
||||
There are certain conditions where we create an OVZFile object but that
|
||||
file may or may not exist and this provides us with a way to create
|
||||
that file if it doesn't exist.
|
||||
|
||||
Run the command:
|
||||
|
||||
touch <filename>
|
||||
|
||||
If this does not run an exception is raised as a failure to touch a
|
||||
file when you intend to do so would cause a serious failure of
|
||||
procedure.
|
||||
"""
|
||||
self.make_path()
|
||||
ovz_utils.execute('touch', self.filename, run_as_root=True)
|
||||
|
||||
def make_proper_script(self):
|
||||
"""
|
||||
OpenVz mount and umount files must be properly formatted scripts.
|
||||
Prepend the proper shell script header to the files
|
||||
"""
|
||||
if not self.contents[:1] == ['#!/bin/sh']:
|
||||
self.prepend('#!/bin/sh')
|
||||
|
||||
def append(self, contents):
|
||||
"""
|
||||
Add the argument contents to the end of self.contents
|
||||
"""
|
||||
if not isinstance(contents, list):
|
||||
contents = [str(contents)]
|
||||
self.contents += contents
|
||||
LOG.debug(_('File contents: %s') % self.contents)
|
||||
|
||||
def prepend(self, contents):
|
||||
"""
|
||||
Add the argument contents to the beginning of self.contents
|
||||
"""
|
||||
if not isinstance(contents, list):
|
||||
contents = [str(contents)]
|
||||
self.contents = contents + self.contents
|
||||
LOG.debug(_('File contents: %s') % self.contents)
|
||||
|
||||
def delete(self, contents):
|
||||
"""
|
||||
Delete the argument contents from self.contents if they exist.
|
||||
"""
|
||||
if isinstance(contents, list):
|
||||
LOG.debug(_('A list was passed to delete: %s') % contents)
|
||||
for line in contents:
|
||||
LOG.debug(_('Line: %(line)s \tFound in: %(contents)s') %
|
||||
locals())
|
||||
self.remove_line(line)
|
||||
else:
|
||||
LOG.debug(_('A string was passed to delete: %s') % contents)
|
||||
self.remove_line(contents)
|
||||
|
||||
def remove_line(self, line):
|
||||
"""
|
||||
Simple helper method to actually do the removal of a line from an array
|
||||
"""
|
||||
LOG.debug(_('Removing line if present: %s') % line)
|
||||
if line in self.contents:
|
||||
LOG.debug(_('Line found: %s') % line)
|
||||
self.contents.remove(line)
|
||||
LOG.debug(_('Line removed: %(line)s \t%(contents)s') %
|
||||
{'line': line, 'contents': self.contents})
|
||||
|
||||
def set_permissions(self, permissions):
|
||||
"""
|
||||
Because nova runs as an unprivileged user we need a way to mangle
|
||||
permissions on files that may be owned by root for manipulation
|
||||
|
||||
Run the command:
|
||||
|
||||
chmod <permissions> <filename>
|
||||
|
||||
If this doesn't run an exception is raised because the permissions not
|
||||
being set to what the application believes they are set to can cause
|
||||
a failure of epic proportions.
|
||||
"""
|
||||
ovz_utils.execute('chmod', permissions, self.filename,
|
||||
run_as_root=True)
|
||||
|
||||
def make_path(self, path=None):
|
||||
"""
|
||||
Helper method for an OVZFile object to be able to create the path for
|
||||
self.filename if it doesn't exist before running touch()
|
||||
"""
|
||||
if not path:
|
||||
path = self.filename
|
||||
basedir = os.path.dirname(path)
|
||||
ovz_utils.make_dir(basedir)
|
||||
|
||||
def set_contents(self, new_contents):
|
||||
"""
|
||||
We need the ability to overwrite all contents and this is the cleanest
|
||||
way while allowing some validation.
|
||||
"""
|
||||
if isinstance(new_contents, str):
|
||||
LOG.debug(_('Type of new_contents is: str'))
|
||||
self.contents = new_contents.split('\n')
|
||||
LOG.debug(_('Contents of file: %s') % self.contents)
|
||||
elif isinstance(new_contents, list):
|
||||
LOG.debug(_('Type of new_contents is: list'))
|
||||
self.contents = new_contents
|
||||
else:
|
||||
raise TypeError(_("I don't know what to do with this type: %s") %
|
||||
type(new_contents))
|
||||
|
||||
def run_contents(self):
|
||||
# Because only approved commands in rootwrap can be executed we
|
||||
# don't need to worry about unwanted command injection
|
||||
for line in self.contents:
|
||||
if len(line) > 0:
|
||||
if line[0] != '#' and line != '\n':
|
||||
line = line.strip()
|
||||
try:
|
||||
LOG.debug(
|
||||
_('Running line from %(filename)s: %(line)s') %
|
||||
{'filename': self.filename, 'line': line})
|
||||
line = line.split()
|
||||
ovz_utils.execute(*line, run_as_root=True)
|
||||
except exception.InstanceUnacceptable as err:
|
||||
LOG.error(_('Cannot execute: %s') % line)
|
||||
LOG.error(err)
|
||||
|
||||
def exists(self):
|
||||
"""
|
||||
Simple wrapper for os.path.exists
|
||||
"""
|
||||
return os.path.exists(self.filename)
|
0
nova/virt/openvz/file_ext/__init__.py
Normal file
0
nova/virt/openvz/file_ext/__init__.py
Normal file
43
nova/virt/openvz/file_ext/boot.py
Normal file
43
nova/virt/openvz/file_ext/boot.py
Normal file
@ -0,0 +1,43 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
OpenVZ doesn't allow for a script to be run after a container has started up
|
||||
using the host node's context so we are implementing one here
|
||||
"""
|
||||
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.virt.openvz import file as ovzfile
|
||||
import os
|
||||
from oslo.config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger('nova.virt.openvz.file_ext.boot')
|
||||
|
||||
|
||||
class OVZBootFile(ovzfile.OVZFile):
|
||||
def __init__(self, instance_id, permissions):
|
||||
"""
|
||||
Child class of OVZFile used to manage the CTID.boot file. This file
|
||||
will be executed line by line after the container has started but
|
||||
using the host's context.
|
||||
|
||||
:param instance_id: Instance used for the file
|
||||
"""
|
||||
filename = "%s/%s.boot" % (CONF.ovz_config_dir, instance_id)
|
||||
filename = os.path.abspath(filename)
|
||||
super(OVZBootFile, self).__init__(filename, permissions)
|
114
nova/virt/openvz/file_ext/ext_storage.py
Normal file
114
nova/virt/openvz/file_ext/ext_storage.py
Normal file
@ -0,0 +1,114 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
A driver specific to OpenVz as the support for Ovz in libvirt
|
||||
is sketchy at best.
|
||||
"""
|
||||
|
||||
import json
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.virt.openvz import file as ovzfile
|
||||
import os
|
||||
from oslo.config import cfg
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
LOG = logging.getLogger('nova.virt.openvz.file_ext.ext_storage')
|
||||
|
||||
|
||||
class OVZExtStorage(object):
|
||||
"""
|
||||
Local store for volume information needing to be persisted to the local
|
||||
host.
|
||||
"""
|
||||
def __init__(self, instance_id):
|
||||
"""
|
||||
:param instance_id: CTID of the container
|
||||
:return: None
|
||||
"""
|
||||
filename = '%s/%s.ext_storage' % (CONF.ovz_config_dir, instance_id)
|
||||
filename = os.path.abspath(filename)
|
||||
self.instance_id = instance_id
|
||||
self.local_store = ovzfile.OVZFile(filename, 600)
|
||||
|
||||
# read the local store for the CTID or create it if it doesn't already
|
||||
# exist.
|
||||
with self.local_store:
|
||||
self.local_store.read()
|
||||
|
||||
# preload volume info
|
||||
self.load_volumes()
|
||||
|
||||
def load_volumes(self):
|
||||
"""
|
||||
return the contents of self.contents *after* converting it from json
|
||||
to python objects.
|
||||
|
||||
:return: list
|
||||
"""
|
||||
try:
|
||||
self.volumes = json.loads(self.local_store.contents[0])
|
||||
except (ValueError, IndexError):
|
||||
self.volumes = dict()
|
||||
|
||||
def add_volume(self, device, volume_info, overwrite=True):
|
||||
"""
|
||||
Add a volume to local storage so volumes can be reconnected to in
|
||||
emergencies without nova services if need be.
|
||||
|
||||
:param device: Device name
|
||||
:param volume_info: Nova volume information from block_device_map
|
||||
:return: None
|
||||
"""
|
||||
if device in self.volumes and not overwrite:
|
||||
msg = (_('You told me not to overwrite volume info for device: '
|
||||
'%(device)s on instance: %(instnace_id)s') %
|
||||
{'device': device, 'instance_id': self.instance_id})
|
||||
LOG.error(msg)
|
||||
raise KeyError(msg)
|
||||
|
||||
self.volumes[device] = volume_info
|
||||
|
||||
def remove_volume(self, device):
|
||||
"""
|
||||
removes a volume from the volume store.
|
||||
|
||||
:param device:
|
||||
:return: None
|
||||
"""
|
||||
try:
|
||||
self.volumes.pop(device)
|
||||
LOG.debug(
|
||||
_('Removed volume %(device)s from instance %(instance_id)s') %
|
||||
{'device': device, 'instance_id': self.instance_id})
|
||||
except KeyError:
|
||||
LOG.error(
|
||||
_('Volume %(device)s was not in local store for '
|
||||
'instance %(instance_id)s') %
|
||||
{'device': device, 'instance_id': self.instance_id})
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Flushes contents of self.volumes to disk for persistance
|
||||
|
||||
:return: None
|
||||
"""
|
||||
self.local_store.set_contents(json.dumps(self.volumes))
|
||||
with self.local_store:
|
||||
self.local_store.write()
|
44
nova/virt/openvz/file_ext/shutdown.py
Normal file
44
nova/virt/openvz/file_ext/shutdown.py
Normal file
@ -0,0 +1,44 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
OpenVz doesn't have provision for a script to be run before a container
|
||||
is stopped but with the host node's context so we are implementing one here.
|
||||
"""
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.virt.openvz import file as ovzfile
|
||||
import os
|
||||
from oslo.config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger('nova.virt.openvz.file_ext.shutdown')
|
||||
|
||||
|
||||
class OVZShutdownFile(ovzfile.OVZFile):
|
||||
def __init__(self, instance_id, permissions):
|
||||
"""
|
||||
Child class of OVZFile used to manage the CTID.boot file. This file
|
||||
will be executed line by line after the container has started but
|
||||
using the host's context.
|
||||
|
||||
:param instance_id: Instance used for the file
|
||||
"""
|
||||
LOG.debug(_('Beginning OVZShutdownFile'))
|
||||
filename = "%s/%s.shutdown" % (CONF.ovz_config_dir, instance_id)
|
||||
filename = os.path.abspath(filename)
|
||||
LOG.debug(_('OVZShutdownFile: %s') % filename)
|
||||
super(OVZShutdownFile, self).__init__(filename, permissions)
|
33
nova/virt/openvz/file_ext/start.py
Normal file
33
nova/virt/openvz/file_ext/start.py
Normal file
@ -0,0 +1,33 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
A driver specific to OpenVz as the support for Ovz in libvirt
|
||||
is sketchy at best.
|
||||
"""
|
||||
from nova.virt.openvz import file as ovzfile
|
||||
import os
|
||||
from oslo.config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class OVZContainerStartScript(ovzfile.OVZFile):
|
||||
def __init__(self, instance_id):
|
||||
filename = "%s/%s.start" % (CONF.ovz_config_dir, instance_id)
|
||||
filename = os.path.abspath(filename)
|
||||
super(OVZContainerStartScript, self).__init__(filename)
|
33
nova/virt/openvz/file_ext/stop.py
Normal file
33
nova/virt/openvz/file_ext/stop.py
Normal file
@ -0,0 +1,33 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
A driver specific to OpenVz as the support for Ovz in libvirt
|
||||
is sketchy at best.
|
||||
"""
|
||||
from nova.virt.openvz import file as ovzfile
|
||||
import os
|
||||
from oslo.config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class OVZContainerStopScript(ovzfile.OVZFile):
|
||||
def __init__(self, instance_id):
|
||||
filename = "%s/%s.stop" % (CONF.ovz_config_dir, instance_id)
|
||||
filename = os.path.abspath(filename)
|
||||
super(OVZContainerStopScript, self).__init__(filename)
|
209
nova/virt/openvz/network.py
Normal file
209
nova/virt/openvz/network.py
Normal file
@ -0,0 +1,209 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
A driver specific to OpenVz as the support for Ovz in libvirt
|
||||
is sketchy at best.
|
||||
"""
|
||||
|
||||
from Cheetah import Template
|
||||
from nova import exception
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.virt.openvz import file as ovzfile
|
||||
from nova.virt.openvz.file_ext import boot as ovzboot
|
||||
from nova.virt.openvz.file_ext import shutdown as ovzshutdown
|
||||
from nova.virt.openvz.network_drivers import tc as ovztc
|
||||
from nova.virt.openvz import utils as ovz_utils
|
||||
import os
|
||||
from oslo.config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger('nova.virt.openvz.network')
|
||||
|
||||
|
||||
class OVZNetworkInterfaces(object):
|
||||
"""
|
||||
Helper class for managing interfaces in OpenVz
|
||||
"""
|
||||
#TODO(imsplitbit): fix this to work with redhat based distros
|
||||
def __init__(self, interface_info):
|
||||
"""
|
||||
Manage the network interfaces for your OpenVz containers.
|
||||
"""
|
||||
self.interface_info = interface_info
|
||||
LOG.debug(_('Interface info: %s') % self.interface_info)
|
||||
self.boot_file = ovzboot.OVZBootFile(self.interface_info[0]['id'], 755)
|
||||
with self.boot_file:
|
||||
self.boot_file.set_contents(list())
|
||||
self.shutdown_file = ovzshutdown.OVZShutdownFile(
|
||||
self.interface_info[0]['id'], 755)
|
||||
with self.shutdown_file:
|
||||
self.shutdown_file.set_contents(list())
|
||||
|
||||
def add(self):
|
||||
"""
|
||||
Add all interfaces and addresses to the container.
|
||||
"""
|
||||
if CONF.ovz_use_veth_devs:
|
||||
for net_dev in self.interface_info:
|
||||
self._add_netif(net_dev['id'], net_dev['name'],
|
||||
net_dev['bridge'], net_dev['mac'])
|
||||
tc_rules = ovztc.OVZTcRules()
|
||||
tc_rules.instance_info(net_dev['id'], net_dev['address'],
|
||||
net_dev['vz_host_if'])
|
||||
with self.boot_file:
|
||||
self.boot_file.append(tc_rules.container_start())
|
||||
|
||||
with self.shutdown_file:
|
||||
self.shutdown_file.append(tc_rules.container_stop())
|
||||
|
||||
self._load_template()
|
||||
self._fill_templates()
|
||||
else:
|
||||
for net_dev in self.interface_info:
|
||||
self._add_ip(net_dev['id'], net_dev['address'])
|
||||
|
||||
with self.boot_file:
|
||||
self.boot_file.write()
|
||||
|
||||
with self.shutdown_file:
|
||||
self.shutdown_file.write()
|
||||
|
||||
self._set_nameserver(self.interface_info[0]['id'],
|
||||
self.interface_info[0]['dns'])
|
||||
|
||||
def _load_template(self):
|
||||
"""
|
||||
Load templates needed for network interfaces.
|
||||
"""
|
||||
if CONF.ovz_use_veth_devs:
|
||||
# TODO(imsplitbit): make a cheatah template for dhcp networking
|
||||
self.template = open(CONF.injected_network_template).read()
|
||||
else:
|
||||
self.template = None
|
||||
|
||||
def _fill_templates(self):
|
||||
"""
|
||||
Iterate through each file necessary for creating interfaces on a
|
||||
given platform, open the file and write the contents of the template
|
||||
to the file.
|
||||
"""
|
||||
for filename in self._filename_factory():
|
||||
network_file = OVZNetworkFile(filename)
|
||||
self.iface_file = str(
|
||||
Template.Template(self.template,
|
||||
searchList=[
|
||||
{'interfaces': self.interface_info,
|
||||
'use_ipv6': CONF.use_ipv6}]))
|
||||
with network_file:
|
||||
network_file.append(self.iface_file.split('\n'))
|
||||
network_file.write()
|
||||
|
||||
def _filename_factory(self, variant='debian'):
|
||||
"""
|
||||
Generate a path for the file needed to implement an interface
|
||||
"""
|
||||
#TODO(imsplitbit): Figure out how to introduce OS hints into nova
|
||||
# so we can generate support for redhat based distros. This will
|
||||
# require an os hint to be placed in glance to use for making
|
||||
# decisions. Then we can create a generator that will generate
|
||||
# redhat style interface paths like:
|
||||
#
|
||||
# /etc/sysconfig/network-scripts/ifcfg-eth0
|
||||
#
|
||||
# for now, we just return the debian path.
|
||||
|
||||
redhat_path = '/etc/sysconfig/network-scripts/'
|
||||
debian_path = '/etc/network/interfaces'
|
||||
prefix = '%(private_dir)s/%(instance_id)s' %\
|
||||
{'private_dir': CONF.ovz_ve_private_dir,
|
||||
'instance_id': self.interface_info[0]['id']}
|
||||
prefix = os.path.abspath(prefix)
|
||||
|
||||
#TODO(imsplitbit): fix this placeholder for RedHat compatibility.
|
||||
if variant == 'redhat':
|
||||
for net_dev in self.interface_info:
|
||||
path = prefix + redhat_path + ('ifcfg-%s' % net_dev['name'])
|
||||
path = os.path.abspath(path)
|
||||
LOG.debug(_('Generated filename %(path)s') % locals())
|
||||
yield path
|
||||
elif variant == 'debian':
|
||||
path = prefix + debian_path
|
||||
path = os.path.abspath(path)
|
||||
LOG.debug(_('Generated filename %(path)s') % locals())
|
||||
yield path
|
||||
else:
|
||||
raise exception.InvalidMetadata(
|
||||
_('Variant %(variant)s is not known') % locals())
|
||||
|
||||
def _add_netif(self, instance_id, netif, bridge, host_mac):
|
||||
"""
|
||||
This is a work around to add the eth devices the way OpenVZ
|
||||
wants them.
|
||||
|
||||
When this works, it runs a command similar to this:
|
||||
|
||||
vzctl set 1 --save --netif_add \
|
||||
eth0,,veth1.eth0,11:11:11:11:11:11,br100
|
||||
"""
|
||||
host_if = 'veth%s.%s' % (instance_id, netif)
|
||||
ovz_utils.execute('vzctl', 'set', instance_id, '--save', '--netif_add',
|
||||
'%s,,%s,%s,%s' % (netif, host_if, host_mac, bridge),
|
||||
run_as_root=True)
|
||||
|
||||
def _add_ip(self, instance_id, ip):
|
||||
"""
|
||||
Add an IP address to a container if you are not using veth devices.
|
||||
|
||||
Run the command:
|
||||
|
||||
vzctl set <ctid> --save --ipadd <ip>
|
||||
|
||||
If this fails to run an exception is raised as this indicates a failure
|
||||
to create a network available connection within the container thus
|
||||
making it unusable to all but local users and therefore unusable to
|
||||
nova.
|
||||
"""
|
||||
ovz_utils.execute('vzctl', 'set', instance_id, '--save', '--ipadd', ip,
|
||||
run_as_root=True)
|
||||
|
||||
def _set_nameserver(self, instance_id, dns):
|
||||
"""
|
||||
Get the nameserver for the assigned network and set it using
|
||||
OpenVz's tools.
|
||||
|
||||
Run the command:
|
||||
|
||||
vzctl set <ctid> --save --nameserver <nameserver>
|
||||
|
||||
If this fails to run an exception is raised as this will indicate
|
||||
the container's inability to do name resolution.
|
||||
"""
|
||||
ovz_utils.execute('vzctl', 'set', instance_id, '--save',
|
||||
'--nameserver', dns, run_as_root=True)
|
||||
|
||||
|
||||
class OVZNetworkFile(ovzfile.OVZFile):
|
||||
"""
|
||||
An abstraction for network files. This is necessary for multi-platform
|
||||
support. OpenVz runs on all linux distros and can host all linux distros
|
||||
but they don't all create interfaces the same way. This should make it
|
||||
easy to add interface files to all flavors of linux.
|
||||
"""
|
||||
|
||||
def __init__(self, filename):
|
||||
super(OVZNetworkFile, self).__init__(filename, 644)
|
0
nova/virt/openvz/network_drivers/__init__.py
Normal file
0
nova/virt/openvz/network_drivers/__init__.py
Normal file
58
nova/virt/openvz/network_drivers/network_bridge.py
Normal file
58
nova/virt/openvz/network_drivers/network_bridge.py
Normal file
@ -0,0 +1,58 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
A driver specific to OpenVz as the support for Ovz in libvirt
|
||||
is sketchy at best.
|
||||
"""
|
||||
|
||||
from nova.network import linux_net
|
||||
from nova.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger('nova.virt.openvz.network_drivers.network_bridge')
|
||||
|
||||
|
||||
class OVZNetworkBridgeDriver(object):
|
||||
"""
|
||||
VIF driver for a Linux Bridge
|
||||
"""
|
||||
|
||||
def plug(self, instance, network, mapping):
|
||||
"""
|
||||
Ensure that the bridge exists and add a vif to it.
|
||||
"""
|
||||
if (not network.get('should_create_bridge') and
|
||||
mapping.get('should_create_vlan')):
|
||||
if mapping.get('should_create_vlan'):
|
||||
LOG.debug(_('Ensuring bridge %(bridge)s and vlan %(vlan)s') %
|
||||
{'bridge': network['bridge'],
|
||||
'vlan': network['vlan']})
|
||||
linux_net.LinuxBridgeInterfaceDriver.ensure_vlan_bridge(
|
||||
network['vlan'],
|
||||
network['bridge'],
|
||||
network['bridge_interface'])
|
||||
else:
|
||||
LOG.debug(_('Ensuring bridge %s') % network['bridge'])
|
||||
linux_net.LinuxBridgeInterfaceDriver.ensure_bridge(
|
||||
network['bridge'],
|
||||
network['bridge_interface'])
|
||||
|
||||
def unplug(self, instance, network, mapping):
|
||||
"""
|
||||
No manual unplugging required
|
||||
"""
|
||||
pass
|
267
nova/virt/openvz/network_drivers/tc.py
Normal file
267
nova/virt/openvz/network_drivers/tc.py
Normal file
@ -0,0 +1,267 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from Cheetah import Template
|
||||
from nova.conductor import api
|
||||
from nova import context
|
||||
from nova import exception
|
||||
from nova.openstack.common import lockutils
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.virt.openvz import utils as ovz_utils
|
||||
import os
|
||||
from oslo.config import cfg
|
||||
import random
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger('nova.virt.openvz.network_drivers.tc')
|
||||
|
||||
global _ovz_tc_available_ids
|
||||
global _ovz_tc_inflight_ids
|
||||
_ovz_tc_available_ids = None
|
||||
_ovz_tc_inflight_ids = None
|
||||
|
||||
|
||||
class OVZTcRules(object):
|
||||
# Set our class variables if they don't exist
|
||||
# TODO(imsplitbit): figure out if there is a more pythonic way to do this
|
||||
if not _ovz_tc_available_ids:
|
||||
_ovz_tc_available_ids = list()
|
||||
|
||||
if not _ovz_tc_inflight_ids:
|
||||
_ovz_tc_inflight_ids = list()
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
This class is used to return TC rulesets for both a host and guest for
|
||||
use with host and guest startup and shutdown.
|
||||
"""
|
||||
self.conductor = api.API()
|
||||
|
||||
if not len(OVZTcRules._ovz_tc_available_ids):
|
||||
LOG.debug(_('Available_ids is empty, filling it with numbers'))
|
||||
OVZTcRules._ovz_tc_available_ids = [
|
||||
i for i in range(1, CONF.ovz_tc_id_max)
|
||||
]
|
||||
|
||||
# do an initial clean of the available ids
|
||||
self._remove_used_ids()
|
||||
LOG.debug(
|
||||
_('OVZTcRules thinks ovz_tc_host_slave_device is set to %s')
|
||||
% CONF.ovz_tc_host_slave_device)
|
||||
|
||||
def instance_info(self, instance_id, address, vz_iface):
|
||||
"""
|
||||
Use this method when creating or resizing an instance. It will
|
||||
generate a new tc ruleset
|
||||
|
||||
:param instance_id: Instance to generate the rules for
|
||||
:param address: IP address for the instance
|
||||
:param vz_iface: interface on the hosts bridge that is associated with
|
||||
the instance
|
||||
"""
|
||||
if not instance_id:
|
||||
self.instance_type = dict()
|
||||
self.instance_type['memory_mb'] = 2048
|
||||
else:
|
||||
admin_context = context.get_admin_context()
|
||||
self.instance = self.conductor.instance_get(
|
||||
admin_context, instance_id)
|
||||
self.instance_type = self.conductor.instance_type_get(
|
||||
admin_context, self.instance['instance_type_id'])
|
||||
LOG.debug(_('CT TC address: %s') % address)
|
||||
|
||||
self.address = address
|
||||
self.vz_iface = vz_iface
|
||||
|
||||
# Calculate the bandwidth total by figuring out how many units we have
|
||||
self.bandwidth = int(
|
||||
round(self.instance_type['memory_mb'] /
|
||||
CONF.ovz_memory_unit_size)) * CONF.ovz_tc_mbit_per_unit
|
||||
LOG.debug(_('Allotted bandwidth: %s') % self.bandwidth)
|
||||
self.tc_id = self._get_instance_tc_id()
|
||||
if not self.tc_id:
|
||||
LOG.debug(_('No preassigned tc_id for %s, getting a new one') %
|
||||
instance_id)
|
||||
self.tc_id = self.get_id()
|
||||
|
||||
self._save_instance_tc_id()
|
||||
LOG.debug(_('Saved the tc_id in the database for the instance'))
|
||||
|
||||
@lockutils.synchronized('get_id_lock', 'openvz-')
|
||||
def get_id(self):
|
||||
"""
|
||||
Uses nova utils decorator @lockutils.synchronized to make sure that we
|
||||
do not return duplicate available ids. This will return a random id
|
||||
number between 1 and 9999 which are the limits of TC.
|
||||
"""
|
||||
self._remove_used_ids()
|
||||
LOG.debug(_('Pulling new TC id'))
|
||||
tc_id = self._pull_id()
|
||||
LOG.debug(_('TC id %s pulled, testing for dupe') % tc_id)
|
||||
while tc_id in OVZTcRules._ovz_tc_inflight_ids:
|
||||
LOG.debug(_('TC id %s inflight already, pulling another') % tc_id)
|
||||
tc_id = self._pull_id()
|
||||
LOG.debug(_('TC id %s pulled, testing for dupe') % tc_id)
|
||||
LOG.debug(_('TC id %s pulled, verified unique'))
|
||||
self._reserve_id(tc_id)
|
||||
return tc_id
|
||||
|
||||
def container_start(self):
|
||||
template = self._load_template('tc_container_start.template')
|
||||
search_list = {
|
||||
'prio': self.tc_id,
|
||||
'host_iface': CONF.ovz_tc_host_slave_device,
|
||||
'vz_iface': self.vz_iface,
|
||||
'bandwidth': self.bandwidth,
|
||||
'vz_address': self.address,
|
||||
'line_speed': CONF.ovz_tc_max_line_speed
|
||||
}
|
||||
ovz_utils.save_instance_metadata(self.instance['id'],
|
||||
'tc_id', self.tc_id)
|
||||
return self._fill_template(template, search_list).splitlines()
|
||||
|
||||
def container_stop(self):
|
||||
template = self._load_template('tc_container_stop.template')
|
||||
search_list = {
|
||||
'prio': self.tc_id,
|
||||
'host_iface': CONF.ovz_tc_host_slave_device,
|
||||
'vz_iface': self.vz_iface,
|
||||
'bandwidth': self.bandwidth,
|
||||
'vz_address': self.address
|
||||
}
|
||||
ovz_utils.save_instance_metadata(self.instance['id'],
|
||||
'tc_id', self.tc_id)
|
||||
return self._fill_template(template, search_list).splitlines()
|
||||
|
||||
def host_start(self):
|
||||
template = self._load_template('tc_host_start.template')
|
||||
search_list = {
|
||||
'host_iface': CONF.ovz_tc_host_slave_device,
|
||||
'line_speed': CONF.ovz_tc_max_line_speed
|
||||
}
|
||||
return self._fill_template(template, search_list).splitlines()
|
||||
|
||||
def host_stop(self):
|
||||
template = self._load_template('tc_host_stop.template')
|
||||
search_list = {
|
||||
'host_iface': CONF.ovz_tc_host_slave_device
|
||||
}
|
||||
return self._fill_template(template, search_list).splitlines()
|
||||
|
||||
def _load_template(self, template_name):
|
||||
"""
|
||||
read and return the template file
|
||||
"""
|
||||
full_template_path = '%s/%s' % (
|
||||
CONF.ovz_tc_template_dir, template_name)
|
||||
full_template_path = os.path.abspath(full_template_path)
|
||||
try:
|
||||
LOG.debug(_('Opening template file: %s') % full_template_path)
|
||||
template_file = open(full_template_path).read()
|
||||
LOG.debug(_('Template file opened successfully'))
|
||||
return template_file
|
||||
except Exception as err:
|
||||
LOG.error(_('Unable to open template: %s') % full_template_path)
|
||||
raise exception.FileNotFound(err)
|
||||
|
||||
def _fill_template(self, template, search_list):
|
||||
"""
|
||||
Take the vars in search_list and fill out template
|
||||
"""
|
||||
return str(Template.Template(template, searchList=[search_list]))
|
||||
|
||||
def _pull_id(self):
|
||||
"""
|
||||
Pop a random id from the list of available ids for tc rules
|
||||
"""
|
||||
return OVZTcRules._ovz_tc_available_ids[random.randint(
|
||||
0, len(OVZTcRules._ovz_tc_available_ids) - 1)]
|
||||
|
||||
def _list_existing_ids(self):
|
||||
LOG.debug(_('Attempting to list existing IDs'))
|
||||
out = ovz_utils.execute('tc', 'filter', 'show', 'dev',
|
||||
CONF.ovz_tc_host_slave_device,
|
||||
run_as_root=True)
|
||||
ids = list()
|
||||
for line in out.splitlines():
|
||||
line = line.split()
|
||||
if line[0] == 'filter':
|
||||
tc_id = int(line[6])
|
||||
if tc_id not in ids:
|
||||
ids.append(int(tc_id))
|
||||
|
||||
# get the metadata for instances from the database
|
||||
# this will provide us with any ids of instances that aren't
|
||||
# currently running so that we can remove active and provisioned
|
||||
# but inactive ids from the available list
|
||||
instances_metadata = ovz_utils.read_all_instance_metadata()
|
||||
LOG.debug(_('Instances metadata: %s') % instances_metadata)
|
||||
if instances_metadata:
|
||||
for instance_id, meta in instances_metadata.iteritems():
|
||||
tc_id = meta.get('tc_id')
|
||||
if tc_id:
|
||||
if tc_id not in ids:
|
||||
LOG.debug(
|
||||
_('TC id "%(tc_id)s" for instance '
|
||||
'"%(instance_id)s" found') % locals())
|
||||
ids.append(int(tc_id))
|
||||
return ids
|
||||
|
||||
def _reserve_id(self, tc_id):
|
||||
"""
|
||||
This removes the id from the _ovz_tc_available_ids and adds it to the
|
||||
_ovz_tc_inflight_ids
|
||||
"""
|
||||
LOG.debug(_('Beginning reservation process'))
|
||||
OVZTcRules._ovz_tc_inflight_ids.append(tc_id)
|
||||
LOG.debug(_('Added id "%s" to _ovz_tc_inflight_ids') % tc_id)
|
||||
OVZTcRules._ovz_tc_available_ids.remove(tc_id)
|
||||
LOG.debug(_('Removed id "%s" from _ovz_tc_available_ids') % tc_id)
|
||||
|
||||
def _remove_used_ids(self):
|
||||
"""
|
||||
Clean ids that are currently provisioned on the host from the
|
||||
list of _ovz_tc_available_ids
|
||||
"""
|
||||
LOG.debug(_('Beginning cleanup of used ids'))
|
||||
used_ids = self._list_existing_ids()
|
||||
LOG.debug(_('Used ids found, removing from _ovz_tc_available_ids'))
|
||||
for tc_id in used_ids:
|
||||
if tc_id in OVZTcRules._ovz_tc_available_ids:
|
||||
OVZTcRules._ovz_tc_available_ids.remove(tc_id)
|
||||
LOG.debug(_('Removed all ids in use'))
|
||||
|
||||
def _save_instance_tc_id(self):
|
||||
"""
|
||||
Save the tc id in the instance metadata in the database
|
||||
"""
|
||||
ovz_utils.save_instance_metadata(self.instance['id'], 'tc_id',
|
||||
self.tc_id, fail_on_dupe_key=True)
|
||||
|
||||
def _get_instance_tc_id(self):
|
||||
"""
|
||||
Look up instance metadata in the db and see if there is already
|
||||
a tc_id for the instance
|
||||
"""
|
||||
instance_metadata = ovz_utils.read_instance_metadata(
|
||||
self.instance['id'])
|
||||
LOG.debug(_('Instances metadata: %s') % instance_metadata)
|
||||
if instance_metadata:
|
||||
tc_id = instance_metadata.get('tc_id')
|
||||
LOG.debug(
|
||||
_('TC id for instance %(instance_id)s is %(tc_id)s') %
|
||||
{'instance_id': self.instance['id'], 'tc_id': tc_id})
|
||||
return tc_id
|
@ -0,0 +1,8 @@
|
||||
tc qdisc add dev ${vz_iface} root handle 1: htb default 10
|
||||
tc class add dev ${vz_iface} parent 1: classid 1:1 htb rate ${line_speed}mbit ceil ${line_speed}mbit burst 15k
|
||||
tc class add dev ${vz_iface} parent 1:1 classid 1:${prio} htb rate ${bandwidth}mbit ceil ${bandwidth}mbit burst 15k
|
||||
tc qdisc add dev ${vz_iface} parent 1:${prio} handle ${prio}: sfq perturb 10
|
||||
tc filter replace dev ${vz_iface} protocol ip parent 1: prio ${prio} u32 match ip dst ${vz_address} flowid 1:${prio}
|
||||
tc class add dev ${host_iface} parent 1:1 classid 1:${prio} htb rate ${bandwidth}mbit ceil ${bandwidth}mbit burst 15k
|
||||
tc qdisc add dev ${host_iface} parent 1:${prio} handle ${prio}: sfq perturb 10
|
||||
tc filter replace dev ${host_iface} protocol ip parent 1: prio ${prio} u32 match ip src ${vz_address} flowid 1:${prio}
|
@ -0,0 +1,6 @@
|
||||
tc filter del dev ${host_iface} protocol ip parent 1:0 prio ${prio}
|
||||
tc class del dev ${host_iface} parent 1:1 classid 1:${prio} htb rate ${bandwidth}mbit
|
||||
tc qdisc delete dev ${host_iface} parent 1:${prio} handle ${prio}
|
||||
tc filter del dev ${vz_iface} protocol ip parent 1:0 prio ${prio}
|
||||
tc class del dev ${vz_iface} parent 1:1 classid 1:${prio} htb rate ${bandwidth}mbit
|
||||
tc qdisc delete dev ${vz_iface} parent 1:${prio} handle ${prio}
|
@ -0,0 +1,5 @@
|
||||
# Outgoing traffic control
|
||||
# Should only be run once at setup or all rules are wiped
|
||||
tc qdisc del dev ${host_iface} root
|
||||
tc qdisc add dev ${host_iface} root handle 1: htb default 10
|
||||
tc class add dev ${host_iface} parent 1: classid 1:1 htb rate ${line_speed}mbit ceil ${line_speed}mbit burst 15k
|
@ -0,0 +1 @@
|
||||
tc qdisc del dev ${host_iface} root
|
616
nova/virt/openvz/utils.py
Normal file
616
nova/virt/openvz/utils.py
Normal file
@ -0,0 +1,616 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
A driver specific to OpenVz as the support for Ovz in libvirt
|
||||
is sketchy at best.
|
||||
"""
|
||||
|
||||
import multiprocessing
|
||||
from nova.conductor import api
|
||||
from nova import context
|
||||
from nova import exception
|
||||
from nova.openstack.common import log as logging
|
||||
from nova import utils
|
||||
import os
|
||||
from oslo.config import cfg
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger('nova.virt.openvz.utils')
|
||||
conductor = api.API()
|
||||
|
||||
|
||||
def execute(*cmd, **kwargs):
|
||||
"""
|
||||
This is a wrapper for utils.execute so as to reduce the amount of
|
||||
duplicated code in the driver.
|
||||
"""
|
||||
if 'raise_on_error' in kwargs:
|
||||
raise_on_error = kwargs.pop('raise_on_error')
|
||||
else:
|
||||
raise_on_error = True
|
||||
|
||||
try:
|
||||
out, err = utils.execute(*cmd, **kwargs)
|
||||
if out:
|
||||
LOG.debug(_('Stdout from %(command)s: %(out)s') %
|
||||
{'command': cmd[0], 'out': out})
|
||||
if err:
|
||||
LOG.debug(_('Stderr from %(command)s: %(out)s') %
|
||||
{'command': cmd[0], 'out': err})
|
||||
return out
|
||||
except exception.ProcessExecutionError as err:
|
||||
msg = (_('Stderr from %(command)s: %(out)s') %
|
||||
{'command': cmd[0], 'out': err})
|
||||
if raise_on_error:
|
||||
LOG.error(msg)
|
||||
raise exception.InstanceUnacceptable(
|
||||
_('Error running %(command)s: %(out)s') %
|
||||
{'command': cmd[0], 'out': err})
|
||||
else:
|
||||
LOG.warn(msg)
|
||||
return None
|
||||
|
||||
|
||||
def mkfs(path, fs, fs_uuid=None, fs_label=None):
|
||||
"""Format a file or block device
|
||||
|
||||
:param fs: Filesystem type (examples include 'ext3', 'ext4'
|
||||
'btrfs', etc.)
|
||||
:param path: Path to file or block device to format
|
||||
:param fs_uuid: Volume uuid to use
|
||||
:param fs_label: Volume label to use
|
||||
"""
|
||||
if not fs_uuid:
|
||||
fs_uuid = str(uuid.uuid4())
|
||||
|
||||
args = ['mkfs', '-F', '-t', fs]
|
||||
if fs_uuid:
|
||||
args.extend(['-U', fs_uuid])
|
||||
if fs_label:
|
||||
args.extend(['-L', fs_label])
|
||||
args.append(path)
|
||||
LOG.debug(_('Mkfs command: %s') % args)
|
||||
execute(*args, run_as_root=True)
|
||||
|
||||
|
||||
def get_fs_uuid(device):
|
||||
"""
|
||||
Because we may need to operate on a volume by it's uuid
|
||||
we need a way to see if a filesystem has a uuid on it
|
||||
"""
|
||||
LOG.debug(_('Getting FS UUID for: %s') % device)
|
||||
out = execute('blkid', '-o', 'value', '-s', 'UUID', device,
|
||||
run_as_root=True, raise_on_error=False)
|
||||
if out:
|
||||
LOG.debug(_('Found something in get_fs_uuid'))
|
||||
for line in out.split('\n'):
|
||||
line = line.strip()
|
||||
LOG.debug(_('Examining line: %s') % line)
|
||||
result = re.search(
|
||||
'[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}',
|
||||
line)
|
||||
if result:
|
||||
LOG.debug(
|
||||
_('Found the FS UUID for: %(device)s It is: %(uuid)s') %
|
||||
{'device': device, 'uuid': result.group(0)})
|
||||
return result.group(0)
|
||||
return None
|
||||
|
||||
|
||||
def get_vcpu_total():
|
||||
"""Get vcpu number of physical computer.
|
||||
|
||||
:returns: the number of cpu core.
|
||||
|
||||
"""
|
||||
LOG.debug(_('Getting cpu count'))
|
||||
# On certain platforms, this will raise a NotImplementedError.
|
||||
try:
|
||||
cpu_count = multiprocessing.cpu_count()
|
||||
LOG.debug(_('Got cpu count: %s') % cpu_count)
|
||||
return cpu_count
|
||||
|
||||
except NotImplementedError:
|
||||
LOG.warn(_("Cannot get the number of cpu, because this "
|
||||
"function is not implemented for this platform. "
|
||||
"This error can be safely ignored for now."))
|
||||
return 0
|
||||
|
||||
|
||||
def get_cpuinfo():
|
||||
"""
|
||||
Gather detailed statistics on the host's cpus
|
||||
"""
|
||||
|
||||
if sys.platform.upper() not in ['LINUX2', 'LINUX3']:
|
||||
return 0
|
||||
|
||||
cpuinfo = dict()
|
||||
cpuinfo['features'] = list()
|
||||
|
||||
# Gather the per core/hyperthread info for accumulation
|
||||
for line in open('/proc/cpuinfo').readlines():
|
||||
# Do some cleaning up to make searching for keys easier
|
||||
line = line.split(':')
|
||||
line[0] = line[0].strip().replace(' ', '_')
|
||||
if len(line) > 1:
|
||||
line[1] = line[1].strip()
|
||||
|
||||
if line[0] == 'vendor_id':
|
||||
cpuinfo['vendor'] = line[1]
|
||||
|
||||
if line[0] == 'model_name':
|
||||
cpuinfo['model'] = line[1]
|
||||
|
||||
if line[0] == 'flags':
|
||||
cpuinfo['features'] += line[1].split()
|
||||
|
||||
cpuinfo['arch'] = platform.uname()[4]
|
||||
|
||||
# dedupe the features
|
||||
cpuinfo['features'] = list(set(cpuinfo['features']))
|
||||
|
||||
return cpuinfo
|
||||
|
||||
|
||||
def get_iscsi_initiator():
|
||||
"""
|
||||
This is a basic implementation of the iscsi stuff needed for external
|
||||
volumes. This should live in a utility module when the openvz driver is
|
||||
broken up.
|
||||
"""
|
||||
# code borrowed from libvirt utils
|
||||
contents = utils.read_file_as_root('/etc/iscsi/initiatorname.iscsi')
|
||||
for l in contents.split('\n'):
|
||||
if l.startswith('InitiatorName='):
|
||||
return l[l.index('=') + 1:].strip()
|
||||
|
||||
|
||||
def get_cpuunits_capability():
|
||||
"""
|
||||
Use openvz tools to discover the total processing capability of the
|
||||
host node. This is done using the vzcpucheck utility.
|
||||
|
||||
Run the command:
|
||||
|
||||
vzcpucheck
|
||||
|
||||
If this fails to run an exception is raised because the output of this
|
||||
method is required to calculate the overall bean count available on the
|
||||
host to be carved up for guests to use.
|
||||
"""
|
||||
result = {}
|
||||
out = execute('vzcpucheck', run_as_root=True)
|
||||
for line in out.splitlines():
|
||||
line = line.split()
|
||||
if len(line) > 0:
|
||||
if line[0] == 'Power':
|
||||
LOG.debug(_('Power of host: %s') % line[4])
|
||||
result['total'] = int(line[4])
|
||||
elif line[0] == 'Current':
|
||||
LOG.debug(_('Current cpuunits subscribed: %s') % line[3])
|
||||
result['subscribed'] = int(line[3])
|
||||
|
||||
if len(result.keys()) == 2:
|
||||
return result
|
||||
else:
|
||||
raise exception.InvalidCPUInfo(
|
||||
_("Cannot determine the CPUUNITS for host"))
|
||||
|
||||
|
||||
def get_vcpu_used():
|
||||
"""
|
||||
OpenVz doesn't have the concept of VCPUs but we can approximate
|
||||
what they would be by looking at the percentage of subscribed
|
||||
cpuunits.
|
||||
|
||||
:returns: The total number of vcpu that currently used.
|
||||
|
||||
"""
|
||||
|
||||
cpuunits = get_cpuunits_capability()
|
||||
cpus = get_vcpu_total()
|
||||
pct_cpuunits_used = float(cpuunits['subscribed']) / cpuunits['total']
|
||||
return int(cpus * pct_cpuunits_used)
|
||||
|
||||
|
||||
def get_memory_mb_total():
|
||||
"""Get the total memory size(MB) of physical computer.
|
||||
|
||||
:returns: the total amount of memory(MB).
|
||||
|
||||
"""
|
||||
|
||||
if sys.platform.upper() not in ['LINUX2', 'LINUX3']:
|
||||
return 1
|
||||
|
||||
meminfo = open('/proc/meminfo').read().split()
|
||||
idx = meminfo.index(u'MemTotal:')
|
||||
# transforming kb to mb.
|
||||
return int(meminfo[idx + 1]) / 1024
|
||||
|
||||
|
||||
def get_memory_mb_used(instance_id=None, block_size=4096):
|
||||
"""
|
||||
Get the free memory size(MB) of physical computer.
|
||||
|
||||
:returns: the total committed of memory(MB).
|
||||
|
||||
"""
|
||||
|
||||
if instance_id:
|
||||
cmd = "vzlist -H -o ctid,privvmpages.l %s" % instance_id
|
||||
else:
|
||||
cmd = "vzlist --all -H -o ctid,privvmpages.l"
|
||||
|
||||
cmd = cmd.split()
|
||||
total_used_mb = 0
|
||||
out = execute(*cmd, run_as_root=True, raise_on_error=False)
|
||||
if out:
|
||||
for line in out.splitlines():
|
||||
line = line.split()
|
||||
total_used_mb += ((int(line[1]) * block_size) / 1024 ** 2)
|
||||
|
||||
return total_used_mb
|
||||
|
||||
|
||||
def get_local_gb(path):
|
||||
"""
|
||||
Get the total hard drive space at <path>
|
||||
"""
|
||||
|
||||
hddinfo = os.statvfs(path)
|
||||
total = hddinfo.f_frsize * hddinfo.f_blocks
|
||||
free = hddinfo.f_frsize * hddinfo.f_bavail
|
||||
used = hddinfo.f_frsize * (hddinfo.f_blocks - hddinfo.f_bfree)
|
||||
return {'total': total, 'free': free, 'used': used}
|
||||
|
||||
|
||||
def get_local_gb_total():
|
||||
"""
|
||||
Get the total hdd size(GB) of physical computer.
|
||||
|
||||
:returns:
|
||||
The total amount of HDD(GB).
|
||||
Note that this value shows a partition where
|
||||
OVZ_VE_PRIVATE_DIR is.
|
||||
|
||||
"""
|
||||
|
||||
return get_local_gb(CONF.ovz_ve_private_dir)['total'] / (1024 ** 3)
|
||||
|
||||
|
||||
def get_local_gb_used():
|
||||
"""
|
||||
Get the total used disk space on the host computer.
|
||||
|
||||
:returns:
|
||||
The total amount of HDD(GB)
|
||||
Note that this value show a partition where
|
||||
OVZ_VE_PRIVATE_DIR is.
|
||||
"""
|
||||
|
||||
return get_local_gb(CONF.ovz_ve_private_dir)['used'] / (1024 ** 3)
|
||||
|
||||
|
||||
def get_hypervisor_type():
|
||||
"""
|
||||
Just return the type of hypervisor we are using. This will always
|
||||
be 'openvz'
|
||||
"""
|
||||
|
||||
return 'openvz'
|
||||
|
||||
|
||||
def get_hypervisor_version():
|
||||
"""
|
||||
Since the version of the hypervisor is really determined by the kernel
|
||||
it is running lets just return that for version. I don't think it
|
||||
matters at this point and this is the most accurate representation of
|
||||
what *version* we are running.
|
||||
"""
|
||||
|
||||
return platform.uname()[2]
|
||||
|
||||
|
||||
def make_dir(path):
|
||||
"""
|
||||
This is the method that actually creates directories. This is used by
|
||||
make_path and can be called directly as a utility to create
|
||||
directories.
|
||||
|
||||
Run the command:
|
||||
|
||||
mkdir -p <path>
|
||||
|
||||
If this doesn't run an exception is raised as this path creation is
|
||||
required to ensure that other file operations are successful. Such
|
||||
as creating the path for a file yet to be created.
|
||||
"""
|
||||
if not os.path.exists(path):
|
||||
execute('mkdir', '-p', path, run_as_root=True)
|
||||
|
||||
|
||||
def delete_path(path):
|
||||
"""
|
||||
After a volume has been detached and the mount statements have been
|
||||
removed from the mount configuration for a container we want to remove
|
||||
the paths created on the host system so as not to leave orphaned files
|
||||
and directories on the system.
|
||||
|
||||
This runs a command like:
|
||||
sudo rmdir /mnt/100/var/lib/mysql
|
||||
"""
|
||||
try:
|
||||
execute('rmdir', path, run_as_root=True)
|
||||
return True
|
||||
except exception.InstanceUnacceptable:
|
||||
return False
|
||||
|
||||
|
||||
def set_permissions(filename, permissions):
|
||||
"""
|
||||
Because nova runs as an unprivileged user we need a way to mangle
|
||||
permissions on files that may be owned by root for manipulation
|
||||
|
||||
Run the command:
|
||||
|
||||
chmod <permissions> <filename>
|
||||
|
||||
If this doesn't run an exception is raised because the permissions not
|
||||
being set to what the application believes they are set to can cause
|
||||
a failure of epic proportions.
|
||||
"""
|
||||
execute('chmod', permissions, filename, run_as_root=True)
|
||||
|
||||
|
||||
def format_system_metadata(metadata):
|
||||
"""
|
||||
System metadata returned by conductor contains data that is irrelevant to
|
||||
OpenVz and the nova driver. This method sanitizes this information down
|
||||
to key / value pairs in a list
|
||||
|
||||
:param metadata: instance['system_metadata']
|
||||
:return: dict
|
||||
"""
|
||||
fmt_meta = dict()
|
||||
if metadata:
|
||||
for item in metadata:
|
||||
meta_item = dict()
|
||||
for key, value in item.iteritems():
|
||||
if key in ['key', 'value']:
|
||||
meta_item[key] = value
|
||||
fmt_meta[meta_item['key']] = meta_item['value']
|
||||
return fmt_meta
|
||||
|
||||
|
||||
def save_instance_metadata(instance_id, key, value, fail_on_dupe_key=False):
|
||||
"""
|
||||
Simple wrapper for saving data to the db as metadata for an instance
|
||||
|
||||
:param instance_id: Instance id to add metadata to
|
||||
:param key: Key to save to db
|
||||
:param value: Value of key to save to db
|
||||
"""
|
||||
LOG.debug(
|
||||
_('Beginning saving instance metadata for '
|
||||
'%(instance_id)s: {%(key)s: %(value)s}') % locals())
|
||||
admin_context = context.get_admin_context()
|
||||
LOG.debug(_('Got admin context'))
|
||||
try:
|
||||
instance = conductor.instance_get(admin_context, instance_id)
|
||||
LOG.debug(_('Fetched instance: %s') % instance)
|
||||
LOG.debug(_('Fixing system_metadata'))
|
||||
sysmeta = format_system_metadata(instance['system_metadata'])
|
||||
if key in sysmeta:
|
||||
if fail_on_dupe_key:
|
||||
raise KeyError(_('Key %(key)s is set already') % locals())
|
||||
sysmeta[key] = value
|
||||
conductor.instance_update(
|
||||
admin_context, instance['uuid'], system_metadata=sysmeta)
|
||||
LOG.debug(_('Updated instance metadata'))
|
||||
except exception.InstanceNotFound as err:
|
||||
LOG.error(_('Instance not in the database: %s') % instance_id)
|
||||
LOG.error(_('Consistency check is needed, orphaned instance: %s') %
|
||||
instance_id)
|
||||
|
||||
|
||||
def read_instance_metadata(instance_id):
|
||||
"""
|
||||
Read the metadata in the database for a given instance
|
||||
|
||||
:param instance_id: Instance to read all metadata for from the db
|
||||
:returns: dict()
|
||||
"""
|
||||
LOG.debug(_('Beginning read_instance_metadata: %s') % instance_id)
|
||||
admin_context = context.get_admin_context()
|
||||
LOG.debug(_('Got admin context for db access'))
|
||||
try:
|
||||
instance = conductor.instance_get(admin_context, instance_id)
|
||||
LOG.debug(_('Fetched instance'))
|
||||
LOG.debug(
|
||||
_('Results from read_instance_metadata: %s') %
|
||||
instance['system_metadata'])
|
||||
return format_system_metadata(instance['system_metadata'])
|
||||
except exception.InstanceNotFound:
|
||||
LOG.error(_('Instance not in the database: %s') % instance_id)
|
||||
LOG.error(_('Consistency check is needed, orphaned instance: %s') %
|
||||
instance_id)
|
||||
return dict()
|
||||
|
||||
|
||||
def read_all_instance_metadata():
|
||||
"""
|
||||
Fetch a list of all instance ids on the local host and for each get
|
||||
the associated metadata and return it in a dictionary
|
||||
|
||||
:returns dict():
|
||||
"""
|
||||
instances_metadata = {}
|
||||
out = execute(
|
||||
'vzlist', '-H', '-o', 'ctid', '--all', raise_on_error=False,
|
||||
run_as_root=True)
|
||||
if out:
|
||||
for line in out.splitlines():
|
||||
instance_id = line.strip()
|
||||
instances_metadata[instance_id] = read_instance_metadata(
|
||||
instance_id)
|
||||
|
||||
return instances_metadata
|
||||
|
||||
|
||||
def remove_instance_metadata_key(instance_id, key):
|
||||
"""
|
||||
Remove a specific key from an instance's metadata
|
||||
"""
|
||||
LOG.debug(_('Getting an admin context for remove_instance_metadata_key'))
|
||||
admin_context = context.get_admin_context()
|
||||
LOG.debug(_('Got admin context, fetching instance ref'))
|
||||
instance = conductor.instance_get(admin_context, instance_id)
|
||||
LOG.debug(_('Got instance ref, beginning deletion of metadata'))
|
||||
sysmeta = format_system_metadata(instance['system_metadata'])
|
||||
if key in sysmeta:
|
||||
LOG.debug(_('Key %s exists in system_metadata') % key)
|
||||
sysmeta.pop(key)
|
||||
conductor.instance_update(
|
||||
admin_context, instance['uuid'], system_metadata=sysmeta)
|
||||
else:
|
||||
LOG.debug(_('Key %s does not exist in system_metadata') % key)
|
||||
|
||||
|
||||
def remove_instance_metadata(instance_id):
|
||||
"""
|
||||
Clean up instance metadata for an instance
|
||||
"""
|
||||
openvz_metadata_keys = ('tc_id',)
|
||||
try:
|
||||
for key in openvz_metadata_keys:
|
||||
LOG.debug(_('Removing metadata key: %s') % key)
|
||||
remove_instance_metadata_key(instance_id, key)
|
||||
|
||||
LOG.debug(_('Deleted metadata for %(instance_id)s') % locals())
|
||||
return True
|
||||
except exception.InstanceNotFound as err:
|
||||
LOG.error(_('Instance not in the database: %s') % instance_id)
|
||||
LOG.error(_('Consistency check is needed, orphaned instance: %s') %
|
||||
instance_id)
|
||||
return False
|
||||
|
||||
|
||||
def generate_network_dict(instance_id, network_info):
|
||||
interfaces = list()
|
||||
interface_num = -1
|
||||
for (network, mapping) in network_info:
|
||||
if mapping['ips']:
|
||||
interface_num += 1
|
||||
|
||||
#TODO(imsplitbit): make this work for ipv6
|
||||
address_v6 = None
|
||||
gateway_v6 = None
|
||||
netmask_v6 = None
|
||||
if CONF.use_ipv6:
|
||||
address_v6 = mapping['ip6s'][0]['ip']
|
||||
netmask_v6 = mapping['ip6s'][0]['netmask']
|
||||
gateway_v6 = mapping['gateway6']
|
||||
|
||||
interface_info = {
|
||||
'id': instance_id,
|
||||
'interface_number': interface_num,
|
||||
'bridge': network['bridge'],
|
||||
'name': 'eth%d' % interface_num,
|
||||
'mac': mapping['mac'],
|
||||
'address': mapping['ips'][0]['ip'],
|
||||
'netmask': mapping['ips'][0]['netmask'],
|
||||
'gateway': mapping['gateway'],
|
||||
'broadcast': mapping['broadcast'],
|
||||
'dns': ' '.join(mapping['dns']),
|
||||
'address_v6': address_v6,
|
||||
'gateway_v6': gateway_v6,
|
||||
'netmask_v6': netmask_v6
|
||||
}
|
||||
interface_info['vz_host_if'] = ('veth%s.%s' %
|
||||
(interface_info['id'],
|
||||
interface_info['name']))
|
||||
interfaces.append(interface_info)
|
||||
return interfaces
|
||||
|
||||
|
||||
def move(src, dest):
|
||||
"""
|
||||
utility method to wrap up moving a file or directory from one place to
|
||||
another.
|
||||
"""
|
||||
try:
|
||||
execute('mv', src, dest, run_as_root=True)
|
||||
return True
|
||||
except exception.InstanceUnacceptable as err:
|
||||
LOG.error(_('Error moving file %s') % src)
|
||||
LOG.error(err)
|
||||
return False
|
||||
|
||||
|
||||
def copy(src, dest):
|
||||
"""
|
||||
utility method wrap up copying files from one directory to another
|
||||
"""
|
||||
try:
|
||||
execute('cp', src, dest, run_as_root=True)
|
||||
return True
|
||||
except exception.InstanceUnacceptable as err:
|
||||
LOG.error(_('Error copying file %s') % src)
|
||||
LOG.error(err)
|
||||
return False
|
||||
|
||||
|
||||
def tar(source, target_file, working_dir=None, skip_list=[]):
|
||||
"""
|
||||
wrapper for tar for making backup archives
|
||||
"""
|
||||
cmd = ['tar', '-cf', target_file]
|
||||
if working_dir:
|
||||
cmd += ['-C', working_dir]
|
||||
|
||||
if skip_list:
|
||||
for exclude_path in skip_list:
|
||||
cmd += ['--exclude', '%s/*' % exclude_path]
|
||||
|
||||
cmd.append(source)
|
||||
try:
|
||||
execute(*cmd, run_as_root=True)
|
||||
return True
|
||||
except exception.InstanceUnacceptable as err:
|
||||
LOG.error(_('Error tarring: %s') % source)
|
||||
LOG.error(err)
|
||||
return False
|
||||
|
||||
|
||||
def untar(target_file, destination):
|
||||
"""
|
||||
wrapper for untarring files into a destination directory
|
||||
"""
|
||||
try:
|
||||
execute('tar', '-xf', target_file, '-C', destination, run_as_root=True)
|
||||
return True
|
||||
except exception.InstanceUnacceptable as err:
|
||||
LOG.error(_('Error untarring: %s') % target_file)
|
||||
LOG.error(err)
|
||||
return False
|
473
nova/virt/openvz/volume.py
Normal file
473
nova/virt/openvz/volume.py
Normal file
@ -0,0 +1,473 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
A driver specific to OpenVz as the support for Ovz in libvirt
|
||||
is sketchy at best.
|
||||
"""
|
||||
|
||||
import glob
|
||||
from nova import exception
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.virt.openvz import file as ovzfile
|
||||
from nova.virt.openvz import utils as ovz_utils
|
||||
import os
|
||||
from oslo.config import cfg
|
||||
import re
|
||||
import time
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opt(
|
||||
cfg.IntOpt('ovz_volume_settle_num_tries',
|
||||
default=10,
|
||||
help='Number of attempts before we determine that a device '
|
||||
'has had time to settle and we are beyond a reasonable '
|
||||
'wait time'))
|
||||
LOG = logging.getLogger('nova.virt.openvz.volume')
|
||||
|
||||
|
||||
class OVZVolume(object):
|
||||
"""
|
||||
This Class is a helper class to manage the mount and umount files
|
||||
for a given container.
|
||||
"""
|
||||
def __init__(self, instance_id, mountpoint, dev):
|
||||
"""
|
||||
Setup critical information for attaching/detaching volumes from
|
||||
instance_id.
|
||||
|
||||
:param instance_id:
|
||||
:param mountpoint: connection_info['data']['mount_device']
|
||||
:param dev:
|
||||
"""
|
||||
#TODO(imsplitbit): Need to make this happen while supporting block
|
||||
# devices and externally mounted filesystems at the same time.
|
||||
# Currently it does not.
|
||||
self.instance_id = instance_id
|
||||
self.mountpoint = mountpoint
|
||||
self.device = dev
|
||||
|
||||
def attach(self):
|
||||
"""
|
||||
Public method for attaching volumes either by creating bind mounts or
|
||||
attaching a raw device to the instance.
|
||||
"""
|
||||
self._attach_raw_devices()
|
||||
|
||||
def detach(self):
|
||||
"""
|
||||
Public method for detaching volumes from an instance.
|
||||
"""
|
||||
self._detach_raw_devices()
|
||||
|
||||
def device_name(self):
|
||||
"""
|
||||
Generates an device name. If no volume driver is used this will
|
||||
assist in generating an error should things get this far.
|
||||
"""
|
||||
return
|
||||
|
||||
def _check_device_exists(self, device_path):
|
||||
"""Check that the device path exists.
|
||||
|
||||
Verify that the device path has actually been created and can report
|
||||
it's size, only then can it be available for formatting, retry
|
||||
num_tries to account for the time lag.
|
||||
"""
|
||||
try:
|
||||
ovz_utils.execute('blockdev', '--getsize64', device_path,
|
||||
attempts=CONF.ovz_system_num_tries,
|
||||
run_as_root=True)
|
||||
except exception.ProcessExecutionError:
|
||||
raise exception.InvalidDevicePath(path=device_path)
|
||||
|
||||
def _find_device(self):
|
||||
try:
|
||||
return os.path.realpath(self.device_name())
|
||||
|
||||
except (OSError, AttributeError) as err:
|
||||
raise exception.VolumeNotFound(
|
||||
_('Device cannot be found %s') % err)
|
||||
|
||||
def _list_host_devices(self):
|
||||
dev = self._find_device()
|
||||
LOG.debug(_('Device name: %(dev)s') % locals())
|
||||
devs = list()
|
||||
listing = glob.glob('/dev/sd*')
|
||||
|
||||
for device in listing:
|
||||
LOG.debug(_('Trying to match against %(device)s') % locals())
|
||||
m = re.match('^(?P<device>%s)(?P<part>\\d*)$' % dev, device)
|
||||
if m:
|
||||
LOG.debug(_('Matched device %(device)s') % locals())
|
||||
devs.append(m.string)
|
||||
|
||||
LOG.debug(_('Devices: %(devs)s') % locals())
|
||||
return devs
|
||||
|
||||
def _get_major_minor(self, device):
|
||||
try:
|
||||
dev = os.stat(device)
|
||||
maj_min = dict()
|
||||
maj_min['major'] = os.major(dev.st_rdev)
|
||||
maj_min['minor'] = os.minor(dev.st_rdev)
|
||||
LOG.debug(
|
||||
_('Major Minor numbers for %(dev)s: %(maj_min)s') % locals())
|
||||
return maj_min
|
||||
|
||||
except OSError as err:
|
||||
LOG.error(_('Device error in OpenVz volumes: %(err)s') % locals())
|
||||
raise exception.VolumeNotFound(
|
||||
_('Unable to find device: %(device)s') % locals())
|
||||
|
||||
def _let_disk_settle(self):
|
||||
"""
|
||||
Check to see if the device_name is available in /dev/disk/by-path. If
|
||||
it isn't then we'll wait a bit and retry. Disks don't just instantly
|
||||
show up in /dev so this gives a little bit of wait time for things
|
||||
to show up.
|
||||
|
||||
:return:
|
||||
"""
|
||||
ovz_utils.execute(
|
||||
'ls', self.device_name(), run_as_root=True,
|
||||
attempts=CONF.ovz_volume_settle_num_tries, delay_on_retry=True)
|
||||
|
||||
def _list_partition_table(self):
|
||||
"""
|
||||
There is a bit of work done here to make a nice partition table
|
||||
structure for later use. Currently the only thing we care about is
|
||||
if there is already a partition table but we may need this info soon
|
||||
so the legwork is done.
|
||||
|
||||
:return: dict
|
||||
"""
|
||||
# Now we should be able to move on to goodness.
|
||||
dev = self._find_device()
|
||||
LOG.debug(_('Device name: %(dev)s') % locals())
|
||||
part_table = dict()
|
||||
try:
|
||||
out = ovz_utils.execute('sfdisk', '-d', dev, run_as_root=True)
|
||||
|
||||
for line in out.splitlines():
|
||||
line = line.split(':')
|
||||
dev = line[0].strip()
|
||||
LOG.debug(_('Device from part table: %(dev)s') % locals())
|
||||
|
||||
if dev.startswith('/dev'):
|
||||
line = line[1].split(',')
|
||||
|
||||
LOG.debug(_('Line from part table: %(line)s') % locals())
|
||||
part_table[dev] = {}
|
||||
part_table[dev]['start'] = line[0].split('=')[1]
|
||||
part_table[dev]['size'] = line[1].split('=')[1]
|
||||
part_table[dev]['id'] = line[2].split('=')[1]
|
||||
|
||||
LOG.debug(
|
||||
_('Device info from part table: %s') % part_table[dev])
|
||||
|
||||
if len(line) > 3:
|
||||
part_table[dev]['flags'] = line[3]
|
||||
LOG.debug(_('Disk flags exist for %(dev)s') % locals())
|
||||
|
||||
LOG.debug(_('Partition Table: %(part_table)s') % locals())
|
||||
return part_table
|
||||
|
||||
except exception.InstanceUnacceptable as err:
|
||||
LOG.warn(_('No partition table on %(dev)s') % locals())
|
||||
return part_table
|
||||
|
||||
def _partition_disk(self):
|
||||
"""
|
||||
Openvz requires that we pass in not only the root device but if the
|
||||
user is to access partitions on that disk we need also to pass in
|
||||
the major/minor numbers for each partition. Given that requirement
|
||||
we're making the assumption for now that volumes are going to be
|
||||
presented as one partition. This will partition the root device so
|
||||
we can later get the information on both the major and minor numbers.
|
||||
|
||||
If the disk contains a valid partition table then we just return
|
||||
|
||||
:return:
|
||||
"""
|
||||
# We need potentially need to give the device time to settle
|
||||
self._let_disk_settle()
|
||||
|
||||
dev = self._find_device()
|
||||
part_table = self._list_partition_table()
|
||||
LOG.debug(_('Partition table for %(dev)s: %(part_table)s') % locals())
|
||||
|
||||
# If the volume is partitioned we're assuming this is a reconnect of
|
||||
# a volume and we won't partition anything.
|
||||
if not part_table:
|
||||
commands = ['o,w', 'n,p,1,,,t,1,83,w']
|
||||
|
||||
for command in commands:
|
||||
command = command.replace(',', '\n')
|
||||
ovz_utils.execute(
|
||||
'fdisk', dev, process_input=command, run_as_root=True)
|
||||
time.sleep(3)
|
||||
|
||||
def _get_vz_device_list(self):
|
||||
"""
|
||||
Grab existing device information from vz config and create new
|
||||
device string for device connections.
|
||||
|
||||
:return:
|
||||
"""
|
||||
devices = list()
|
||||
config = os.path.normpath(
|
||||
'%s/%s.conf' % (CONF.ovz_config_dir, self.instance_id))
|
||||
config = ovzfile.OVZFile(config, 644)
|
||||
config.read()
|
||||
|
||||
for line in config.contents:
|
||||
if '=' in line:
|
||||
line = line.split('=')
|
||||
if line[0] == 'DEVICES':
|
||||
devices = line[1].replace('\"', '').strip().split()
|
||||
|
||||
return devices
|
||||
|
||||
def _ovz_block_dev_name_generator(self, major, minor, privs):
|
||||
"""
|
||||
OpenVz requires a specific format for granting or revoking block
|
||||
device privileges. This tool will create those names.
|
||||
|
||||
:param major:
|
||||
:param minor:
|
||||
:param privs:
|
||||
:return:
|
||||
"""
|
||||
return 'b:%(major)s:%(minor)s:%(privs)s' % locals()
|
||||
|
||||
def _attach_raw_devices(self):
|
||||
"""
|
||||
If there are no partitions on the disk already, partition it with 1
|
||||
partition. Then use openvz tools and mknod to attach these devices
|
||||
to the container.
|
||||
|
||||
:return:
|
||||
"""
|
||||
LOG.debug(_('Partitioning disk in _attach_raw_devices'))
|
||||
self._partition_disk()
|
||||
|
||||
# storage for block device major/minor pairs for setting in
|
||||
# the container config file.
|
||||
bdevs = []
|
||||
for dev in self._list_host_devices():
|
||||
maj_min = self._get_major_minor(dev)
|
||||
bdevs.append(
|
||||
self._ovz_block_dev_name_generator(
|
||||
maj_min['major'], maj_min['minor'], 'rw'))
|
||||
|
||||
m = re.match('^(?P<device>/[a-z]+/[a-z]+)(?P<part>\\d*)$', dev)
|
||||
device_name = self.mountpoint
|
||||
|
||||
if m and m.group('part'):
|
||||
device_name = '%s%s' % (self.mountpoint, m.group('part'))
|
||||
|
||||
# Remove the device if it happens to exist. This prevents stale
|
||||
# devices with old major/minor numbers existing.
|
||||
self._delete_device(device_name)
|
||||
self._mknod(maj_min['major'], maj_min['minor'], device_name)
|
||||
|
||||
if bdevs:
|
||||
self._add_block_devices(bdevs)
|
||||
|
||||
def _attach_raw_device(self, major, minor):
|
||||
ovz_utils.execute(
|
||||
'vzctl', 'set', self.instance_id,
|
||||
'--devices', 'b:%s:%s:rw' % (major, minor),
|
||||
run_as_root=True)
|
||||
|
||||
def _add_block_devices(self, new_devices):
|
||||
"""
|
||||
New devices should be a list that looks like this:
|
||||
|
||||
['b:8:32:rw', 'b:8:33:rw']
|
||||
|
||||
:param new_devices:
|
||||
:return:
|
||||
"""
|
||||
devices = self._get_vz_device_list()
|
||||
|
||||
for dev in new_devices:
|
||||
if dev not in devices:
|
||||
devices.append(dev)
|
||||
|
||||
self._set_block_devices(devices)
|
||||
|
||||
def _del_block_devices(self, new_devices):
|
||||
"""
|
||||
New devices should be a list that looks like this:
|
||||
|
||||
['b:8:32:rw', 'b:8:33:rw']
|
||||
|
||||
:param new_devices:
|
||||
:return:
|
||||
"""
|
||||
devices = self._get_vz_device_list()
|
||||
|
||||
for dev in new_devices:
|
||||
if dev in devices:
|
||||
devices.remove(dev)
|
||||
|
||||
self._set_block_devices(devices)
|
||||
|
||||
def _detach_remaining_block_devices(self):
|
||||
"""
|
||||
If the final ephemeral volume is detached from the container we issue
|
||||
a privs :none for the remaining devices in the conf file for the
|
||||
container before we remove those entries from the conf file. This is
|
||||
necessary to ensure the removal of the devices from the in memory
|
||||
device list for openvz.
|
||||
|
||||
:return:
|
||||
"""
|
||||
devices = self._get_vz_device_list()
|
||||
cmd = ['vzctl', 'set', self.instance_id]
|
||||
|
||||
for dev in devices:
|
||||
dev = dev.replace(':rw', ':none')
|
||||
cmd.append('--devices')
|
||||
cmd.append(dev)
|
||||
|
||||
ovz_utils.execute(*cmd, run_as_root=True)
|
||||
|
||||
def _set_block_devices(self, devices):
|
||||
"""
|
||||
Run the command necessary to save the block device permissions to
|
||||
the container config file so that they persist on reboot.
|
||||
|
||||
:param devices:
|
||||
:return:
|
||||
"""
|
||||
cmd = ['vzctl', 'set', self.instance_id, '--save']
|
||||
if devices:
|
||||
for dev in devices:
|
||||
cmd.append('--devices')
|
||||
cmd.append(dev)
|
||||
|
||||
ovz_utils.execute(*cmd, run_as_root=True)
|
||||
else:
|
||||
# if the device list is empty this means that it's the last device
|
||||
# attached to the container and we need to run some special case
|
||||
# code to remove that device.
|
||||
self._detach_remaining_block_devices()
|
||||
self._remove_all_device_entries()
|
||||
|
||||
def _remove_all_device_entries(self):
|
||||
"""
|
||||
When there are no devices left to remove we need to remove the
|
||||
DEVICES line from the CTID.conf file. This cannot be achieved by
|
||||
a vzctl command so the file has to be manipulated and saved.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
filename = '%s/%s.conf' % (CONF.ovz_config_dir, self.instance_id)
|
||||
filename = os.path.normpath(filename)
|
||||
ct_conf = ovzfile.OVZFile(filename, 644)
|
||||
ct_conf.read()
|
||||
with ct_conf:
|
||||
for line in ct_conf.contents:
|
||||
if "DEVICES" in line:
|
||||
ct_conf.contents.remove(line)
|
||||
ct_conf.write()
|
||||
|
||||
def _detach_raw_devices(self):
|
||||
"""
|
||||
Remove the associated devices from the container.
|
||||
|
||||
:return:
|
||||
"""
|
||||
LOG.debug(_('Detaching raw devices'))
|
||||
|
||||
# storage for block device major/minor pairs for setting in
|
||||
# the container config file.
|
||||
bdevs_conf = list()
|
||||
for dev in self._list_host_devices():
|
||||
maj_min = self._get_major_minor(dev)
|
||||
bdevs_conf.append(
|
||||
self._ovz_block_dev_name_generator(
|
||||
maj_min['major'], maj_min['minor'], 'rw'))
|
||||
|
||||
# TODO(imsplitbit): research to see if running this remove command
|
||||
# adhoc is necessary or if just removing the device from the config
|
||||
# file is adequate.
|
||||
self._detach_raw_device(maj_min['major'], maj_min['minor'])
|
||||
m = re.match('^(?P<device>/[a-z]+/[a-z]+)(?P<part>\\d*)$', dev)
|
||||
device_name = self.mountpoint
|
||||
|
||||
if m and m.group('part'):
|
||||
device_name = '%s%s' % (self.mountpoint, m.group('part'))
|
||||
|
||||
self._delete_device(device_name)
|
||||
|
||||
if bdevs_conf:
|
||||
self._del_block_devices(bdevs_conf)
|
||||
|
||||
def _detach_raw_device(self, major, minor):
|
||||
"""
|
||||
Remove perms from the container for a device based on major and minor
|
||||
numbers.
|
||||
|
||||
:param major:
|
||||
:param minor:
|
||||
:return:
|
||||
"""
|
||||
ovz_utils.execute(
|
||||
'vzctl', 'set', self.instance_id, '--devices',
|
||||
'b:%s:%s:none' % (major, minor), run_as_root=True)
|
||||
|
||||
def _mknod(self, major, minor, device_name, dev_type='b'):
|
||||
"""
|
||||
Because openvz doesn't support device aliases we manually create
|
||||
a block device sharing the same major and minor numbers as the device
|
||||
on the host. This device should be created in the openvz chrooted
|
||||
/dev directory so as to allow it to be cleaned up upon deletion.
|
||||
|
||||
:param major:
|
||||
:param minor:
|
||||
:param dev_type:
|
||||
:return: None
|
||||
"""
|
||||
good_types = ['b']
|
||||
if dev_type not in good_types:
|
||||
raise TypeError(
|
||||
_('Cannot make device of type "%(dev_type)s"') % locals())
|
||||
|
||||
device_path = '%s/%s/%s' % (
|
||||
CONF.ovz_ve_private_dir, self.instance_id, device_name)
|
||||
device_path = os.path.normpath(device_path)
|
||||
ovz_utils.execute(
|
||||
'mknod', device_path, dev_type, major, minor, run_as_root=True)
|
||||
|
||||
def _delete_device(self, device):
|
||||
"""
|
||||
Utility method for removing a device from inside the container. This
|
||||
is to happen before you create a device because the existing device
|
||||
may or may not have the proper major/minor numbers. This is obviously
|
||||
done on volume detach as well.
|
||||
|
||||
:param device:
|
||||
:return:
|
||||
"""
|
||||
device = '%s/%s/%s' % (
|
||||
CONF.ovz_ve_private_dir, self.instance_id, device)
|
||||
device = os.path.normpath(device)
|
||||
ovz_utils.execute('rm', '-f', device, run_as_root=True)
|
0
nova/virt/openvz/volume_drivers/__init__.py
Normal file
0
nova/virt/openvz/volume_drivers/__init__.py
Normal file
164
nova/virt/openvz/volume_drivers/iscsi.py
Normal file
164
nova/virt/openvz/volume_drivers/iscsi.py
Normal file
@ -0,0 +1,164 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
A driver specific to OpenVz as the support for Ovz in libvirt
|
||||
is sketchy at best.
|
||||
"""
|
||||
from nova import exception
|
||||
from nova.openstack.common import lockutils
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.virt.openvz import utils as ovz_utils
|
||||
from nova.virt.openvz import volume as ovzvolume
|
||||
from oslo.config import cfg
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opt(
|
||||
cfg.IntOpt('ovz_iscsiadm_num_tries',
|
||||
default=1,
|
||||
help='Number of attempts to make an iscsi connection'))
|
||||
|
||||
LOG = logging.getLogger('nova.virt.openvz.volume_drivers.iscsi')
|
||||
|
||||
|
||||
class OVZISCSIStorageDriver(ovzvolume.OVZVolume):
|
||||
def __init__(self, instance_id, mountpoint, connection_data):
|
||||
"""
|
||||
Driver for openvz to connect iscsi volumes to instances
|
||||
|
||||
:param connection_data: Data from the connection_info given to the
|
||||
ovz driver
|
||||
:param instance_id: Instance ID for the volume to be attached to
|
||||
:param mountpoint: place to mount the volume within the container
|
||||
"""
|
||||
self.iscsi_properties = connection_data['data']
|
||||
super(OVZISCSIStorageDriver, self).__init__(
|
||||
instance_id, mountpoint, self.device_name())
|
||||
self.init_iscsi_device()
|
||||
|
||||
def device_name(self):
|
||||
"""
|
||||
Generates an device name based on the iSCSI parameters in the
|
||||
block device mapping
|
||||
"""
|
||||
return "/dev/disk/by-path/ip-%s-iscsi-%s-lun-%s" % \
|
||||
(self.iscsi_properties['target_portal'],
|
||||
self.iscsi_properties['target_iqn'],
|
||||
self.iscsi_properties['target_lun'])
|
||||
|
||||
def init_iscsi_device(self):
|
||||
"""
|
||||
Log into the device and setup files
|
||||
"""
|
||||
self.discover_volume()
|
||||
|
||||
def _run_iscsiadm(self, iscsi_command, raise_on_error=True):
|
||||
out = ovz_utils.execute('iscsiadm', '-m', 'node', '-T',
|
||||
self.iscsi_properties['target_iqn'],
|
||||
'-p', self.iscsi_properties['target_portal'],
|
||||
*iscsi_command, run_as_root=True,
|
||||
attempts=CONF.ovz_iscsiadm_num_tries,
|
||||
raise_on_error=raise_on_error)
|
||||
LOG.debug("iscsiadm %s: stdout=%s" %
|
||||
(iscsi_command, out))
|
||||
return out
|
||||
|
||||
def _iscsiadm_update(self, property_key, property_value,
|
||||
raise_on_error=True):
|
||||
iscsi_command = ('--op', 'update', '-n', property_key,
|
||||
'-v', property_value)
|
||||
return self._run_iscsiadm(iscsi_command, raise_on_error=raise_on_error)
|
||||
|
||||
def get_iscsi_properties_for_volume(self):
|
||||
if not self.iscsi_properties['target_discovered']:
|
||||
self._run_iscsiadm(('--op', 'new'))
|
||||
|
||||
def session_currently_connected(self):
|
||||
out = ovz_utils.execute(
|
||||
'iscsiadm', '-m', 'session', run_as_root=True,
|
||||
raise_on_error=False)
|
||||
if out:
|
||||
for line in out.splitlines():
|
||||
if self.iscsi_properties['target_iqn'] in line:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_iscsi_auth(self):
|
||||
if self.iscsi_properties.get('auth_method', None):
|
||||
self._iscsiadm_update("node.session.auth.authmethod",
|
||||
self.iscsi_properties['auth_method'])
|
||||
self._iscsiadm_update("node.session.auth.username",
|
||||
self.iscsi_properties['auth_username'])
|
||||
self._iscsiadm_update("node.session.auth.password",
|
||||
self.iscsi_properties['auth_password'])
|
||||
|
||||
@lockutils.synchronized('iscsiadm_lock', 'openvz-')
|
||||
def discover_volume(self):
|
||||
"""Discover volume on a remote host."""
|
||||
self.get_iscsi_properties_for_volume()
|
||||
self.set_iscsi_auth()
|
||||
|
||||
try:
|
||||
if not self.session_currently_connected():
|
||||
LOG.debug(_('iSCSI session for %s not connected, '
|
||||
'connecting now') %
|
||||
self.iscsi_properties['target_iqn'])
|
||||
self._run_iscsiadm(("--login",))
|
||||
LOG.debug(_('iSCSI session for %s connected') %
|
||||
self.iscsi_properties['target_iqn'])
|
||||
except exception.ProcessExecutionError as err:
|
||||
if "15 - already exists" in err.message:
|
||||
raise exception.VolumeUnattached()
|
||||
LOG.error(err)
|
||||
raise exception.VolumeNotFound(_("iSCSI device %s not found") %
|
||||
self.iscsi_properties['target_iqn'])
|
||||
|
||||
def detach(self, raise_on_error=True):
|
||||
"""Detach the volume from instance_name."""
|
||||
super(OVZISCSIStorageDriver, self).detach()
|
||||
self._detach_iscsi()
|
||||
|
||||
@lockutils.synchronized('iscsiadm_lock', 'openvz-')
|
||||
def _detach_iscsi(self):
|
||||
"""
|
||||
Detach all iscsi sessions for this volume
|
||||
|
||||
:return:
|
||||
"""
|
||||
self._iscsiadm_update("node.startup", "manual")
|
||||
self._run_iscsiadm(("--logout",), raise_on_error=False)
|
||||
self._run_iscsiadm(('--op', 'delete'), raise_on_error=False)
|
||||
|
||||
@lockutils.synchronized('iscsiadm_lock', 'openvz-')
|
||||
def rescan(self):
|
||||
"""Rescan the client storage connection."""
|
||||
|
||||
self.get_iscsi_properties_for_volume()
|
||||
try:
|
||||
LOG.debug("ISCSI Properties: %s" % self.iscsi_properties)
|
||||
self._run_iscsiadm(("--rescan",))
|
||||
return ("/dev/disk/by-path/ip-%s-iscsi-%s-lun-%s" %
|
||||
(self.iscsi_properties['target_portal'],
|
||||
self.iscsi_properties['target_iqn'],
|
||||
self.iscsi_properties['target_lun']))
|
||||
except exception.ProcessExecutionError as err:
|
||||
LOG.error(err)
|
||||
raise exception.ISCSITargetNotFoundForVolume(
|
||||
_("Error rescanning iscsi device: %s") %
|
||||
self.iscsi_properties['target_iqn'])
|
Loading…
x
Reference in New Issue
Block a user