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 <abalutoiu@cloudbasesolutions.com>

Change-Id: Iabd667dd6ed0b1ecb277f527bff1c6a1ce51ecea
Implements: blueprint windows-docker-support
This commit is contained in:
Alessandro Pilotti 2015-09-01 23:50:07 +03:00 committed by Alin Balutoiu
parent 8cdafd4e81
commit 54b3791a25
10 changed files with 153 additions and 58 deletions

View File

@ -13,48 +13,60 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import posix
import mock import mock
import multiprocessing
from nova import test from nova import test
from novadocker.virt.docker import hostinfo from novadocker.virt.docker import hostinfo
import psutil
class HostInfoTestCase(test.NoDBTestCase): class HostInfoTestCase(test.NoDBTestCase):
_FAKE_DISK_INFO = {'total_size': 100000,
'free_size': 50000,
'used_size': 50000}
def setUp(self): def setUp(self):
super(HostInfoTestCase, self).setUp() super(HostInfoTestCase, self).setUp()
self.stubs.Set(hostinfo, 'statvfs', self.statvfs) self.stubs.Set(hostinfo, 'statvfs', self.statvfs)
def statvfs(self): def statvfs(self):
seq = (4096, 4096, 10047582, 7332259, 6820195, diskinfo = psutil.namedtuple('usage', ('total', 'free', 'used'))
2564096, 2271310, 2271310, 1024, 255) return diskinfo(self._FAKE_DISK_INFO['total_size'],
return posix.statvfs_result(sequence=seq) self._FAKE_DISK_INFO['free_size'],
self._FAKE_DISK_INFO['used_size'])
def test_get_disk_usage(self): def test_get_disk_usage(self):
disk_usage = hostinfo.get_disk_usage() disk_usage = hostinfo.get_disk_usage()
self.assertEqual(disk_usage['total'], 41154895872) self.assertEqual(disk_usage['total'],
self.assertEqual(disk_usage['available'], 27935518720) self._FAKE_DISK_INFO['total_size'])
self.assertEqual(disk_usage['used'], 11121963008) 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): def test_get_memory_usage(self):
meminfo_str = """MemTotal: 1018784 kB fake_total_memory = 4096
MemFree: 220060 kB fake_used_memory = 2048
Buffers: 21640 kB
Cached: 63364 kB with mock.patch.object(psutil,
SwapCached: 0 kB 'virtual_memory') as mock_virtual_memory:
Active: 13988 kB mock_virtual_memory.return_value.total = fake_total_memory
Inactive: 50616 kB mock_virtual_memory.return_value.used = fake_used_memory
"""
with mock.patch('__builtin__.open',
mock.mock_open(read_data=meminfo_str),
create=True) as m:
usage = hostinfo.get_memory_usage() usage = hostinfo.get_memory_usage()
m.assert_called_once_with('/proc/meminfo')
self.assertEqual(usage['total'], 1043234816) self.assertEqual(fake_total_memory, usage['total'])
self.assertEqual(usage['used'], 730849280) self.assertEqual(fake_used_memory, usage['used'])
@mock.patch('novadocker.virt.docker.hostinfo.get_mounts') @mock.patch('novadocker.virt.docker.hostinfo.get_mounts')
def test_find_cgroup_devices_path_centos(self, mock): def test_find_cgroup_devices_path_centos(self, mock):

View File

@ -29,24 +29,34 @@ import mock
class NetworkTestCase(test.NoDBTestCase): class NetworkTestCase(test.NoDBTestCase):
@mock.patch.object(network, 'os')
@mock.patch.object(utils, 'execute') @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" id = "second-id"
utils_mock.return_value = ("first-id\nsecond-id\nthird-id\n", None) utils_mock.return_value = ("first-id\nsecond-id\nthird-id\n", None)
network.teardown_network(id) network.teardown_network(id)
utils_mock.assert_called_with('ip', 'netns', 'delete', id, utils_mock.assert_called_with('ip', 'netns', 'delete', id,
run_as_root=True) run_as_root=True)
@mock.patch.object(network, 'os')
@mock.patch.object(utils, 'execute') @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) utils_mock.return_value = ("first-id\nsecond-id\nthird-id\n", None)
network.teardown_network("not-in-list") network.teardown_network("not-in-list")
utils_mock.assert_called_with('ip', '-o', 'netns', '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(network, 'LOG')
@mock.patch.object(utils, 'execute', @mock.patch.object(utils, 'execute',
side_effect=processutils.ProcessExecutionError) 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. # Call fails but method should not fail.
# Error will be caught and logged. # Error will be caught and logged.
utils_mock.return_value = ("first-id\nsecond-id\nthird-id\n", None) utils_mock.return_value = ("first-id\nsecond-id\nthird-id\n", None)

View File

@ -316,6 +316,26 @@ class DockerGenericVIFDriverTestCase(test.TestCase):
'project_id': tenant_id}, network_info) 'project_id': tenant_id}, network_info)
ex.assert_has_calls(calls) 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): def test_unplug_vifs_ovs_hybrid(self):
iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0' iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
calls = [ calls = [
@ -456,3 +476,13 @@ class DockerGenericVIFDriverTestCase(test.TestCase):
driver = docker_driver.DockerDriver(object) driver = docker_driver.DockerDriver(object)
driver._attach_vifs({'uuid': 'fake_uuid'}, network_info) driver._attach_vifs({'uuid': 'fake_uuid'}, network_info)
ex.assert_has_calls(calls) 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)

View File

@ -20,9 +20,35 @@ from novadocker.virt import hostutils
class HostUtilsTestCase(test.NoDBTestCase): class HostUtilsTestCase(test.NoDBTestCase):
def test_sys_uptime(self): def _test_sys_uptime(self, is_nt_os=False):
expect_uptime = "this is my uptime" expect_uptime = ("fake_time up 0:00:00, 0 users, "
with mock.patch('nova.utils.execute', "load average: 0, 0, 0")
return_value=(expect_uptime, None)): 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() 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)

View File

@ -92,7 +92,7 @@ class DockerHTTPClient(client.Client):
return res.status_code == 204 return res.status_code == 204
def load_repository_file(self, name, path): def load_repository_file(self, name, path):
with open(path) as fh: with open(path, 'rb') as fh:
self.load_image(fh) self.load_image(fh)
def get_container_logs(self, container_id): def get_container_logs(self, container_id):

View File

@ -224,6 +224,10 @@ class DockerDriver(driver.ComputeDriver):
"""Plug VIFs into container.""" """Plug VIFs into container."""
if not network_info: if not network_info:
return return
if os.name == 'nt':
return
container_id = self._get_container_id(instance) container_id = self._get_container_id(instance)
if not container_id: if not container_id:
return return

View File

@ -13,9 +13,11 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import multiprocessing
import os import os
from oslo_config import cfg from oslo_config import cfg
import psutil
CONF = cfg.CONF CONF = cfg.CONF
@ -24,7 +26,7 @@ def statvfs():
docker_path = CONF.docker.root_directory docker_path = CONF.docker.root_directory
if not os.path.exists(docker_path): if not os.path.exists(docker_path):
docker_path = '/' docker_path = '/'
return os.statvfs(docker_path) return psutil.disk_usage(docker_path)
def get_disk_usage(): def get_disk_usage():
@ -32,21 +34,14 @@ def get_disk_usage():
# hardcoded in Docker so it's not configurable yet. # hardcoded in Docker so it's not configurable yet.
st = statvfs() st = statvfs()
return { return {
'total': st.f_blocks * st.f_frsize, 'total': st.total,
'available': st.f_bavail * st.f_frsize, 'available': st.free,
'used': (st.f_blocks - st.f_bfree) * st.f_frsize 'used': st.used
} }
def get_total_vcpus(): def get_total_vcpus():
total_vcpus = 0 return multiprocessing.cpu_count()
with open('/proc/cpuinfo') as f:
for ln in f.readlines():
if ln.startswith('processor'):
total_vcpus += 1
return total_vcpus
def get_vcpus_used(containers): def get_vcpus_used(containers):
@ -60,19 +55,10 @@ def get_vcpus_used(containers):
def get_memory_usage(): def get_memory_usage():
with open('/proc/meminfo') as f: vmem = psutil.virtual_memory()
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])
return { return {
'total': total * 1024, 'total': vmem.total,
'used': (total - avail) * 1024 'used': vmem.used
} }

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import os
from oslo_concurrency import processutils from oslo_concurrency import processutils
from oslo_log import log from oslo_log import log
@ -25,6 +27,9 @@ LOG = log.getLogger(__name__)
def teardown_network(container_id): def teardown_network(container_id):
if os.name == 'nt':
return
try: try:
output, err = utils.execute('ip', '-o', 'netns', 'list') output, err = utils.execute('ip', '-o', 'netns', 'list')
for line in output.split('\n'): for line in output.split('\n'):

View File

@ -66,10 +66,15 @@ class DockerGenericVIFDriver(object):
self.plug_midonet(instance, vif) self.plug_midonet(instance, vif)
elif vif_type == network_model.VIF_TYPE_IOVISOR: elif vif_type == network_model.VIF_TYPE_IOVISOR:
self.plug_iovisor(instance, vif) self.plug_iovisor(instance, vif)
elif vif_type == 'hyperv':
self.plug_windows(instance, vif)
else: else:
raise exception.NovaException( raise exception.NovaException(
_("Unexpected vif_type=%s") % vif_type) _("Unexpected vif_type=%s") % vif_type)
def plug_windows(self, instance, vif):
pass
def plug_iovisor(self, instance, vif): def plug_iovisor(self, instance, vif):
"""Plug docker vif into IOvisor """Plug docker vif into IOvisor
@ -333,10 +338,15 @@ class DockerGenericVIFDriver(object):
self.unplug_midonet(instance, vif) self.unplug_midonet(instance, vif)
elif vif_type == network_model.VIF_TYPE_IOVISOR: elif vif_type == network_model.VIF_TYPE_IOVISOR:
self.unplug_iovisor(instance, vif) self.unplug_iovisor(instance, vif)
elif vif_type == 'hyperv':
self.unplug_windows(instance, vif)
else: else:
raise exception.NovaException( raise exception.NovaException(
_("Unexpected vif_type=%s") % vif_type) _("Unexpected vif_type=%s") % vif_type)
def unplug_windows(self, instance, vif):
pass
def unplug_iovisor(self, instance, vif): def unplug_iovisor(self, instance, vif):
"""Unplug vif from IOvisor """Unplug vif from IOvisor

View File

@ -14,10 +14,22 @@
# under the License. # under the License.
import ctypes
import datetime
import os
import time
from nova import utils from nova import utils
def sys_uptime(): def sys_uptime():
"""Returns the result of calling "uptime".""" """Returns the host uptime."""
out, err = utils.execute('env', 'LANG=C', 'uptime')
return out 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