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:
parent
8cdafd4e81
commit
54b3791a25
@ -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):
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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'):
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user