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}
|
||||
# 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.
|
||||
|
@ -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'
|
||||
|
@ -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']
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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 = {}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user