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:
Kien Nguyen 2018-03-02 17:05:53 +07:00
parent faaf53d2e7
commit 956461e9c3
9 changed files with 190 additions and 11 deletions

View File

@ -29,7 +29,8 @@ commands =
bash tools/flake8wrap.sh {posargs}
# The following bandit tests are being skipped:
# 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]
#set PYTHONHASHSEED=0 to prevent oslo_policy.sphinxext from randomly failing.

View File

@ -387,9 +387,8 @@ class ContainersController(base.Controller):
return view.format_container(pecan.request.host_url, new_container)
def _set_default_resource_limit(self, container_dict):
if CONF.default_disk >= 0:
container_dict['disk'] = container_dict.get(
'disk', CONF.default_disk)
# NOTE(kiennt): Default disk size will be set later.
container_dict['disk'] = container_dict.get('disk')
container_dict['memory'] = container_dict.get(
'memory', CONF.default_memory)
container_dict['memory'] = str(container_dict['memory']) + 'M'

View File

@ -23,7 +23,7 @@ CAPSULE_STATUSES = (
PENDING, RUNNING, SUCCEEDED, FAILED, UNKNOWN
) = (
'Pending', 'Running', 'Succeeded', 'Failed', 'Unknown'
)
)
TASK_STATES = (
IMAGE_PULLING, CONTAINER_CREATING, SANDBOX_CREATING,
@ -54,3 +54,7 @@ ALLOCATED = 'allocated'
# The name of Docker container is of the form NAME_PREFIX-<uuid>
NAME_PREFIX = 'zun-'
SANDBOX_NAME_PREFIX = 'zun-sandbox-'
# Storage drivers that support disk quota feature
SUPPORTED_STORAGE_DRIVERS = \
['devicemapper', 'overlay2', 'windowfilter', 'zfs', 'btrfs']

View File

@ -182,6 +182,39 @@ class Manager(periodic_task.PeriodicTasks):
self._fail_container(context, container, msg, unset_host=True)
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,
requested_volumes, container, run, pci_requests=None):
@utils.synchronized(container.uuid)
@ -189,6 +222,7 @@ class Manager(periodic_task.PeriodicTasks):
self._wait_for_volumes_available(context, requested_volumes,
container)
self._attach_volumes(context, container, requested_volumes)
self._check_support_disk_quota(context, container)
created_container = self._do_container_create(
context, container, requested_networks, requested_volumes,
pci_requests, limits)

View File

@ -14,13 +14,13 @@
import datetime
import eventlet
import functools
import six
import types
from docker import errors
from oslo_log import log as logging
from oslo_utils import timeutils
from oslo_utils import uuidutils
import six
from zun.common import consts
from zun.common import exception
@ -98,6 +98,12 @@ class DockerDriver(driver.ContainerDriver):
def __init__(self):
super(DockerDriver, self).__init__()
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):
with docker_utils.docker_client() as docker:
@ -187,6 +193,7 @@ class DockerDriver(driver.ContainerDriver):
name = container.restart_policy['Name']
host_config['restart_policy'] = {'Name': name,
'MaximumRetryCount': count}
if container.disk:
disk_size = str(container.disk) + 'G'
host_config['storage_opt'] = {'size': disk_size}
@ -208,6 +215,12 @@ class DockerDriver(driver.ContainerDriver):
container.save(context)
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,
requested_networks, host_config,
container_kwargs, docker):

View File

@ -17,6 +17,9 @@ Manages information about the host.
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
LOG = logging.getLogger(__name__)
@ -40,3 +43,43 @@ class Host(object):
'to take effect.',
{'old': self._hostname, 'new': 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

View File

@ -258,3 +258,9 @@ class ContainerDriver(object):
def network_attach(self, context, container, network):
raise NotImplementedError()
def node_support_disk_quota(self):
raise NotImplementedError()
def get_host_default_base_size(self):
raise NotImplementedError()

View File

@ -44,7 +44,10 @@ _numa_topo_spec = [_numa_node]
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()
self.driver = DockerDriver()
dfc_patcher = mock.patch.object(docker_utils, 'docker_client')
@ -92,10 +95,11 @@ class TestDockerDriver(base.DriverTestCase):
'.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(self, mock_save,
mock_get_security_group_ids,
mock_create_or_update_port,
mock_connect):
def test_create_image_path_is_none_with_overlay2(
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(
@ -112,6 +116,75 @@ class TestDockerDriver(base.DriverTestCase):
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 = '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,
image, networks, volumes)
host_config = {}

View File

@ -115,3 +115,9 @@ class FakeDriver(driver.ContainerDriver):
def check_container_exist(self, context):
pass
def node_support_disk_quota(self):
return True
def get_host_default_base_size(self):
return None