summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--etc/nova/rootwrap.d/compute.filters6
-rw-r--r--nova/privsep/libvirt.py57
-rw-r--r--nova/tests/unit/privsep/__init__.py0
-rw-r--r--nova/tests/unit/privsep/test_libvirt.py50
-rw-r--r--nova/tests/unit/virt/libvirt/fake_libvirt_utils.py8
-rw-r--r--nova/tests/unit/virt/libvirt/test_driver.py39
-rw-r--r--nova/tests/unit/virt/libvirt/test_utils.py35
-rw-r--r--nova/virt/libvirt/driver.py51
-rw-r--r--nova/virt/libvirt/utils.py33
-rw-r--r--releasenotes/notes/privsep-queens-rootwrap-adds-907aa1bc8e3eb2ca.yaml5
10 files changed, 155 insertions, 129 deletions
diff --git a/etc/nova/rootwrap.d/compute.filters b/etc/nova/rootwrap.d/compute.filters
index 8796050455..d412446286 100644
--- a/etc/nova/rootwrap.d/compute.filters
+++ b/etc/nova/rootwrap.d/compute.filters
@@ -41,12 +41,6 @@ blockdev: RegExpFilter, blockdev, root, blockdev, (--getsize64|--flushbufs), /de
# nova/virt/libvirt/vif.py: utils.execute('tee',
tee: CommandFilter, tee, root
-# nova/virt/libvirt/utils.py: def chown(): execute('chown', owner, path,
-# nova/virt/libvirt/driver.py: 'chown', os.getuid( console_log
-# nova/virt/libvirt/driver.py: 'chown', os.getuid( console_log
-# nova/virt/libvirt/driver.py: 'chown', 'root', basepath('disk')
-chown: CommandFilter, chown, 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
diff --git a/nova/privsep/libvirt.py b/nova/privsep/libvirt.py
new file mode 100644
index 0000000000..d5dc6c8c84
--- /dev/null
+++ b/nova/privsep/libvirt.py
@@ -0,0 +1,57 @@
+# Copyright 2016 Red Hat, Inc
+# Copyright 2017 Rackspace Australia
+#
+# 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.
+
+"""
+libvirt specific routines that use the dac_admin_pctxt to bypass file-system
+checks.
+"""
+
+import errno
+import os
+
+import nova.privsep
+
+
+@nova.privsep.dac_admin_pctxt.entrypoint
+def last_bytes(path, num):
+ # NOTE(mikal): this is implemented in this contrived manner because you
+ # can't mock a decorator in python (they're loaded at file parse time,
+ # and the mock happens later).
+ with open(path, 'rb') as f:
+ return _last_bytes_inner(f, num)
+
+
+def _last_bytes_inner(file_like_object, num):
+ """Return num bytes from the end of the file, and remaining byte count.
+
+ :param file_like_object: The file to read
+ :param num: The number of bytes to return
+
+ :returns: (data, remaining)
+ """
+
+ try:
+ file_like_object.seek(-num, os.SEEK_END)
+ except IOError as e:
+ # seek() fails with EINVAL when trying to go before the start of
+ # the file. It means that num is larger than the file size, so
+ # just go to the start.
+ if e.errno == errno.EINVAL:
+ file_like_object.seek(0, os.SEEK_SET)
+ else:
+ raise
+
+ remaining = file_like_object.tell()
+ return (file_like_object.read(), remaining)
diff --git a/nova/tests/unit/privsep/__init__.py b/nova/tests/unit/privsep/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/nova/tests/unit/privsep/__init__.py
diff --git a/nova/tests/unit/privsep/test_libvirt.py b/nova/tests/unit/privsep/test_libvirt.py
new file mode 100644
index 0000000000..af8bb5a7ce
--- /dev/null
+++ b/nova/tests/unit/privsep/test_libvirt.py
@@ -0,0 +1,50 @@
+# Copyright 2016 Red Hat, Inc
+# Copyright 2017 Rackspace Australia
+#
+# 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.
+
+import os
+import six
+import tempfile
+
+import nova.privsep.libvirt
+from nova import test
+
+
+class LastBytesTestCase(test.NoDBTestCase):
+ """Test the last_bytes() utility method."""
+
+ def setUp(self):
+ super(LastBytesTestCase, self).setUp()
+ self.f = six.BytesIO(b'1234567890')
+
+ def test_truncated(self):
+ self.f.seek(0, os.SEEK_SET)
+ out, remaining = nova.privsep.libvirt._last_bytes_inner(self.f, 5)
+ self.assertEqual(out, b'67890')
+ self.assertGreater(remaining, 0)
+
+ def test_read_all(self):
+ self.f.seek(0, os.SEEK_SET)
+ out, remaining = nova.privsep.libvirt._last_bytes_inner(self.f, 1000)
+ self.assertEqual(out, b'1234567890')
+ self.assertFalse(remaining > 0)
+
+ def test_seek_too_far_real_file(self):
+ # StringIO doesn't raise IOError if you see past the start of the file.
+ with tempfile.TemporaryFile() as flo:
+ content = b'1234567890'
+ flo.write(content)
+ self.assertEqual(
+ (content, 0),
+ nova.privsep.libvirt._last_bytes_inner(flo, 1000))
diff --git a/nova/tests/unit/virt/libvirt/fake_libvirt_utils.py b/nova/tests/unit/virt/libvirt/fake_libvirt_utils.py
index e6831e3fb5..be27277b5e 100644
--- a/nova/tests/unit/virt/libvirt/fake_libvirt_utils.py
+++ b/nova/tests/unit/virt/libvirt/fake_libvirt_utils.py
@@ -82,10 +82,6 @@ def write_to_file(path, contents, umask=None):
pass
-def chown(path, owner):
- pass
-
-
def update_mtime(path):
pass
@@ -177,7 +173,3 @@ def chown_for_id_maps(path, id_maps):
def get_arch(image_meta):
return libvirt_utils.get_arch(image_meta)
-
-
-def last_bytes(file_like_object, num):
- return libvirt_utils.last_bytes(file_like_object, num)
diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py
index 4ab7baaa30..65e98c8c8c 100644
--- a/nova/tests/unit/virt/libvirt/test_driver.py
+++ b/nova/tests/unit/virt/libvirt/test_driver.py
@@ -71,6 +71,7 @@ from nova.objects import migrate_data as migrate_data_obj
from nova.objects import virtual_interface as obj_vif
from nova.pci import manager as pci_manager
from nova.pci import utils as pci_utils
+import nova.privsep.libvirt
from nova import test
from nova.tests.unit import fake_block_device
from nova.tests.unit import fake_diagnostics
@@ -11288,9 +11289,9 @@ class LibvirtConnTestCase(test.NoDBTestCase,
drvr._ensure_console_log_for_instance,
mock.ANY)
- def test_get_console_output_file(self):
- fake_libvirt_utils.files['console.log'] = b'01234567890'
-
+ @mock.patch('nova.privsep.libvirt.last_bytes',
+ return_value=(b'67890', 0))
+ def test_get_console_output_file(self, mock_last_bytes):
with utils.tempdir() as tmpdir:
self.flags(instances_path=tmpdir)
@@ -11370,9 +11371,11 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertEqual('', output)
@mock.patch('os.path.exists', return_value=True)
- def test_get_console_output_pty(self, mocked_path_exists):
- fake_libvirt_utils.files['pty'] = b'01234567890'
-
+ @mock.patch('nova.privsep.libvirt.last_bytes',
+ return_value=(b'67890', 0))
+ @mock.patch('nova.privsep.dac_admin.writefile')
+ def test_get_console_output_pty(self, mocked_writefile, mocked_last_bytes,
+ mocked_path_exists):
with utils.tempdir() as tmpdir:
self.flags(instances_path=tmpdir)
@@ -11402,13 +11405,9 @@ class LibvirtConnTestCase(test.NoDBTestCase,
def _fake_flush(self, fake_pty):
return 'foo'
- def _fake_append_to_file(self, data, fpath):
- return 'pty'
-
self.create_fake_libvirt_mock()
libvirt_driver.LibvirtDriver._conn.lookupByUUIDString = fake_lookup
libvirt_driver.LibvirtDriver._flush_libvirt_console = _fake_flush
- libvirt_driver.LibvirtDriver._append_to_file = _fake_append_to_file
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
@@ -11469,7 +11468,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
drvr.get_console_output, self.context, instance)
@mock.patch('nova.virt.libvirt.host.Host.get_domain')
- @mock.patch.object(libvirt_guest.Guest, "get_xml_desc")
+ @mock.patch.object(libvirt_guest.Guest, 'get_xml_desc')
def test_get_console_output_logrotate(self, mock_get_xml, get_domain):
fake_libvirt_utils.files['console.log'] = b'uvwxyz'
fake_libvirt_utils.files['console.log.0'] = b'klmnopqrst'
@@ -11478,6 +11477,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
def mock_path_exists(path):
return os.path.basename(path) in fake_libvirt_utils.files
+ def mock_last_bytes(path, count):
+ with fake_libvirt_utils.file_open(path) as flo:
+ return nova.privsep.libvirt._last_bytes_inner(flo, count)
+
xml = """
<domain type='kvm'>
<devices>
@@ -11506,8 +11509,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
libvirt_driver.MAX_CONSOLE_BYTES = bytes_to_read
with mock.patch('os.path.exists',
side_effect=mock_path_exists):
- log_data = drvr.get_console_output(self.context,
- instance)
+ with mock.patch('nova.privsep.libvirt.last_bytes',
+ side_effect=mock_last_bytes):
+ log_data = drvr.get_console_output(self.context,
+ instance)
finally:
libvirt_driver.MAX_CONSOLE_BYTES = prev_max
return log_data
@@ -15101,7 +15106,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
mock.sentinel.new_connection_info, 'vdb', instance)
@mock.patch('nova.virt.libvirt.guest.BlockDevice.is_job_complete')
- def _test_live_snapshot(self, mock_is_job_complete,
+ @mock.patch('nova.privsep.dac_admin.chown')
+ def _test_live_snapshot(self, mock_chown, mock_is_job_complete,
can_quiesce=False, require_quiesce=False):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI())
mock_dom = mock.MagicMock()
@@ -15114,11 +15120,10 @@ class LibvirtConnTestCase(test.NoDBTestCase,
mock.patch.object(fake_libvirt_utils, 'get_disk_size'),
mock.patch.object(fake_libvirt_utils, 'get_disk_backing_file'),
mock.patch.object(fake_libvirt_utils, 'create_cow_image'),
- mock.patch.object(fake_libvirt_utils, 'chown'),
mock.patch.object(fake_libvirt_utils, 'extract_snapshot'),
mock.patch.object(drvr, '_set_quiesced')
) as (mock_define, mock_size, mock_backing, mock_create_cow,
- mock_chown, mock_snapshot, mock_quiesce):
+ mock_snapshot, mock_quiesce):
xmldoc = "<domain/>"
srcfile = "/first/path"
@@ -15157,7 +15162,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
mock_backing.assert_called_once_with(srcfile, basename=False,
format="qcow2")
mock_create_cow.assert_called_once_with(bckfile, dltfile, 1004009)
- mock_chown.assert_called_once_with(dltfile, os.getuid())
+ mock_chown.assert_called_once_with(dltfile, uid=os.getuid())
mock_snapshot.assert_called_once_with(dltfile, "qcow2",
dstfile, "qcow2")
mock_define.assert_called_once_with(xmldoc)
diff --git a/nova/tests/unit/virt/libvirt/test_utils.py b/nova/tests/unit/virt/libvirt/test_utils.py
index b63b771e2b..a7bb935d64 100644
--- a/nova/tests/unit/virt/libvirt/test_utils.py
+++ b/nova/tests/unit/virt/libvirt/test_utils.py
@@ -492,12 +492,6 @@ disk size: 4.4M
os.unlink(dst_path)
@mock.patch.object(utils, 'execute')
- def test_chown(self, mock_execute):
- libvirt_utils.chown('/some/path', 'soren')
- mock_execute.assert_called_once_with('chown', 'soren', '/some/path',
- run_as_root=True)
-
- @mock.patch.object(utils, 'execute')
def test_chown_for_id_maps(self, mock_execute):
id_maps = [vconfig.LibvirtConfigGuestUIDMap(),
vconfig.LibvirtConfigGuestUIDMap(),
@@ -939,32 +933,3 @@ sunrpc /var/lib/nfs/rpc_pipefs rpc_pipefs rw,relatime 0 0
disk_path, format = libvirt_utils.find_disk(guest)
self.assertEqual('/test/disk', disk_path)
self.assertEqual('ploop', format)
-
-
-class LastBytesTestCase(test.NoDBTestCase):
- """Test the last_bytes() utility method."""
-
- def setUp(self):
- super(LastBytesTestCase, self).setUp()
- self.f = six.BytesIO(b'1234567890')
-
- def test_truncated(self):
- self.f.seek(0, os.SEEK_SET)
- out, remaining = libvirt_utils.last_bytes(self.f, 5)
- self.assertEqual(out, b'67890')
- self.assertGreater(remaining, 0)
-
- def test_read_all(self):
- self.f.seek(0, os.SEEK_SET)
- out, remaining = libvirt_utils.last_bytes(self.f, 1000)
- self.assertEqual(out, b'1234567890')
- self.assertFalse(remaining > 0)
-
- def test_seek_too_far_real_file(self):
- # StringIO doesn't raise IOError if you see past the start of the file.
- with tempfile.TemporaryFile() as flo:
- content = b'1234567890'
- flo.write(content)
- self.assertEqual(
- (content, 0),
- libvirt_utils.last_bytes(flo, 1000))
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index f408e160c8..7d89d29768 100644
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -35,6 +35,7 @@ import itertools
import mmap
import operator
import os
+import pwd
import shutil
import tempfile
import time
@@ -81,6 +82,8 @@ from nova.objects import fields
from nova.objects import migrate_data as migrate_data_obj
from nova.pci import manager as pci_manager
from nova.pci import utils as pci_utils
+from nova.privsep import dac_admin
+import nova.privsep.libvirt
from nova import utils
from nova import version
from nova.virt import block_device as driver_block_device
@@ -1906,7 +1909,7 @@ class LibvirtDriver(driver.ComputeDriver):
time.sleep(0.5)
dev.abort_job()
- libvirt_utils.chown(disk_delta, os.getuid())
+ dac_admin.chown(disk_delta, uid=os.getuid())
finally:
self._host.write_instance_config(xml)
if quiesced:
@@ -2825,30 +2828,24 @@ class LibvirtDriver(driver.ComputeDriver):
check_exit_code=False)
return out
- def _append_to_file(self, data, fpath):
- LOG.info('data: %(data)r, fpath: %(fpath)r',
- {'data': data, 'fpath': fpath})
- with open(fpath, 'a+') as fp:
- fp.write(data)
-
- return fpath
-
def _get_console_output_file(self, instance, console_log):
bytes_to_read = MAX_CONSOLE_BYTES
log_data = b"" # The last N read bytes
i = 0 # in case there is a log rotation (like "virtlogd")
path = console_log
+
while bytes_to_read > 0 and os.path.exists(path):
- libvirt_utils.chown(path, os.getuid())
- with libvirt_utils.file_open(path, 'rb') as fp:
- read_log_data, remaining = libvirt_utils.last_bytes(
- fp, bytes_to_read)
- # We need the log file content in chronological order,
- # that's why we *prepend* the log data.
- log_data = read_log_data + log_data
- bytes_to_read -= len(read_log_data)
- path = console_log + "." + str(i)
- i += 1
+ read_log_data, remaining = nova.privsep.libvirt.last_bytes(
+ path, bytes_to_read)
+ # We need the log file content in chronological order,
+ # that's why we *prepend* the log data.
+ log_data = read_log_data + log_data
+
+ # Prep to read the next file in the chain
+ bytes_to_read -= len(read_log_data)
+ path = console_log + "." + str(i)
+ i += 1
+
if remaining > 0:
LOG.info('Truncated console log returned, '
'%d bytes ignored', remaining, instance=instance)
@@ -2896,12 +2893,6 @@ class LibvirtDriver(driver.ComputeDriver):
raise exception.ConsoleNotAvailable()
console_log = self._get_console_log_path(instance)
- # By default libvirt chowns the console log when it starts a domain.
- # We need to chown it back before attempting to read from or write
- # to it.
- if os.path.exists(console_log):
- libvirt_utils.chown(console_log, os.getuid())
-
data = self._flush_libvirt_console(pty)
# NOTE(markus_z): The virt_types kvm and qemu are the only ones
# which create a dedicated file device for the console logging.
@@ -2909,9 +2900,8 @@ class LibvirtDriver(driver.ComputeDriver):
# flush of that pty device into the "console.log" file to ensure
# that a series of "get_console_output" calls return the complete
# content even after rebooting a guest.
- fpath = self._append_to_file(data, console_log)
-
- return self._get_console_output_file(instance, fpath)
+ dac_admin.writefile(console_log, 'a+', data)
+ return self._get_console_output_file(instance, console_log)
def get_host_ip_addr(self):
ips = compute_utils.get_machine_ips()
@@ -3224,7 +3214,10 @@ class LibvirtDriver(driver.ComputeDriver):
image_id=disk_images['ramdisk_id'])
if CONF.libvirt.virt_type == 'uml':
- libvirt_utils.chown(image('disk').path, 'root')
+ # PONDERING(mikal): can I assume that root is UID zero in every
+ # OS? Probably not.
+ uid = pwd.getpwnam('root').pw_uid
+ dac_admin.chown(image('disk').path, uid=uid)
self._create_and_inject_local_root(context, instance,
booted_from_volume, suffix,
diff --git a/nova/virt/libvirt/utils.py b/nova/virt/libvirt/utils.py
index a87d2328f5..395dcae1ce 100644
--- a/nova/virt/libvirt/utils.py
+++ b/nova/virt/libvirt/utils.py
@@ -253,15 +253,6 @@ def write_to_file(path, contents, umask=None):
os.umask(saved_umask)
-def chown(path, owner):
- """Change ownership of file or directory
-
- :param path: File or directory whose ownership to change
- :param owner: Desired new owner (given as uid or username)
- """
- utils.execute('chown', owner, path, run_as_root=True)
-
-
def update_mtime(path):
"""Touch a file without being the owner.
@@ -529,27 +520,3 @@ def is_mounted(mount_path, source=None):
def is_valid_hostname(hostname):
return re.match(r"^[\w\-\.:]+$", hostname)
-
-
-def last_bytes(file_like_object, num):
- """Return num bytes from the end of the file, and remaining byte count.
-
- :param file_like_object: The file to read
- :param num: The number of bytes to return
-
- :returns: (data, remaining)
- """
-
- try:
- file_like_object.seek(-num, os.SEEK_END)
- except IOError as e:
- # seek() fails with EINVAL when trying to go before the start of
- # the file. It means that num is larger than the file size, so
- # just go to the start.
- if e.errno == errno.EINVAL:
- file_like_object.seek(0, os.SEEK_SET)
- else:
- raise
-
- remaining = file_like_object.tell()
- return (file_like_object.read(), remaining)
diff --git a/releasenotes/notes/privsep-queens-rootwrap-adds-907aa1bc8e3eb2ca.yaml b/releasenotes/notes/privsep-queens-rootwrap-adds-907aa1bc8e3eb2ca.yaml
index b6d8673674..2a2c4d3579 100644
--- a/releasenotes/notes/privsep-queens-rootwrap-adds-907aa1bc8e3eb2ca.yaml
+++ b/releasenotes/notes/privsep-queens-rootwrap-adds-907aa1bc8e3eb2ca.yaml
@@ -2,4 +2,7 @@
upgrade:
- |
A dac-admin privsep daemon has been added and needs to be included in your
- rootwrap configuration. \ No newline at end of file
+ rootwrap configuration.
+ - |
+ The following commands are no longer required to be listed in your rootwrap
+ configuration: cat; chown; readlink. \ No newline at end of file