Add detailed parameters for Capsule create

Currently we don't have detailed parameters checking
when creating a capsule, this patch add the volume
parameters type checking and several test cases.

Also modify the related template file and design document.
Change some parameters in the template to Kubernetes
friendly.

Modify the field check from "spec" to "template" in
check_capsule_template, since there already a low level spec
field in capsule yaml. Will also modify the python-zunclient.

Part of blueprint introduce-compose

Change-Id: I88c1c248d83d0a27f5a291fcf9d952bb70234dff
Signed-off-by: Kevin Zhao <kevin.zhao@arm.com>
This commit is contained in:
Kevin Zhao 2017-12-26 14:59:28 +08:00
parent 97524cba92
commit d4a5f9c5e9
11 changed files with 529 additions and 98 deletions

View File

@ -164,7 +164,7 @@ Sample capsule:
env:
PATH: /usr/local/bin
resources:
allocation:
requests:
cpu: 1
memory: 2GB
volumes:
@ -219,7 +219,7 @@ Ports fields:
* protocol(string): TCP or UDP, by default is TCP
RecourcesObject fields:
* allocation(AllocationObject): the resources that the capsule needed
* requests(AllocationObject): the resources that the capsule needed
AllocationObject:
* cpu(string): cpu resources, cores number

View File

@ -1,18 +1,17 @@
capsule_template_version: 2017-12-20
# use "-" because that the fields have many items
capsule_version: beta
capsuleVersion: beta
kind: capsule
metadata:
name: capsule-volume
labels:
foo: bar
restart_policy: always
restartPolicy: always
spec:
containers:
- image: test
command:
- "/bin/bash"
workdir: /root
workDir: /root
labels:
app: web
volumeMounts:

View File

@ -1,33 +1,30 @@
capsule_template_version: 2017-06-21
# use "-" because that the fields have many items
capsule_version: beta
capsuleVersion: beta
kind: capsule
metadata:
name: capsule-example
labels:
app: web
nihao: baibai
restart_policy: always
app1: web1
restartPolicy: always
spec:
containers:
- image: ubuntu
command:
- "/bin/bash"
image_pull_policy: ifnotpresent
workdir: /root
labels:
app: web
imagePullPolicy: ifnotpresent
workDir: /root
ports:
- name: nginx-port
containerPort: 80
hostPort: 80
protocol: TCP
resources:
allocation:
requests:
cpu: 1
memory: 1024
environment:
PATCH: /usr/local/bin
env:
ENV1: /usr/local/bin
volumeMounts:
- name: volume1
mountPath: /data1
@ -38,10 +35,8 @@ spec:
args:
- "Hello"
- "World"
image_pull_policy: ifnotpresent
workdir: /root
labels:
app: web01
imagePullPolicy: ifnotpresent
workDir: /root
ports:
- name: nginx-port
containerPort: 80
@ -52,11 +47,11 @@ spec:
hostPort: 3306
protocol: TCP
resources:
allocation:
requests:
cpu: 1
memory: 1024
environment:
NWH: /usr/bin/
env:
ENV2: /usr/bin/
volumeMounts:
- name: volume2
mountPath: /data2

View File

@ -131,8 +131,8 @@ class CapsuleController(base.Controller):
action="capsule:create")
# Abstract the capsule specification
capsules_spec = capsule_dict['spec']
spec_content = utils.check_capsule_template(capsules_spec)
capsules_template = capsule_dict.get('template')
spec_content = utils.check_capsule_template(capsules_template)
containers_spec = utils.capsule_get_container_spec(spec_content)
volumes_spec = utils.capsule_get_volume_spec(spec_content)
@ -148,10 +148,11 @@ class CapsuleController(base.Controller):
capsule_need_memory = 0
container_volume_requests = []
capsule_restart_policy = capsules_spec.get('restart_policy', 'always')
capsule_restart_policy = capsules_template.get('restart_policy',
'always')
metadata_info = capsules_spec.get('metadata', None)
requested_networks_info = capsules_spec.get('nets', [])
metadata_info = capsules_template.get('metadata', None)
requested_networks_info = capsules_template.get('nets', [])
requested_networks = \
utils.build_requested_networks(context, requested_networks_info)
@ -203,7 +204,7 @@ class CapsuleController(base.Controller):
if container_dict.get('resources'):
resources_list = container_dict.get('resources')
allocation = resources_list.get('allocation')
allocation = resources_list.get('requests')
if allocation.get('cpu'):
capsule_need_cpu += allocation.get('cpu')
container_dict['cpu'] = allocation.get('cpu')

View File

@ -15,12 +15,12 @@
from zun.common.validation import parameter_types
_capsule_properties = {
'spec': parameter_types.spec
'template': parameter_types.capsule_template
}
capsule_create = {
'type': 'object',
'properties': _capsule_properties,
'required': ['spec'],
'required': ['template'],
'additionalProperties': False
}

View File

@ -76,6 +76,28 @@ VALID_STATES = {
consts.PAUSED]
}
VALID_CONTAINER_FILED = {
'image': 'image',
'command': 'command',
'args': 'args',
'resources': 'resources',
'ports': 'ports',
'volumeMounts': 'volumeMounts',
'env': 'environment',
'workDir': 'workdir',
'imagePullPolicy': 'image_pull_policy',
}
VALID_CAPSULE_FIELD = {
'restartPolicy': 'restart_policy',
}
VALID_CAPSULE_RESTART_POLICY = {
'Never': 'no',
'Always': 'always',
'OnFailure': 'on-failure',
}
def validate_container_state(container, action):
if container.status not in VALID_STATES[action]:
@ -344,6 +366,12 @@ def check_capsule_template(tpl):
if kind_field not in ['capsule', 'Capsule']:
raise exception.InvalidCapsuleTemplate("kind fields need to be "
"set as capsule or Capsule")
# Align the Capsule restartPolicy with container restart_policy
if 'restartPolicy' in tpl.keys():
tpl['restartPolicy'] = \
VALID_CAPSULE_RESTART_POLICY[tpl['restartPolicy']]
tpl[VALID_CAPSULE_FIELD['restartPolicy']] = tpl.pop('restartPolicy')
spec_field = tpl.get('spec')
if spec_field is None:
raise exception.InvalidCapsuleTemplate("No Spec found")
@ -360,10 +388,15 @@ def capsule_get_container_spec(spec_field):
"container at least")
for i in range(0, containers_num):
container_image = containers_spec[i].get('image')
if container_image is None:
container_spec = containers_spec[i]
if 'image' not in container_spec.keys():
raise exception.InvalidCapsuleTemplate("Container "
"image is needed")
# Remap the Capsule's container fields to native Zun container fields.
for key in list(container_spec.keys()):
container_spec[VALID_CONTAINER_FILED[key]] = \
container_spec.pop(key)
return containers_spec

View File

@ -256,6 +256,176 @@ security_groups = {
}
}
spec = {
'type': ['object'],
capsule_kind = {
"type": ["string"],
'enum': ['capsule', 'Capsule']
}
capsule_version = {
"type": ["string"],
'enum': ['beta', 'Beta']
}
capsule_metadata = {
"type": ["object"],
"properties": {
"labels": labels,
# use the same format as container name
"name": container_name,
}
}
capsule_restart_policy = {
"type": ["string"],
"enum": ['Always', 'OnFailure', 'Never']
}
capsule_container_command = {
'type': ['array'],
'items': command
}
capsule_container_args = capsule_container_command
capsule_container_resources = {
'type': ['object'],
'properties': {
'requests': {
"type": ["object"],
'properties': {
'cpu': cpu,
'memory': memory,
},
'additionalProperties': False,
},
},
"additionalProperties": False,
"required": ['requests']
}
capsule_port_protocol = {
"type": ["string"],
'enum': ['TCP', 'UDP']
}
capsule_container_ports = {
'type': ['array'],
'items': {
'type': 'object',
'properties': {
'name': container_name,
'containerPort': non_negative_integer,
'hostPort': non_negative_integer,
'protocol': capsule_port_protocol,
},
'additionalProperties': False,
'required': ['containerPort', 'hostPort']
}
}
volume_name = {
'type': ['string'],
'minLength': 2,
'maxLength': 255,
'pattern': '^[a-zA-Z0-9][a-zA-Z0-9_.-]+$'
}
capsule_volume_path = {
'type': ['string']
}
capsule_container_volume_list = {
'type': ['array'],
'items': {
'type': 'object',
'properties': {
'name': volume_name,
'mountPath': capsule_volume_path,
'readOnly': boolean,
},
'additionalProperties': False,
'required': ['name', 'mountPath']
}
}
capsule_containers_list = {
'type': ['array'],
'items': {
'type': 'object',
'properties': {
'image': image_name,
'command': capsule_container_command,
'args': capsule_container_args,
'resources': capsule_container_resources,
'ports': capsule_container_ports,
'volumeMounts': capsule_container_volume_list,
'env': environment,
'workDir': workdir,
'imagePullPolicy': image_pull_policy,
},
'additionalProperties': False,
'required': ['image']
}
}
volume_size = {
'type': ['number'],
'pattern': '^[0-9]*$',
'minLength': 1
}
volume_auto_remove = {
'type': boolean,
}
volume_uuid = {
'type': 'string',
'maxLength': 36,
'minLength': 36
}
capsule_cinder_volume = {
'type': 'object',
'properties': {
'volumeID': volume_uuid,
'size': volume_size,
'autoRemove': boolean,
},
'additionalProperties': False,
}
capsule_volumes_list = {
'type': ['array', 'null'],
'items': {
'type': 'object',
'properties': {
'name': image_name,
'cinder': capsule_cinder_volume,
},
'additionalProperties': True,
'required': ['name', 'cinder']
}
}
capsule_spec = {
'type': ['object'],
"properties": {
"containers": capsule_containers_list,
"volumes": capsule_volumes_list,
},
"additionalProperties": True,
"required": ['containers']
}
capsule_template = {
'type': ['object'],
"properties": {
"kind": capsule_kind,
"capsuleVersion": capsule_version,
"metadata": capsule_metadata,
"restartPolicy": capsule_restart_policy,
"spec": capsule_spec,
},
"additionalProperties": False,
"required": ['kind', 'spec', 'metadata']
}

View File

@ -28,14 +28,14 @@ class TestCapsuleController(api_base.FunctionalTest):
def test_create_capsule(self, mock_capsule_create,
mock_neutron_get_network):
params = ('{'
'"spec": '
'"template": '
'{"kind": "capsule",'
' "spec": {'
' "containers":'
' [{"environment": {"ROOT_PASSWORD": "foo0"}, '
' "image": "test", "labels": {"app": "web"}, '
' "image_driver": "docker", "resources": '
' {"allocation": {"cpu": 1, "memory": 1024}}'
' [{"env": {"ROOT_PASSWORD": "foo0"}, '
' "image": "test",'
' "resources": '
' {"requests": {"cpu": 1, "memory": 1024}}'
' }]'
' }, '
' "metadata": {"labels": {"foo0": "bar0", "foo1": "bar1"},'
@ -66,21 +66,16 @@ class TestCapsuleController(api_base.FunctionalTest):
def test_create_capsule_two_containers(self, mock_capsule_create,
mock_neutron_get_network):
params = ('{'
'"spec": '
'"template": '
'{"kind": "capsule",'
' "spec": {'
' "containers":'
' [{"environment": {"ROOT_PASSWORD": "foo0"}, '
' "image": "test", "labels": {"app": "web"}, '
' "image_driver": "docker", "resources": '
' {"allocation": {"cpu": 1, "memory": 1024}}},'
' {"environment": {"ROOT_PASSWORD": "foo1"}, '
' "image": "test1", "labels": {"app1": "web1"}, '
' "image_driver": "docker", "resources": '
' {"allocation": {"cpu": 1, "memory": 1024}}}'
' ]'
' [{"image": "test", "resources": '
' {"requests": {"cpu": 1, "memory": 1024}}}, '
' {"image": "test1", "resources": '
' {"requests": {"cpu": 1, "memory": 1024}}}]'
' }, '
' "metadata": {"labels": {"foo0": "bar0", "foo1": "bar1"},'
' "metadata": {"labels": {"foo0": "bar0"},'
' "name": "capsule-example"}'
' }'
'}')
@ -89,7 +84,7 @@ class TestCapsuleController(api_base.FunctionalTest):
content_type='application/json')
return_value = response.json
expected_meta_name = "capsule-example"
expected_meta_labels = {"foo0": "bar0", "foo1": "bar1"}
expected_meta_labels = {"foo0": "bar0"}
expected_memory = '2048M'
expected_cpu = 2.0
expected_container_num = 3
@ -109,12 +104,11 @@ class TestCapsuleController(api_base.FunctionalTest):
@patch('zun.common.utils.check_capsule_template')
def test_create_capsule_wrong_kind_set(self, mock_check_template,
mock_capsule_create):
params = ('{"spec": {"kind": "test",'
params = ('{"template": {"kind": "test",'
'"spec": {"containers":'
'[{"environment": {"ROOT_PASSWORD": "foo0"}, '
'"image": "test1", "labels": {"app0": "web0"}, '
'"image_driver": "docker", "resources": '
'{"allocation": {"cpu": 1, "memory": 1024}}}]}, '
'"image": "test1", "resources": '
'{"requests": {"cpu": 1, "memory": 1024}}}]}, '
'"metadata": {"labels": {"foo0": "bar0"}, '
'"name": "capsule-example"}}}')
mock_check_template.side_effect = exception.InvalidCapsuleTemplate(
@ -127,7 +121,7 @@ class TestCapsuleController(api_base.FunctionalTest):
@patch('zun.common.utils.check_capsule_template')
def test_create_capsule_less_than_one_container(self, mock_check_template,
mock_capsule_create):
params = ('{"spec": {"kind": "capsule",'
params = ('{"template": {"kind": "capsule",'
'"spec": {container:[]}, '
'"metadata": {"labels": {"foo0": "bar0"}, '
'"name": "capsule-example"}}}')
@ -141,7 +135,7 @@ class TestCapsuleController(api_base.FunctionalTest):
@patch('zun.common.utils.check_capsule_template')
def test_create_capsule_no_container_field(self, mock_check_template,
mock_capsule_create):
params = ('{"spec": {"kind": "capsule",'
params = ('{"template": {"kind": "capsule",'
'"spec": {}, '
'"metadata": {"labels": {"foo0": "bar0"}, '
'"name": "capsule-example"}}}')
@ -155,8 +149,8 @@ class TestCapsuleController(api_base.FunctionalTest):
@patch('zun.common.utils.check_capsule_template')
def test_create_capsule_no_container_image(self, mock_check_template,
mock_capsule_create):
params = ('{"spec": {"kind": "capsule",'
'"spec": {container:[{"environment": '
params = ('{"template": {"kind": "capsule",'
'"spec": {container:[{"env": '
'{"ROOT_PASSWORD": "foo1"}]}, '
'"metadata": {"labels": {"foo0": "bar0"}, '
'"name": "capsule-example"}}}')
@ -174,18 +168,17 @@ class TestCapsuleController(api_base.FunctionalTest):
mock_neutron_get_network,
mock_create_volume,
mock_ensure_volume_usable):
fake_volume_id = 'fakevolid'
fake_volume_id = '3259309d-659c-4e20-b354-ee712e64b3b2'
fake_volume = mock.Mock(id=fake_volume_id)
mock_create_volume.return_value = fake_volume
params = ('{'
'"spec":'
'"template":'
'{"kind": "capsule",'
' "spec":'
' {"containers":'
' [{"environment": {"ROOT_PASSWORD": "foo0"}, '
' "image": "test", "labels": {"app": "web"}, '
' "image_driver": "docker", "resources": '
' {"allocation": {"cpu": 1, "memory": 1024}},'
' [{"env": {"ROOT_PASSWORD": "foo0"}, '
' "image": "test", "resources": '
' {"requests": {"cpu": 1, "memory": 1024}},'
' "volumeMounts": [{"name": "volume1", '
' "mountPath": "/data1"}]'
' }'
@ -195,7 +188,8 @@ class TestCapsuleController(api_base.FunctionalTest):
' "cinder": {"size": 3, "autoRemove": "True"}'
' }]'
' }, '
' "metadata": {"labels": {"foo0": "bar0", "foo1": "bar1"},'
' "metadata": {"labels": '
' {"foo0": "bar0", "foo1": "bar1"},'
' "name": "capsule-example"}'
' }'
'}')
@ -227,28 +221,29 @@ class TestCapsuleController(api_base.FunctionalTest):
mock_neutron_get_network,
mock_search_volume,
mock_ensure_volume_usable):
fake_volume_id = 'fakevolid'
fake_volume_id = '3259309d-659c-4e20-b354-ee712e64b3b2'
fake_volume = mock.Mock(id=fake_volume_id)
mock_search_volume.return_value = fake_volume
params = ('{'
'"spec":'
'"template":'
'{"kind": "capsule",'
' "spec":'
' {"containers":'
' [{"environment": {"ROOT_PASSWORD": "foo0"}, '
' "image": "test", "labels": {"app": "web"}, '
' "image_driver": "docker", "resources": '
' {"allocation": {"cpu": 1, "memory": 1024}},'
' [{"env": {"ROOT_PASSWORD": "foo0"}, '
' "image": "test", "resources": '
' {"requests": {"cpu": 1, "memory": 1024}},'
' "volumeMounts": [{"name": "volume1", '
' "mountPath": "/data1"}]'
' }'
' ],'
' "volumes":'
' [{"name": "volume1",'
' "cinder": {"volumeID": "fakevolid"}'
' "cinder": {"volumeID": '
' "3259309d-659c-4e20-b354-ee712e64b3b2"}'
' }]'
' }, '
' "metadata": {"labels": {"foo0": "bar0", "foo1": "bar1"},'
' "metadata": {"labels": '
' {"foo0": "bar0", "foo1": "bar1"},'
' "name": "capsule-example"}'
' }'
'}')
@ -283,30 +278,30 @@ class TestCapsuleController(api_base.FunctionalTest):
mock_search_volume,
mock_ensure_volume_usable,
mock_create_volume):
fake_volume_id1 = 'fakevolid1'
fake_volume_id1 = '3259309d-659c-4e20-b354-ee712e64b3b2'
fake_volume = mock.Mock(id=fake_volume_id1)
mock_search_volume.return_value = fake_volume
fake_volume_id2 = 'fakevolid2'
fake_volume_id2 = 'ef770cfb-349a-483a-97f6-b86e46e344b8'
fake_volume = mock.Mock(id=fake_volume_id2)
mock_create_volume.return_value = fake_volume
params = ('{'
'"spec":'
'"template":'
'{"kind": "capsule",'
' "spec":'
' {"containers":'
' [{"environment": {"ROOT_PASSWORD": "foo0"}, '
' "image": "test", "labels": {"app": "web"}, '
' "image_driver": "docker", "resources": '
' {"allocation": {"cpu": 1, "memory": 1024}},'
' [{"env": {"ROOT_PASSWORD": "foo0"}, '
' "image": "test", "resources": '
' {"requests": {"cpu": 1, "memory": 1024}},'
' "volumeMounts": [{"name": "volume1", '
' "mountPath": "/data1"},'
' {"name": "volume2", '
' {"name": "volume2", '
' "mountPath": "/data2"}]'
' }'
' ],'
' "volumes":'
' [{"name": "volume1",'
' "cinder": {"volumeID": "fakevolid1"}},'
' "cinder": {"volumeID": '
' "3259309d-659c-4e20-b354-ee712e64b3b2"}},'
' {"name": "volume2",'
' "cinder": {"size": 3, "autoRemove": "True"}'
' }]'

View File

@ -143,6 +143,12 @@ class TestUtils(base.TestCase):
params = ({"kind": "capsule", "spec": {}})
utils.check_capsule_template(params)
params = ({"kind": "capsule", "restartPolicy": "Always", "spec": {
"containers": [{"image": "test1"}]
}})
utils.check_capsule_template(params)
self.assertEqual(params["restart_policy"], "always")
def test_capsule_get_container_spec(self):
with self.assertRaisesRegex(
exception.InvalidCapsuleTemplate,
@ -163,6 +169,13 @@ class TestUtils(base.TestCase):
{"environment": {"ROOT_PASSWORD": "foo0"}}]})
utils.capsule_get_container_spec(params)
params = ({"containers": [
{"image": "test1", "env": {"ROOT_PASSWORD": "foo0"}}]})
utils.capsule_get_container_spec(params)
self.assertEqual(params.get("containers")[0].get("environment"),
{"ROOT_PASSWORD": "foo0"})
self.assertNotIn("env", params.get("containers"))
def test_capsule_get_volume_spec(self):
with self.assertRaisesRegex(
exception.InvalidCapsuleTemplate,

View File

@ -37,6 +37,15 @@ CONTAINER_CREATE = {
'additionalProperties': False,
}
CAPSULE_CREATE = {
'type': 'object',
'properties': {
'template': parameter_types.capsule_template
},
'required': ['template'],
'additionalProperties': False
}
class TestSchemaValidations(base.BaseTestCase):
def setUp(self):
@ -194,3 +203,213 @@ class TestSchemaValidations(base.BaseTestCase):
"Invalid input for field"
" 'runtime'"):
self.schema_validator.validate(request_to_validate)
class TestCapsuleSchemaValidations(base.BaseTestCase):
def setUp(self):
super(TestCapsuleSchemaValidations, self).setUp()
self.schema_validator = validators.SchemaValidator(CAPSULE_CREATE)
def test_create_schema_with_all_valid_parameters(self):
request_to_validate = \
{"template":
{"kind": "capsule",
"capsuleVersion": "beta",
"metadata": {
"labels": {"app": "web"},
"name": "template"},
"restartPolicy": "Always",
"spec": {
"containers": [
{"workDir": "/root", "image": "ubuntu",
"volumeMounts": [{"readOnly": True,
"mountPath": "/data1",
"name": "volume1"}],
"command": ["/bin/bash"],
"env": {"ENV2": "/usr/bin"},
"imagePullPolicy": "ifnotpresent",
"ports": [{"containerPort": 80,
"protocol": "TCP",
"name": "nginx-port",
"hostPort": 80}],
"resources": {"requests": {"cpu": 1,
"memory": 1024}}}],
"volumes": [
{"cinder": {"autoRemove": True, "size": 5},
"name": "volume1"}
]}}}
self.schema_validator.validate(request_to_validate)
def test_create_schema_kind_missing(self):
request_to_validate = \
{"template":
{"capsuleVersion": "beta",
"metadata": {
"labels": {"app": "web"},
"name": "template"},
"spec": {"containers": [{"image": "test"}]}
}}
with self.assertRaisesRegex(exception.SchemaValidationError,
"'kind' is a required property"):
self.schema_validator.validate(request_to_validate)
def test_create_schema_metadata_missing(self):
request_to_validate = \
{"template":
{"capsuleVersion": "beta",
"kind": "capsule",
"spec": {"containers": [{"image": "test"}]}
}}
with self.assertRaisesRegex(exception.SchemaValidationError,
"'metadata' is a required property"):
self.schema_validator.validate(request_to_validate)
def test_create_schema_spec_missing(self):
request_to_validate = \
{"template":
{"capsuleVersion": "beta",
"kind": "capsule",
"metadata": {
"labels": {"app": "web"},
"name": "template"},
}}
with self.assertRaisesRegex(exception.SchemaValidationError,
"'spec' is a required property"):
self.schema_validator.validate(request_to_validate)
def test_create_schema_with_all_essential_params(self):
request_to_validate = \
{"template":
{"kind": "capsule",
"capsuleVersion": "beta",
"metadata": {
"labels": {},
"name": "test-essential"},
"spec": {
"containers": [
{"image": "test"}]
}}}
self.schema_validator.validate(request_to_validate)
def test_create_schema_capsule_restart_policy(self):
valid_restart_policy = ["Always", "OnFailure", "Never"]
invalid_restart_policy = ["always", "4", "onfailure", "never"]
for restart_policy in valid_restart_policy:
request_to_validate = \
{"template":
{"capsuleVersion": "beta",
"kind": "capsule",
"metadata": {
"labels": {"app": "web"},
"name": "template"},
"restartPolicy": restart_policy,
"spec": {"containers": [{"image": "test"}]}
}}
self.schema_validator.validate(request_to_validate)
for restart_policy in invalid_restart_policy:
request_to_validate = \
{"template":
{"capsuleVersion": "beta",
"kind": "capsule",
"metadata": {
"labels": {"app": "web"},
"name": "template"},
"restartPolicy": restart_policy,
"spec": {"containers": [{"image": "test"}]}
}}
with self.assertRaisesRegex(exception.SchemaValidationError,
"Invalid input for field "
"'restartPolicy'."):
self.schema_validator.validate(request_to_validate)
def test_create_schema_capsule_existed_volume_mounts(self):
request_to_validate = {
"template": {
"kind": "capsule",
"metadata": {},
"spec": {
"containers": [
{"image": "test",
"volumeMounts": [{
"name": "volume1",
"mountPath": "/data"}]
}],
"volumes": [
{"name": "volume1",
"cinder": {
"volumeID":
"d2a28af0-e243-4525-adf9-2d091466e43d"}
}
]
}
}
}
self.schema_validator.validate(request_to_validate)
def test_create_schema_capsule_new_volume_mounts(self):
request_to_validate = {
"template": {
"kind": "capsule",
"metadata": {},
"spec": {
"containers": [
{"image": "test",
"volumeMounts": [{
"name": "volume1",
"mountPath": "/data"}]
}],
"volumes": [
{"name": "volume1",
"cinder": {
"size": 5,
"autoRemove": True}
}
]
}
}
}
self.schema_validator.validate(request_to_validate)
def test_create_schema_capsule_volume_no_cinder(self):
request_to_validate = {
"template": {
"kind": "capsule",
"metadata": {},
"spec": {
"containers": [
{"image": "test"}],
"volumes": [
{"name": "volume1",
"no-cinder-driver": {
"size": 5,
"autoRemove": True}
}
]
}
}
}
with self.assertRaisesRegex(exception.SchemaValidationError,
"'cinder' is a required property"):
self.schema_validator.validate(request_to_validate)
def test_create_schema_capsule_volume_no_name(self):
request_to_validate = {
"template": {
"kind": "capsule",
"metadata": {},
"spec": {
"containers": [
{"image": "test"}],
"volumes": [
{
"cinder": {
"size": 5,
"autoRemove": True}
}
]
}
}
}
with self.assertRaisesRegex(exception.SchemaValidationError,
"'name' is a required property"):
self.schema_validator.validate(request_to_validate)

View File

@ -20,20 +20,26 @@ from zun.db import api as db_api
CONF = cfg.CONF
CAPSULE_SPEC = {"kind": "capsule", "capsule_template_version": "2017-06-21",
"capsule_version": "beta", "restart_policy": "always",
CAPSULE_SPEC = {"kind": "capsule",
"capsuleVersion": "beta",
"restartPolicy": "Always",
"spec": {"containers":
[{"environment": {"MYSQL_ROOT_PASSWORD": "password"},
"image": "mysql", "labels": {"app": "web"},
"image_driver": "docker", "resources":
{"allocation": {"cpu": 1,
"memory": 1024}}}],
"volumes": [{"name": "volume1",
"image": "ubuntu-xenial",
"drivers": "cinder",
"volumeType": "type1",
"driverOptions": "options",
"size": "5GB"}]}}
[{"env": {"TEST": "password"},
"image": "test",
"resources":
{"requests": {"cpu": 1, "memory": 1024}},
"volumeMounts": [
{"name": "volume1", "mountPath": "/data1"},
{"name": "volume2", "mountPath": "/data2"}]
}],
"volumes": [
{"name": "volume1",
"cinder": {
"volumeID":
"9600e785-9320-4d3f-ba02-04e3d43fddec"}
},
{"name": "volume2",
"cinder": {"size": 5}}]}}
def get_test_container(**kwargs):