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
# 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):

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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
}

View File

@ -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'):

View File

@ -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

View File

@ -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