Docker plugin add cpu share property

Docker API 1.8 and docker-py 0.3.0 support the cpu-shares parameter when
creating containers(docker run --cpu-shares=0).

By default, all containers run at the same priority and get the same
proportion of CPU cycles, but you can tell the kernel to give more shares of
CPU time to one or more containers when you start them via Docker.

Change-Id: Ic20cc56070c120ab46379aa42d5da6ac9425cae7
Closes-bug: #1439033
This commit is contained in:
LiangChen 2015-04-02 10:34:29 +08:00
parent f65079fe74
commit 41437ff8a3
2 changed files with 64 additions and 20 deletions

View File

@ -31,7 +31,7 @@ from heat.engine import support
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
DOCKER_INSTALLED = False DOCKER_INSTALLED = False
READ_ONLY_MIN_API_VERSION = '1.17' MIN_API_VERSION_MAP = {'read_only': '1.17', 'cpu_shares': '1.8'}
# conditionally import so tests can work without having the dependency # conditionally import so tests can work without having the dependency
# satisfied # satisfied
try: try:
@ -47,12 +47,12 @@ class DockerContainer(resource.Resource):
DOCKER_ENDPOINT, HOSTNAME, USER, MEMORY, PORT_SPECS, DOCKER_ENDPOINT, HOSTNAME, USER, MEMORY, PORT_SPECS,
PRIVILEGED, TTY, OPEN_STDIN, STDIN_ONCE, ENV, CMD, DNS, PRIVILEGED, TTY, OPEN_STDIN, STDIN_ONCE, ENV, CMD, DNS,
IMAGE, VOLUMES, VOLUMES_FROM, PORT_BINDINGS, LINKS, NAME, IMAGE, VOLUMES, VOLUMES_FROM, PORT_BINDINGS, LINKS, NAME,
RESTART_POLICY, CAP_ADD, CAP_DROP, READ_ONLY, RESTART_POLICY, CAP_ADD, CAP_DROP, READ_ONLY, CPU_SHARES,
) = ( ) = (
'docker_endpoint', 'hostname', 'user', 'memory', 'port_specs', 'docker_endpoint', 'hostname', 'user', 'memory', 'port_specs',
'privileged', 'tty', 'open_stdin', 'stdin_once', 'env', 'cmd', 'dns', 'privileged', 'tty', 'open_stdin', 'stdin_once', 'env', 'cmd', 'dns',
'image', 'volumes', 'volumes_from', 'port_bindings', 'links', 'name', 'image', 'volumes', 'volumes_from', 'port_bindings', 'links', 'name',
'restart_policy', 'cap_add', 'cap_drop', 'read_only' 'restart_policy', 'cap_add', 'cap_drop', 'read_only', 'cpu_shares',
) )
ATTRIBUTES = ( ATTRIBUTES = (
@ -220,9 +220,17 @@ class DockerContainer(resource.Resource):
properties.Schema.BOOLEAN, properties.Schema.BOOLEAN,
_('If true, mount the container\'s root filesystem ' _('If true, mount the container\'s root filesystem '
'as read only (only supported for API version >= %s).') % 'as read only (only supported for API version >= %s).') %
READ_ONLY_MIN_API_VERSION, MIN_API_VERSION_MAP['read_only'],
default=False, default=False,
support_status=support.SupportStatus(version='2015.1'), support_status=support.SupportStatus(version='2015.1'),
),
CPU_SHARES: properties.Schema(
properties.Schema.INTEGER,
_('Relative weight which determines the allocation of the CPU '
'processing power(only supported for API version >= %s).') %
MIN_API_VERSION_MAP['cpu_shares'],
default=0,
support_status=support.SupportStatus(version='2015.2'),
) )
} }
@ -340,10 +348,10 @@ class DockerContainer(resource.Resource):
'environment': self.properties[self.ENV], 'environment': self.properties[self.ENV],
'dns': self.properties[self.DNS], 'dns': self.properties[self.DNS],
'volumes': self.properties[self.VOLUMES], 'volumes': self.properties[self.VOLUMES],
'name': self.properties[self.NAME] 'name': self.properties[self.NAME],
'cpu_shares': self.properties[self.CPU_SHARES]
} }
client = self.get_client() client = self.get_client()
version = client.version()['ApiVersion']
client.pull(self.properties[self.IMAGE]) client.pull(self.properties[self.IMAGE])
result = client.create_container(**create_args) result = client.create_container(**create_args)
container_id = result['Id'] container_id = result['Id']
@ -368,12 +376,7 @@ class DockerContainer(resource.Resource):
if self.properties[self.CAP_DROP]: if self.properties[self.CAP_DROP]:
start_args['cap_drop'] = self.properties[self.CAP_DROP] start_args['cap_drop'] = self.properties[self.CAP_DROP]
if self.properties[self.READ_ONLY]: if self.properties[self.READ_ONLY]:
if compare_version(READ_ONLY_MIN_API_VERSION, version) >= 0: start_args[self.READ_ONLY] = True
start_args[self.READ_ONLY] = True
else:
raise InvalidArgForVersion(arg=self.READ_ONLY,
min_version=(
READ_ONLY_MIN_API_VERSION))
client.start(container_id, **start_args) client.start(container_id, **start_args)
return container_id return container_id
@ -436,6 +439,22 @@ class DockerContainer(resource.Resource):
status = self._get_container_status(container_id) status = self._get_container_status(container_id)
return status['Running'] return status['Running']
def validate(self):
super(DockerContainer, self).validate()
self._validate_arg_for_api_version()
def _validate_arg_for_api_version(self):
version = None
for key in MIN_API_VERSION_MAP:
if self.properties[key]:
if not version:
client = self.get_client()
version = client.version()['ApiVersion']
min_version = MIN_API_VERSION_MAP[key]
if compare_version(min_version, version) < 0:
raise InvalidArgForVersion(arg=key,
min_version=min_version)
def resource_mapping(): def resource_mapping():
return { return {

View File

@ -19,6 +19,7 @@ from oslo_utils import importutils
import six import six
from heat.common import exception from heat.common import exception
from heat.common.i18n import _
from heat.common import template_format from heat.common import template_format
from heat.engine import resource from heat.engine import resource
from heat.engine import rsrc_defn from heat.engine import rsrc_defn
@ -314,24 +315,48 @@ class DockerContainerTest(common.HeatTestCase):
self.assertEqual(['samalba/wordpress'], client.pulled_images) self.assertEqual(['samalba/wordpress'], client.pulled_images)
self.assertIs(True, client.container_start[0]['read_only']) self.assertIs(True, client.container_start[0]['read_only'])
def test_start_with_read_only_for_low_api_version(self): def arg_for_low_api_version(self, arg, value, low_version):
t = template_format.parse(template) t = template_format.parse(template)
stack = utils.parse_stack(t) stack = utils.parse_stack(t)
definition = stack.t.resource_definitions(stack)['Blog'] definition = stack.t.resource_definitions(stack)['Blog']
definition['Properties']['read_only'] = True definition['Properties'][arg] = value
my_resource = docker_container.DockerContainer( my_resource = docker_container.DockerContainer(
'Blog', definition, stack) 'Blog', definition, stack)
get_client_mock = self.patchobject(my_resource, 'get_client') get_client_mock = self.patchobject(my_resource, 'get_client')
get_client_mock.return_value = fakeclient.FakeDockerClient() get_client_mock.return_value = fakeclient.FakeDockerClient()
get_client_mock.return_value.set_api_version('1.16') get_client_mock.return_value.set_api_version(low_version)
self.assertIsNone(my_resource.validate()) msg = self.assertRaises(docker_container.InvalidArgForVersion,
msg = self.assertRaises(exception.ResourceFailure, my_resource.validate)
scheduler.TaskRunner(my_resource.create)) min_version = docker_container.MIN_API_VERSION_MAP[arg]
expected = ('InvalidArgForVersion: "read_only" is not supported ' args = dict(arg=arg, min_version=min_version)
'for API version < "1.17"') expected = _('"%(arg)s" is not supported for API version '
'< "%(min_version)s"') % args
self.assertEqual(expected, six.text_type(msg)) self.assertEqual(expected, six.text_type(msg))
def test_start_with_read_only_for_low_api_version(self):
self.arg_for_low_api_version('read_only', True, '1.16')
def test_compare_version(self): def test_compare_version(self):
self.assertEqual(docker_container.compare_version('1.17', '1.17'), 0) self.assertEqual(docker_container.compare_version('1.17', '1.17'), 0)
self.assertEqual(docker_container.compare_version('1.17', '1.16'), -1) self.assertEqual(docker_container.compare_version('1.17', '1.16'), -1)
self.assertEqual(docker_container.compare_version('1.17', '1.18'), 1) self.assertEqual(docker_container.compare_version('1.17', '1.18'), 1)
def test_create_with_cpu_shares(self):
t = template_format.parse(template)
stack = utils.parse_stack(t)
definition = stack.t.resource_definitions(stack)['Blog']
definition['Properties']['cpu_shares'] = 512
my_resource = docker_container.DockerContainer(
'Blog', definition, stack)
get_client_mock = self.patchobject(my_resource, 'get_client')
get_client_mock.return_value = fakeclient.FakeDockerClient()
self.assertIsNone(my_resource.validate())
scheduler.TaskRunner(my_resource.create)()
self.assertEqual((my_resource.CREATE, my_resource.COMPLETE),
my_resource.state)
client = my_resource.get_client()
self.assertEqual(['samalba/wordpress'], client.pulled_images)
self.assertEqual(512, client.container_create[0]['cpu_shares'])
def test_create_with_cpu_shares_for_low_api_version(self):
self.arg_for_low_api_version('cpu_shares', 512, '1.7')