Check if server support disk quota
Follow the Docker documentation [1], this patch is about check your system which supports disk quota feature or not. [1] https://docs.docker.com/engine/reference/commandline/run/#set-storage-driver-options-per-container Change-Id: Ibb584343062fb42eeb5bd11817f0a0f7325f4ced Closes-Bug: #1746401
This commit is contained in:
parent
faaf53d2e7
commit
956461e9c3
3
tox.ini
3
tox.ini
@ -29,7 +29,8 @@ commands =
|
|||||||
bash tools/flake8wrap.sh {posargs}
|
bash tools/flake8wrap.sh {posargs}
|
||||||
# The following bandit tests are being skipped:
|
# The following bandit tests are being skipped:
|
||||||
# B303 - Use of insecure MD2, MD4, or MD5 hash function.
|
# B303 - Use of insecure MD2, MD4, or MD5 hash function.
|
||||||
bandit -r zun -x tests -n5 -ll --skip B303
|
# B604 - unction call with shell=True parameter identified, possible security issue.
|
||||||
|
bandit -r zun -x tests -n5 -ll --skip B303,B604
|
||||||
|
|
||||||
[testenv:venv]
|
[testenv:venv]
|
||||||
#set PYTHONHASHSEED=0 to prevent oslo_policy.sphinxext from randomly failing.
|
#set PYTHONHASHSEED=0 to prevent oslo_policy.sphinxext from randomly failing.
|
||||||
|
@ -387,9 +387,8 @@ class ContainersController(base.Controller):
|
|||||||
return view.format_container(pecan.request.host_url, new_container)
|
return view.format_container(pecan.request.host_url, new_container)
|
||||||
|
|
||||||
def _set_default_resource_limit(self, container_dict):
|
def _set_default_resource_limit(self, container_dict):
|
||||||
if CONF.default_disk >= 0:
|
# NOTE(kiennt): Default disk size will be set later.
|
||||||
container_dict['disk'] = container_dict.get(
|
container_dict['disk'] = container_dict.get('disk')
|
||||||
'disk', CONF.default_disk)
|
|
||||||
container_dict['memory'] = container_dict.get(
|
container_dict['memory'] = container_dict.get(
|
||||||
'memory', CONF.default_memory)
|
'memory', CONF.default_memory)
|
||||||
container_dict['memory'] = str(container_dict['memory']) + 'M'
|
container_dict['memory'] = str(container_dict['memory']) + 'M'
|
||||||
|
@ -23,7 +23,7 @@ CAPSULE_STATUSES = (
|
|||||||
PENDING, RUNNING, SUCCEEDED, FAILED, UNKNOWN
|
PENDING, RUNNING, SUCCEEDED, FAILED, UNKNOWN
|
||||||
) = (
|
) = (
|
||||||
'Pending', 'Running', 'Succeeded', 'Failed', 'Unknown'
|
'Pending', 'Running', 'Succeeded', 'Failed', 'Unknown'
|
||||||
)
|
)
|
||||||
|
|
||||||
TASK_STATES = (
|
TASK_STATES = (
|
||||||
IMAGE_PULLING, CONTAINER_CREATING, SANDBOX_CREATING,
|
IMAGE_PULLING, CONTAINER_CREATING, SANDBOX_CREATING,
|
||||||
@ -54,3 +54,7 @@ ALLOCATED = 'allocated'
|
|||||||
# The name of Docker container is of the form NAME_PREFIX-<uuid>
|
# The name of Docker container is of the form NAME_PREFIX-<uuid>
|
||||||
NAME_PREFIX = 'zun-'
|
NAME_PREFIX = 'zun-'
|
||||||
SANDBOX_NAME_PREFIX = 'zun-sandbox-'
|
SANDBOX_NAME_PREFIX = 'zun-sandbox-'
|
||||||
|
|
||||||
|
# Storage drivers that support disk quota feature
|
||||||
|
SUPPORTED_STORAGE_DRIVERS = \
|
||||||
|
['devicemapper', 'overlay2', 'windowfilter', 'zfs', 'btrfs']
|
||||||
|
@ -182,6 +182,39 @@ class Manager(periodic_task.PeriodicTasks):
|
|||||||
self._fail_container(context, container, msg, unset_host=True)
|
self._fail_container(context, container, msg, unset_host=True)
|
||||||
raise exception.Conflict(msg)
|
raise exception.Conflict(msg)
|
||||||
|
|
||||||
|
def _check_support_disk_quota(self, context, container):
|
||||||
|
base_device_size = self.driver.get_host_default_base_size()
|
||||||
|
if base_device_size:
|
||||||
|
# NOTE(kiennt): If default_base_size is not None, it means
|
||||||
|
# host storage_driver is in list ['devicemapper',
|
||||||
|
# windowfilter', 'zfs', 'btrfs']. The following
|
||||||
|
# block is to prevent Zun raises Exception everytime
|
||||||
|
# if user do not set container's disk and
|
||||||
|
# default_disk less than base_device_size.
|
||||||
|
# FIXME(kiennt): This block is too complicated. We should find
|
||||||
|
# new effecient way to do the check.
|
||||||
|
if not container.disk:
|
||||||
|
container.disk = max(base_device_size, CONF.default_disk)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
if container.disk < base_device_size:
|
||||||
|
msg = _('Disk size cannot be smaller than '
|
||||||
|
'%(base_device_size)s.') % {
|
||||||
|
'base_device_size': base_device_size
|
||||||
|
}
|
||||||
|
self._fail_container(context, container,
|
||||||
|
msg, unset_host=True)
|
||||||
|
raise exception.Invalid(msg)
|
||||||
|
# NOTE(kiennt): Only raise Exception when user passes disk size and
|
||||||
|
# the disk quota feature isn't supported in host.
|
||||||
|
if not self.driver.node_support_disk_quota() and container.disk:
|
||||||
|
msg = _('Your host does not support disk quota feature.')
|
||||||
|
self._fail_container(context, container, msg, unset_host=True)
|
||||||
|
raise exception.Invalid(msg)
|
||||||
|
if self.driver.node_support_disk_quota() and not container.disk:
|
||||||
|
container.disk = CONF.default_disk
|
||||||
|
return
|
||||||
|
|
||||||
def container_create(self, context, limits, requested_networks,
|
def container_create(self, context, limits, requested_networks,
|
||||||
requested_volumes, container, run, pci_requests=None):
|
requested_volumes, container, run, pci_requests=None):
|
||||||
@utils.synchronized(container.uuid)
|
@utils.synchronized(container.uuid)
|
||||||
@ -189,6 +222,7 @@ class Manager(periodic_task.PeriodicTasks):
|
|||||||
self._wait_for_volumes_available(context, requested_volumes,
|
self._wait_for_volumes_available(context, requested_volumes,
|
||||||
container)
|
container)
|
||||||
self._attach_volumes(context, container, requested_volumes)
|
self._attach_volumes(context, container, requested_volumes)
|
||||||
|
self._check_support_disk_quota(context, container)
|
||||||
created_container = self._do_container_create(
|
created_container = self._do_container_create(
|
||||||
context, container, requested_networks, requested_volumes,
|
context, container, requested_networks, requested_volumes,
|
||||||
pci_requests, limits)
|
pci_requests, limits)
|
||||||
|
@ -14,13 +14,13 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import eventlet
|
import eventlet
|
||||||
import functools
|
import functools
|
||||||
import six
|
|
||||||
import types
|
import types
|
||||||
|
|
||||||
from docker import errors
|
from docker import errors
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
import six
|
||||||
|
|
||||||
from zun.common import consts
|
from zun.common import consts
|
||||||
from zun.common import exception
|
from zun.common import exception
|
||||||
@ -98,6 +98,12 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(DockerDriver, self).__init__()
|
super(DockerDriver, self).__init__()
|
||||||
self._host = host.Host()
|
self._host = host.Host()
|
||||||
|
self._get_host_storage_info()
|
||||||
|
|
||||||
|
def _get_host_storage_info(self):
|
||||||
|
storage_info = self._host.get_storage_info()
|
||||||
|
self.base_device_size = storage_info['default_base_size']
|
||||||
|
self.support_disk_quota = self._host.check_supported_disk_quota()
|
||||||
|
|
||||||
def load_image(self, image_path=None):
|
def load_image(self, image_path=None):
|
||||||
with docker_utils.docker_client() as docker:
|
with docker_utils.docker_client() as docker:
|
||||||
@ -187,6 +193,7 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
name = container.restart_policy['Name']
|
name = container.restart_policy['Name']
|
||||||
host_config['restart_policy'] = {'Name': name,
|
host_config['restart_policy'] = {'Name': name,
|
||||||
'MaximumRetryCount': count}
|
'MaximumRetryCount': count}
|
||||||
|
|
||||||
if container.disk:
|
if container.disk:
|
||||||
disk_size = str(container.disk) + 'G'
|
disk_size = str(container.disk) + 'G'
|
||||||
host_config['storage_opt'] = {'size': disk_size}
|
host_config['storage_opt'] = {'size': disk_size}
|
||||||
@ -208,6 +215,12 @@ class DockerDriver(driver.ContainerDriver):
|
|||||||
container.save(context)
|
container.save(context)
|
||||||
return container
|
return container
|
||||||
|
|
||||||
|
def node_support_disk_quota(self):
|
||||||
|
return self.support_disk_quota
|
||||||
|
|
||||||
|
def get_host_default_base_size(self):
|
||||||
|
return self.base_device_size
|
||||||
|
|
||||||
def _process_networking_config(self, context, container,
|
def _process_networking_config(self, context, container,
|
||||||
requested_networks, host_config,
|
requested_networks, host_config,
|
||||||
container_kwargs, docker):
|
container_kwargs, docker):
|
||||||
|
@ -17,6 +17,9 @@ Manages information about the host.
|
|||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from zun.common import consts
|
||||||
|
from zun.common import exception
|
||||||
|
from zun.common import utils
|
||||||
from zun.container.docker import utils as docker_utils
|
from zun.container.docker import utils as docker_utils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -40,3 +43,43 @@ class Host(object):
|
|||||||
'to take effect.',
|
'to take effect.',
|
||||||
{'old': self._hostname, 'new': hostname})
|
{'old': self._hostname, 'new': hostname})
|
||||||
return self._hostname
|
return self._hostname
|
||||||
|
|
||||||
|
def get_storage_info(self):
|
||||||
|
with docker_utils.docker_client() as docker:
|
||||||
|
info = docker.info()
|
||||||
|
storage_driver = str(info['Driver'])
|
||||||
|
# DriverStatus is list. Convert it to dict
|
||||||
|
driver_status = dict(info['DriverStatus'])
|
||||||
|
backing_filesystem = \
|
||||||
|
str(driver_status.get('Backing Filesystem'))
|
||||||
|
default_base_size = driver_status.get('Base Device Size')
|
||||||
|
if default_base_size:
|
||||||
|
default_base_size = float(default_base_size.strip('GB'))
|
||||||
|
return {
|
||||||
|
'storage_driver': storage_driver,
|
||||||
|
'backing_filesystem': backing_filesystem,
|
||||||
|
'default_base_size': default_base_size
|
||||||
|
}
|
||||||
|
|
||||||
|
def check_supported_disk_quota(self):
|
||||||
|
"""Check your system be supported disk quota or not"""
|
||||||
|
storage_info = self.get_storage_info()
|
||||||
|
sp_disk_quota = True
|
||||||
|
storage_driver = storage_info['storage_driver']
|
||||||
|
backing_filesystem = storage_info['backing_filesystem']
|
||||||
|
if storage_driver not in consts.SUPPORTED_STORAGE_DRIVERS:
|
||||||
|
sp_disk_quota = False
|
||||||
|
else:
|
||||||
|
if storage_driver == 'overlay2':
|
||||||
|
if backing_filesystem == 'xfs':
|
||||||
|
# Check project quota mount option
|
||||||
|
try:
|
||||||
|
utils.execute(
|
||||||
|
"mount | grep $(df /var/lib/docker | "
|
||||||
|
"awk 'FNR==2 {print $1}') |grep 'xfs' |"
|
||||||
|
" grep 'pquota'", shell=True)
|
||||||
|
except exception.CommandError:
|
||||||
|
self.sp_disk_quota = False
|
||||||
|
else:
|
||||||
|
sp_disk_quota = False
|
||||||
|
return sp_disk_quota
|
||||||
|
@ -258,3 +258,9 @@ class ContainerDriver(object):
|
|||||||
|
|
||||||
def network_attach(self, context, container, network):
|
def network_attach(self, context, container, network):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def node_support_disk_quota(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def get_host_default_base_size(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
@ -44,7 +44,10 @@ _numa_topo_spec = [_numa_node]
|
|||||||
|
|
||||||
|
|
||||||
class TestDockerDriver(base.DriverTestCase):
|
class TestDockerDriver(base.DriverTestCase):
|
||||||
def setUp(self):
|
|
||||||
|
@mock.patch('zun.container.docker.driver.DockerDriver.'
|
||||||
|
'_get_host_storage_info')
|
||||||
|
def setUp(self, mock_get):
|
||||||
super(TestDockerDriver, self).setUp()
|
super(TestDockerDriver, self).setUp()
|
||||||
self.driver = DockerDriver()
|
self.driver = DockerDriver()
|
||||||
dfc_patcher = mock.patch.object(docker_utils, 'docker_client')
|
dfc_patcher = mock.patch.object(docker_utils, 'docker_client')
|
||||||
@ -92,10 +95,11 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
'.create_or_update_port')
|
'.create_or_update_port')
|
||||||
@mock.patch('zun.common.utils.get_security_group_ids')
|
@mock.patch('zun.common.utils.get_security_group_ids')
|
||||||
@mock.patch('zun.objects.container.Container.save')
|
@mock.patch('zun.objects.container.Container.save')
|
||||||
def test_create_image_path_is_none(self, mock_save,
|
def test_create_image_path_is_none_with_overlay2(
|
||||||
mock_get_security_group_ids,
|
self, mock_save,
|
||||||
mock_create_or_update_port,
|
mock_get_security_group_ids,
|
||||||
mock_connect):
|
mock_create_or_update_port,
|
||||||
|
mock_connect):
|
||||||
self.mock_docker.create_host_config = mock.Mock(
|
self.mock_docker.create_host_config = mock.Mock(
|
||||||
return_value={'Id1': 'val1', 'key2': 'val2'})
|
return_value={'Id1': 'val1', 'key2': 'val2'})
|
||||||
self.mock_docker.create_container = mock.Mock(
|
self.mock_docker.create_container = mock.Mock(
|
||||||
@ -112,6 +116,75 @@ class TestDockerDriver(base.DriverTestCase):
|
|||||||
volumes = []
|
volumes = []
|
||||||
fake_port = {'mac_address': 'fake_mac'}
|
fake_port = {'mac_address': 'fake_mac'}
|
||||||
mock_create_or_update_port.return_value = ([], fake_port)
|
mock_create_or_update_port.return_value = ([], fake_port)
|
||||||
|
# DockerDriver with supported storage driver - overlay2
|
||||||
|
self.driver._host.sp_disk_quota = True
|
||||||
|
self.driver._host.storage_driver = 'overlay2'
|
||||||
|
result_container = self.driver.create(self.context, mock_container,
|
||||||
|
image, networks, volumes)
|
||||||
|
host_config = {}
|
||||||
|
host_config['mem_limit'] = '512m'
|
||||||
|
host_config['cpu_quota'] = 100000
|
||||||
|
host_config['cpu_period'] = 100000
|
||||||
|
host_config['restart_policy'] = {'Name': 'no', 'MaximumRetryCount': 0}
|
||||||
|
host_config['runtime'] = 'runc'
|
||||||
|
host_config['binds'] = {}
|
||||||
|
host_config['network_mode'] = 'fake-network'
|
||||||
|
host_config['storage_opt'] = {'size': '20G'}
|
||||||
|
self.mock_docker.create_host_config.assert_called_once_with(
|
||||||
|
**host_config)
|
||||||
|
|
||||||
|
kwargs = {
|
||||||
|
'name': '%sea8e2a25-2901-438d-8157-de7ffd68d051' %
|
||||||
|
consts.NAME_PREFIX,
|
||||||
|
'command': 'fake_command',
|
||||||
|
'environment': {'key1': 'val1', 'key2': 'val2'},
|
||||||
|
'working_dir': '/home/ubuntu',
|
||||||
|
'labels': {'key1': 'val1', 'key2': 'val2'},
|
||||||
|
'host_config': {'Id1': 'val1', 'key2': 'val2'},
|
||||||
|
'stdin_open': True,
|
||||||
|
'tty': True,
|
||||||
|
'hostname': 'testhost',
|
||||||
|
'volumes': [],
|
||||||
|
'networking_config': {'Id': 'val1', 'key1': 'val2'},
|
||||||
|
'mac_address': 'fake_mac',
|
||||||
|
}
|
||||||
|
self.mock_docker.create_container.assert_called_once_with(
|
||||||
|
image['repo'] + ":" + image['tag'], **kwargs)
|
||||||
|
self.assertEqual('val1', result_container.container_id)
|
||||||
|
self.assertEqual(result_container.status,
|
||||||
|
consts.CREATED)
|
||||||
|
|
||||||
|
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||||
|
'.connect_container_to_network')
|
||||||
|
@mock.patch('zun.network.kuryr_network.KuryrNetwork'
|
||||||
|
'.create_or_update_port')
|
||||||
|
@mock.patch('zun.common.utils.get_security_group_ids')
|
||||||
|
@mock.patch('zun.objects.container.Container.save')
|
||||||
|
def test_create_image_path_is_none_with_devicemapper(
|
||||||
|
self, mock_save,
|
||||||
|
mock_get_security_group_ids,
|
||||||
|
mock_create_or_update_port,
|
||||||
|
mock_connect):
|
||||||
|
self.mock_docker.create_host_config = mock.Mock(
|
||||||
|
return_value={'Id1': 'val1', 'key2': 'val2'})
|
||||||
|
self.mock_docker.create_container = mock.Mock(
|
||||||
|
return_value={'Id': 'val1', 'key1': 'val2'})
|
||||||
|
self.mock_docker.create_networking_config = mock.Mock(
|
||||||
|
return_value={'Id': 'val1', 'key1': 'val2'})
|
||||||
|
self.mock_docker.inspect_container = mock.Mock(
|
||||||
|
return_value={'State': 'created',
|
||||||
|
'Config': {'Cmd': ['fake_command']}})
|
||||||
|
image = {'path': '', 'image': '', 'repo': 'test', 'tag': 'test'}
|
||||||
|
mock_container = self.mock_default_container
|
||||||
|
mock_container.status = 'Creating'
|
||||||
|
networks = [{'network': 'fake-network'}]
|
||||||
|
volumes = []
|
||||||
|
fake_port = {'mac_address': 'fake_mac'}
|
||||||
|
mock_create_or_update_port.return_value = ([], fake_port)
|
||||||
|
# DockerDriver with supported storage driver - overlay2
|
||||||
|
self.driver._host.sp_disk_quota = True
|
||||||
|
self.driver._host.storage_driver = 'devicemapper'
|
||||||
|
self.driver._host.default_base_size = 10
|
||||||
result_container = self.driver.create(self.context, mock_container,
|
result_container = self.driver.create(self.context, mock_container,
|
||||||
image, networks, volumes)
|
image, networks, volumes)
|
||||||
host_config = {}
|
host_config = {}
|
||||||
|
@ -115,3 +115,9 @@ class FakeDriver(driver.ContainerDriver):
|
|||||||
|
|
||||||
def check_container_exist(self, context):
|
def check_container_exist(self, context):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def node_support_disk_quota(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_host_default_base_size(self):
|
||||||
|
return None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user