Merge "Docker plugin add read_only property"

This commit is contained in:
Jenkins
2015-03-25 15:19:26 +00:00
committed by Gerrit Code Review
3 changed files with 88 additions and 2 deletions

View File

@@ -14,19 +14,24 @@
# 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 distutils
from oslo_log import log as logging from oslo_log import log as logging
import six import six
from heat.common import exception
from heat.common.i18n import _ from heat.common.i18n import _
from heat.common.i18n import _LW from heat.common.i18n import _LW
from heat.engine import attributes from heat.engine import attributes
from heat.engine import constraints from heat.engine import constraints
from heat.engine import properties from heat.engine import properties
from heat.engine import resource from heat.engine import resource
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'
# conditionally import so tests can work without having the dependency # conditionally import so tests can work without having the dependency
# satisfied # satisfied
try: try:
@@ -42,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, RESTART_POLICY, CAP_ADD, CAP_DROP, READ_ONLY,
) = ( ) = (
'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' 'restart_policy', 'cap_add', 'cap_drop', 'read_only'
) )
ATTRIBUTES = ( ATTRIBUTES = (
@@ -210,6 +215,14 @@ class DockerContainer(resource.Resource):
] ]
), ),
default=[] default=[]
),
READ_ONLY: properties.Schema(
properties.Schema.BOOLEAN,
_('If true, mount the container\'s root filesystem '
'as read only (only supported for API version >= %s).') %
READ_ONLY_MIN_API_VERSION,
default=False,
support_status=support.SupportStatus(version='2015.1'),
) )
} }
@@ -330,6 +343,7 @@ class DockerContainer(resource.Resource):
'name': self.properties[self.NAME] 'name': self.properties[self.NAME]
} }
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']
@@ -353,6 +367,13 @@ class DockerContainer(resource.Resource):
start_args['cap_add'] = self.properties[self.CAP_ADD] start_args['cap_add'] = self.properties[self.CAP_ADD]
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 compare_version(READ_ONLY_MIN_API_VERSION, version) >= 0:
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
@@ -428,3 +449,19 @@ def available_resource_mapping():
else: else:
LOG.warn(_LW("Docker plug-in loaded, but docker lib not installed.")) LOG.warn(_LW("Docker plug-in loaded, but docker lib not installed."))
return {} return {}
def compare_version(v1, v2):
s1 = distutils.version.StrictVersion(v1)
s2 = distutils.version.StrictVersion(v2)
if s1 == s2:
return 0
elif s1 > s2:
return -1
else:
return 1
class InvalidArgForVersion(exception.HeatException):
msg_fmt = _('"%(arg)s" is not supported for API version '
'< "%(min_version)s"')

View File

@@ -30,6 +30,7 @@ class FakeDockerClient(object):
self.pulled_images = [] self.pulled_images = []
self.container_create = [] self.container_create = []
self.container_start = [] self.container_start = []
self.version_info = {}
def _generate_string(self, n=32): def _generate_string(self, n=32):
return ''.join(random.choice(string.lowercase) for i in range(n)) return ''.join(random.choice(string.lowercase) for i in range(n))
@@ -88,3 +89,11 @@ class FakeDockerClient(object):
def pull(self, image): def pull(self, image):
self.pulled_images.append(image) self.pulled_images.append(image)
def version(self, api_version=True):
if not self.version_info:
self.version_info['ApiVersion'] = '1.15'
return self.version_info
def set_api_version(self, version):
self.version_info['ApiVersion'] = version

View File

@@ -295,3 +295,43 @@ class DockerContainerTest(common.HeatTestCase):
self.assertEqual(['samalba/wordpress'], client.pulled_images) self.assertEqual(['samalba/wordpress'], client.pulled_images)
self.assertEqual(['NET_ADMIN'], client.container_start[0]['cap_add']) self.assertEqual(['NET_ADMIN'], client.container_start[0]['cap_add'])
self.assertEqual(['MKNOD'], client.container_start[0]['cap_drop']) self.assertEqual(['MKNOD'], client.container_start[0]['cap_drop'])
def test_start_with_read_only(self):
t = template_format.parse(template)
stack = utils.parse_stack(t)
definition = stack.t.resource_definitions(stack)['Blog']
definition['Properties']['read_only'] = True
resource = docker_container.DockerContainer(
'Blog', definition, stack)
get_client_mock = self.patchobject(resource, 'get_client')
get_client_mock.return_value = fakeclient.FakeDockerClient()
get_client_mock.return_value.set_api_version('1.17')
self.assertIsNone(resource.validate())
scheduler.TaskRunner(resource.create)()
self.assertEqual((resource.CREATE, resource.COMPLETE),
resource.state)
client = resource.get_client()
self.assertEqual(['samalba/wordpress'], client.pulled_images)
self.assertIs(True, client.container_start[0]['read_only'])
def test_start_with_read_only_for_low_api_version(self):
t = template_format.parse(template)
stack = utils.parse_stack(t)
definition = stack.t.resource_definitions(stack)['Blog']
definition['Properties']['read_only'] = True
my_resource = docker_container.DockerContainer(
'Blog', definition, stack)
get_client_mock = self.patchobject(my_resource, 'get_client')
get_client_mock.return_value = fakeclient.FakeDockerClient()
get_client_mock.return_value.set_api_version('1.16')
self.assertIsNone(my_resource.validate())
msg = self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(my_resource.create))
expected = ('InvalidArgForVersion: "read_only" is not supported '
'for API version < "1.17"')
self.assertEqual(expected, six.text_type(msg))
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.16'), -1)
self.assertEqual(docker_container.compare_version('1.17', '1.18'), 1)