From 54b3791a2555e2369d95b86c08d50d5b32433b5b Mon Sep 17 00:00:00 2001 From: Alessandro Pilotti Date: Tue, 1 Sep 2015 23:50:07 +0300 Subject: [PATCH] Adds Windows support Windows Server 2016 includes support for containers and Docker. This commit adds portability support for running nova-docker on Windows. Co-Authored-By: Alin Balutoiu Change-Id: Iabd667dd6ed0b1ecb277f527bff1c6a1ce51ecea Implements: blueprint windows-docker-support --- novadocker/tests/virt/docker/test_hostinfo.py | 56 +++++++++++-------- novadocker/tests/virt/docker/test_network.py | 16 +++++- novadocker/tests/virt/docker/test_vifs.py | 30 ++++++++++ novadocker/tests/virt/test_hostutils.py | 36 ++++++++++-- novadocker/virt/docker/client.py | 2 +- novadocker/virt/docker/driver.py | 4 ++ novadocker/virt/docker/hostinfo.py | 34 ++++------- novadocker/virt/docker/network.py | 5 ++ novadocker/virt/docker/vifs.py | 10 ++++ novadocker/virt/hostutils.py | 18 +++++- 10 files changed, 153 insertions(+), 58 deletions(-) diff --git a/novadocker/tests/virt/docker/test_hostinfo.py b/novadocker/tests/virt/docker/test_hostinfo.py index b002d02..1037efe 100644 --- a/novadocker/tests/virt/docker/test_hostinfo.py +++ b/novadocker/tests/virt/docker/test_hostinfo.py @@ -13,48 +13,60 @@ # License for the specific language governing permissions and limitations # under the License. -import posix - import mock +import multiprocessing from nova import test from novadocker.virt.docker import hostinfo +import psutil class HostInfoTestCase(test.NoDBTestCase): + _FAKE_DISK_INFO = {'total_size': 100000, + 'free_size': 50000, + 'used_size': 50000} + def setUp(self): super(HostInfoTestCase, self).setUp() self.stubs.Set(hostinfo, 'statvfs', self.statvfs) def statvfs(self): - seq = (4096, 4096, 10047582, 7332259, 6820195, - 2564096, 2271310, 2271310, 1024, 255) - return posix.statvfs_result(sequence=seq) + diskinfo = psutil.namedtuple('usage', ('total', 'free', 'used')) + return diskinfo(self._FAKE_DISK_INFO['total_size'], + self._FAKE_DISK_INFO['free_size'], + self._FAKE_DISK_INFO['used_size']) def test_get_disk_usage(self): disk_usage = hostinfo.get_disk_usage() - self.assertEqual(disk_usage['total'], 41154895872) - self.assertEqual(disk_usage['available'], 27935518720) - self.assertEqual(disk_usage['used'], 11121963008) + self.assertEqual(disk_usage['total'], + self._FAKE_DISK_INFO['total_size']) + self.assertEqual(disk_usage['available'], + self._FAKE_DISK_INFO['free_size']) + self.assertEqual(disk_usage['used'], + self._FAKE_DISK_INFO['used_size']) + + @mock.patch.object(multiprocessing, 'cpu_count') + def test_get_total_vcpus(self, mock_cpu_count): + mock_cpu_count.return_value = 1 + + cpu_count = hostinfo.get_total_vcpus() + + self.assertEqual(mock_cpu_count.return_value, cpu_count) def test_get_memory_usage(self): - meminfo_str = """MemTotal: 1018784 kB -MemFree: 220060 kB -Buffers: 21640 kB -Cached: 63364 kB -SwapCached: 0 kB -Active: 13988 kB -Inactive: 50616 kB -""" - with mock.patch('__builtin__.open', - mock.mock_open(read_data=meminfo_str), - create=True) as m: + fake_total_memory = 4096 + fake_used_memory = 2048 + + with mock.patch.object(psutil, + 'virtual_memory') as mock_virtual_memory: + mock_virtual_memory.return_value.total = fake_total_memory + mock_virtual_memory.return_value.used = fake_used_memory usage = hostinfo.get_memory_usage() - m.assert_called_once_with('/proc/meminfo') - self.assertEqual(usage['total'], 1043234816) - self.assertEqual(usage['used'], 730849280) + + self.assertEqual(fake_total_memory, usage['total']) + self.assertEqual(fake_used_memory, usage['used']) @mock.patch('novadocker.virt.docker.hostinfo.get_mounts') def test_find_cgroup_devices_path_centos(self, mock): diff --git a/novadocker/tests/virt/docker/test_network.py b/novadocker/tests/virt/docker/test_network.py index 63fcd8d..a12c65d 100644 --- a/novadocker/tests/virt/docker/test_network.py +++ b/novadocker/tests/virt/docker/test_network.py @@ -29,24 +29,34 @@ import mock class NetworkTestCase(test.NoDBTestCase): + @mock.patch.object(network, 'os') @mock.patch.object(utils, 'execute') - def test_teardown_delete_network(self, utils_mock): + def test_teardown_delete_network(self, utils_mock, mock_os): id = "second-id" utils_mock.return_value = ("first-id\nsecond-id\nthird-id\n", None) network.teardown_network(id) utils_mock.assert_called_with('ip', 'netns', 'delete', id, run_as_root=True) + @mock.patch.object(network, 'os') @mock.patch.object(utils, 'execute') - def test_teardown_network_not_in_list(self, utils_mock): + def test_teardown_network_not_in_list(self, utils_mock, mock_os): utils_mock.return_value = ("first-id\nsecond-id\nthird-id\n", None) network.teardown_network("not-in-list") utils_mock.assert_called_with('ip', '-o', 'netns', 'list') + @mock.patch.object(network, 'os') + @mock.patch.object(utils, 'execute') + def test_teardown_network_hyperv(self, utils_mock, mock_os): + mock_os.name = 'nt' + network.teardown_network("fake_id") + self.assertFalse(utils_mock.execute.called) + + @mock.patch.object(network, 'os') @mock.patch.object(network, 'LOG') @mock.patch.object(utils, 'execute', side_effect=processutils.ProcessExecutionError) - def test_teardown_network_fails(self, utils_mock, log_mock): + def test_teardown_network_fails(self, utils_mock, log_mock, mock_os): # Call fails but method should not fail. # Error will be caught and logged. utils_mock.return_value = ("first-id\nsecond-id\nthird-id\n", None) diff --git a/novadocker/tests/virt/docker/test_vifs.py b/novadocker/tests/virt/docker/test_vifs.py index b208526..81f8f02 100644 --- a/novadocker/tests/virt/docker/test_vifs.py +++ b/novadocker/tests/virt/docker/test_vifs.py @@ -316,6 +316,26 @@ class DockerGenericVIFDriverTestCase(test.TestCase): 'project_id': tenant_id}, network_info) ex.assert_has_calls(calls) + def test_plug_vifs_windows(self): + network_info = [{'type': 'hyperv'}] + fake_instance = mock.sentinel.instance + with mock.patch.object(vifs.DockerGenericVIFDriver, + 'plug_windows') as mock_plug_windows: + driver = docker_driver.DockerDriver(object) + driver.plug_vifs(fake_instance, network_info) + mock_plug_windows.assert_called_once_with( + fake_instance, {'type': 'hyperv'}) + + def test_unplug_vifs_windows(self): + network_info = [{'type': 'hyperv'}] + fake_instance = mock.sentinel.instance + with mock.patch.object(vifs.DockerGenericVIFDriver, + 'unplug_windows') as mock_unplug_windows: + driver = docker_driver.DockerDriver(object) + driver.unplug_vifs(fake_instance, network_info) + mock_unplug_windows.assert_called_once_with( + fake_instance, {'type': 'hyperv'}) + def test_unplug_vifs_ovs_hybrid(self): iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0' calls = [ @@ -456,3 +476,13 @@ class DockerGenericVIFDriverTestCase(test.TestCase): driver = docker_driver.DockerDriver(object) driver._attach_vifs({'uuid': 'fake_uuid'}, network_info) ex.assert_has_calls(calls) + + @mock.patch.object(docker_driver, 'os') + def test_attach_vifs_hyperv(self, mock_os): + mock_os.name = 'nt' + with mock.patch.object(docker_driver.DockerDriver, + '_get_container_id') as mock_get_container_id: + driver = docker_driver.DockerDriver(object) + driver._attach_vifs(mock.sentinel.instance, + mock.sentinel.network_info) + self.assertFalse(mock_get_container_id.called) diff --git a/novadocker/tests/virt/test_hostutils.py b/novadocker/tests/virt/test_hostutils.py index 3a7b92c..c858ea8 100644 --- a/novadocker/tests/virt/test_hostutils.py +++ b/novadocker/tests/virt/test_hostutils.py @@ -20,9 +20,35 @@ from novadocker.virt import hostutils class HostUtilsTestCase(test.NoDBTestCase): - def test_sys_uptime(self): - expect_uptime = "this is my uptime" - with mock.patch('nova.utils.execute', - return_value=(expect_uptime, None)): + def _test_sys_uptime(self, is_nt_os=False): + expect_uptime = ("fake_time up 0:00:00, 0 users, " + "load average: 0, 0, 0") + fake_tick_count = 0 + fake_time = 'fake_time' + + with mock.patch.multiple(hostutils, os=mock.DEFAULT, time=mock.DEFAULT, + ctypes=mock.DEFAULT, utils=mock.DEFAULT, + create=True) as lib_mocks: + + lib_mocks['os'].name = 'nt' if is_nt_os else '' + lib_mocks['time'].strftime.return_value = fake_time + lib_mocks['utils'].execute.return_value = (expect_uptime, None) + tick_count = lib_mocks['ctypes'].windll.kernel32.GetTickCount64 + tick_count.return_value = fake_tick_count + uptime = hostutils.sys_uptime() - self.assertEqual(expect_uptime, uptime) + + if is_nt_os: + tick_count.assert_called_once_with() + lib_mocks['time'].strftime.assert_called_once_with("%H:%M:%S") + else: + lib_mocks['utils'].execute.assert_called_once_with( + 'env', 'LANG=C', 'uptime') + + self.assertEqual(expect_uptime, uptime) + + def test_sys_uptime(self): + self._test_sys_uptime() + + def test_nt_sys_uptime(self): + self._test_sys_uptime(is_nt_os=True) diff --git a/novadocker/virt/docker/client.py b/novadocker/virt/docker/client.py index a3dc357..f081eef 100644 --- a/novadocker/virt/docker/client.py +++ b/novadocker/virt/docker/client.py @@ -92,7 +92,7 @@ class DockerHTTPClient(client.Client): return res.status_code == 204 def load_repository_file(self, name, path): - with open(path) as fh: + with open(path, 'rb') as fh: self.load_image(fh) def get_container_logs(self, container_id): diff --git a/novadocker/virt/docker/driver.py b/novadocker/virt/docker/driver.py index 18e52ee..8272054 100644 --- a/novadocker/virt/docker/driver.py +++ b/novadocker/virt/docker/driver.py @@ -224,6 +224,10 @@ class DockerDriver(driver.ComputeDriver): """Plug VIFs into container.""" if not network_info: return + + if os.name == 'nt': + return + container_id = self._get_container_id(instance) if not container_id: return diff --git a/novadocker/virt/docker/hostinfo.py b/novadocker/virt/docker/hostinfo.py index 589b114..f424359 100644 --- a/novadocker/virt/docker/hostinfo.py +++ b/novadocker/virt/docker/hostinfo.py @@ -13,9 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. +import multiprocessing import os from oslo_config import cfg +import psutil CONF = cfg.CONF @@ -24,7 +26,7 @@ def statvfs(): docker_path = CONF.docker.root_directory if not os.path.exists(docker_path): docker_path = '/' - return os.statvfs(docker_path) + return psutil.disk_usage(docker_path) def get_disk_usage(): @@ -32,21 +34,14 @@ def get_disk_usage(): # hardcoded in Docker so it's not configurable yet. st = statvfs() return { - 'total': st.f_blocks * st.f_frsize, - 'available': st.f_bavail * st.f_frsize, - 'used': (st.f_blocks - st.f_bfree) * st.f_frsize + 'total': st.total, + 'available': st.free, + 'used': st.used } def get_total_vcpus(): - total_vcpus = 0 - - with open('/proc/cpuinfo') as f: - for ln in f.readlines(): - if ln.startswith('processor'): - total_vcpus += 1 - - return total_vcpus + return multiprocessing.cpu_count() def get_vcpus_used(containers): @@ -60,19 +55,10 @@ def get_vcpus_used(containers): def get_memory_usage(): - with open('/proc/meminfo') as f: - m = f.read().split() - idx1 = m.index('MemTotal:') - idx2 = m.index('MemFree:') - idx3 = m.index('Buffers:') - idx4 = m.index('Cached:') - - total = int(m[idx1 + 1]) - avail = int(m[idx2 + 1]) + int(m[idx3 + 1]) + int(m[idx4 + 1]) - + vmem = psutil.virtual_memory() return { - 'total': total * 1024, - 'used': (total - avail) * 1024 + 'total': vmem.total, + 'used': vmem.used } diff --git a/novadocker/virt/docker/network.py b/novadocker/virt/docker/network.py index 00e7dcb..7912575 100644 --- a/novadocker/virt/docker/network.py +++ b/novadocker/virt/docker/network.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import os + from oslo_concurrency import processutils from oslo_log import log @@ -25,6 +27,9 @@ LOG = log.getLogger(__name__) def teardown_network(container_id): + if os.name == 'nt': + return + try: output, err = utils.execute('ip', '-o', 'netns', 'list') for line in output.split('\n'): diff --git a/novadocker/virt/docker/vifs.py b/novadocker/virt/docker/vifs.py index 6678200..8401615 100644 --- a/novadocker/virt/docker/vifs.py +++ b/novadocker/virt/docker/vifs.py @@ -66,10 +66,15 @@ class DockerGenericVIFDriver(object): self.plug_midonet(instance, vif) elif vif_type == network_model.VIF_TYPE_IOVISOR: self.plug_iovisor(instance, vif) + elif vif_type == 'hyperv': + self.plug_windows(instance, vif) else: raise exception.NovaException( _("Unexpected vif_type=%s") % vif_type) + def plug_windows(self, instance, vif): + pass + def plug_iovisor(self, instance, vif): """Plug docker vif into IOvisor @@ -333,10 +338,15 @@ class DockerGenericVIFDriver(object): self.unplug_midonet(instance, vif) elif vif_type == network_model.VIF_TYPE_IOVISOR: self.unplug_iovisor(instance, vif) + elif vif_type == 'hyperv': + self.unplug_windows(instance, vif) else: raise exception.NovaException( _("Unexpected vif_type=%s") % vif_type) + def unplug_windows(self, instance, vif): + pass + def unplug_iovisor(self, instance, vif): """Unplug vif from IOvisor diff --git a/novadocker/virt/hostutils.py b/novadocker/virt/hostutils.py index 9ad65c4..fb3b4a3 100644 --- a/novadocker/virt/hostutils.py +++ b/novadocker/virt/hostutils.py @@ -14,10 +14,22 @@ # under the License. +import ctypes +import datetime +import os +import time + from nova import utils def sys_uptime(): - """Returns the result of calling "uptime".""" - out, err = utils.execute('env', 'LANG=C', 'uptime') - return out + """Returns the host uptime.""" + + if os.name == 'nt': + tick_count64 = ctypes.windll.kernel32.GetTickCount64() + return ("%s up %s, 0 users, load average: 0, 0, 0" % + (str(time.strftime("%H:%M:%S")), + str(datetime.timedelta(milliseconds=long(tick_count64))))) + else: + out, err = utils.execute('env', 'LANG=C', 'uptime') + return out