summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVladimir Kuklin <vkuklin@mirantis.com>2016-04-29 18:14:10 +0300
committerVladimir Kuklin <vkuklin@mirantis.com>2016-05-11 14:25:17 +0300
commitca8240ff61cd36faddccbf85ffce447cfeda2664 (patch)
tree7f5fadfc759b4c7f458b7abfe1df0deb9111ef6b
parent9737039e70536deb4c9e3451b3cffceb419430e4 (diff)
Lift too strict restrictions on cross-deps role name
Allow a user to specify any arbitrary string as role name for cross-deps that could be a regexp or a TASK_ROLE_PATTERN string Log a warning when task is not assigned to roles/groups/fields Set default logging level to INFO Change-Id: I42c2490cf22f53892a189165698d1acd56ee4c74 Closes-bug: #1557997
Notes
Notes (review): Verified+1: Fuel CI <fuel-ci-bot@mirantis.com> Code-Review+1: Alexander Noskov <anoskov@mirantis.com> Code-Review+1: Ilya Kutukov <ikutukov@mirantis.com> Code-Review+1: Georgy Kibardin <gkibardin@mirantis.com> Code-Review+1: Alexander Kislitsky <akislitsky@mirantis.com> Code-Review+2: Igor Kalnitsky <ikalnitsky@mirantis.com> Workflow+1: Igor Kalnitsky <ikalnitsky@mirantis.com> Verified+2: Jenkins Submitted-by: Jenkins Submitted-at: Fri, 13 May 2016 15:25:22 +0000 Reviewed-on: https://review.openstack.org/311142 Project: openstack/fuel-plugins Branch: refs/heads/master
-rw-r--r--fuel_plugin_builder/errors.py4
-rw-r--r--fuel_plugin_builder/logger.py2
-rw-r--r--fuel_plugin_builder/tests/test_base_validator.py5
-rw-r--r--fuel_plugin_builder/tests/test_validator_v4.py96
-rw-r--r--fuel_plugin_builder/validators/base.py10
-rw-r--r--fuel_plugin_builder/validators/formatchecker.py53
-rw-r--r--fuel_plugin_builder/validators/schemas/v4.py38
-rw-r--r--fuel_plugin_builder/validators/validator_v4.py17
8 files changed, 192 insertions, 33 deletions
diff --git a/fuel_plugin_builder/errors.py b/fuel_plugin_builder/errors.py
index fe21caf..12c0df9 100644
--- a/fuel_plugin_builder/errors.py
+++ b/fuel_plugin_builder/errors.py
@@ -33,6 +33,10 @@ class ValidationError(FuelPluginException):
33 pass 33 pass
34 34
35 35
36class TaskFieldError(ValidationError):
37 pass
38
39
36class FileIsEmpty(ValidationError): 40class FileIsEmpty(ValidationError):
37 def __init__(self, file_path): 41 def __init__(self, file_path):
38 super(FileIsEmpty, self).__init__( 42 super(FileIsEmpty, self).__init__(
diff --git a/fuel_plugin_builder/logger.py b/fuel_plugin_builder/logger.py
index 1f588f9..85a9650 100644
--- a/fuel_plugin_builder/logger.py
+++ b/fuel_plugin_builder/logger.py
@@ -20,7 +20,7 @@ import logging
20def configure_logger(debug=False): 20def configure_logger(debug=False):
21 logger = logging.getLogger('fuel_plugin_builder') 21 logger = logging.getLogger('fuel_plugin_builder')
22 22
23 logger.setLevel(logging.CRITICAL) 23 logger.setLevel(logging.INFO)
24 if debug: 24 if debug:
25 logger.setLevel(logging.DEBUG) 25 logger.setLevel(logging.DEBUG)
26 26
diff --git a/fuel_plugin_builder/tests/test_base_validator.py b/fuel_plugin_builder/tests/test_base_validator.py
index f9d9953..42fe604 100644
--- a/fuel_plugin_builder/tests/test_base_validator.py
+++ b/fuel_plugin_builder/tests/test_base_validator.py
@@ -13,7 +13,7 @@
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations 14# License for the specific language governing permissions and limitations
15# under the License. 15# under the License.
16 16import jsonschema
17import mock 17import mock
18 18
19from fuel_plugin_builder import errors 19from fuel_plugin_builder import errors
@@ -37,6 +37,7 @@ class TestBaseValidator(BaseTestCase):
37 self.validator = NewValidator(self.plugin_path) 37 self.validator = NewValidator(self.plugin_path)
38 self.data = {'data': 'data1'} 38 self.data = {'data': 'data1'}
39 self.schema = self.make_schema(['data'], {'data': {'type': 'string'}}) 39 self.schema = self.make_schema(['data'], {'data': {'type': 'string'}})
40 self.format_checker = jsonschema.FormatChecker
40 41
41 @classmethod 42 @classmethod
42 def make_schema(cls, required, properties): 43 def make_schema(cls, required, properties):
@@ -54,7 +55,7 @@ class TestBaseValidator(BaseTestCase):
54 'file_path') 55 'file_path')
55 schema_mock.validate.assert_called_once_with( 56 schema_mock.validate.assert_called_once_with(
56 self.data, 57 self.data,
57 self.schema) 58 self.schema, format_checker=self.format_checker)
58 59
59 def test_validate_schema_raises_error(self): 60 def test_validate_schema_raises_error(self):
60 schema = self.make_schema(['key'], {'key': {'type': 'string'}}) 61 schema = self.make_schema(['key'], {'key': {'type': 'string'}})
diff --git a/fuel_plugin_builder/tests/test_validator_v4.py b/fuel_plugin_builder/tests/test_validator_v4.py
index 9fb1b21..4e2a784 100644
--- a/fuel_plugin_builder/tests/test_validator_v4.py
+++ b/fuel_plugin_builder/tests/test_validator_v4.py
@@ -290,7 +290,7 @@ class TestValidatorV4(TestValidatorV3):
290 mock_data = [{ 290 mock_data = [{
291 'id': 'plugin_task', 291 'id': 'plugin_task',
292 'type': 'puppet', 292 'type': 'puppet',
293 'group': ['controller'], 293 'groups': ['controller'],
294 'reexecute_on': ['bla']}] 294 'reexecute_on': ['bla']}]
295 err_msg = "File '/tmp/plugin_path/deployment_tasks.yaml', " \ 295 err_msg = "File '/tmp/plugin_path/deployment_tasks.yaml', " \
296 "'bla' is not one of" 296 "'bla' is not one of"
@@ -299,8 +299,9 @@ class TestValidatorV4(TestValidatorV3):
299 err_msg, self.validator.check_deployment_tasks_schema) 299 err_msg, self.validator.check_deployment_tasks_schema)
300 300
301 @mock.patch('fuel_plugin_builder.validators.validator_v4.utils') 301 @mock.patch('fuel_plugin_builder.validators.validator_v4.utils')
302 @mock.patch('fuel_plugin_builder.validators.validator_v4.logger')
302 def test_role_attribute_is_required_for_deployment_task_types( 303 def test_role_attribute_is_required_for_deployment_task_types(
303 self, utils_mock, *args): 304 self, logger_mock, utils_mock, *args):
304 deployment_tasks_data = [ 305 deployment_tasks_data = [
305 { 306 {
306 'id': 'plugin_name', 307 'id': 'plugin_name',
@@ -332,31 +333,98 @@ class TestValidatorV4(TestValidatorV3):
332 } 333 }
333 ] 334 ]
334 335
335 err_msg = "File '/tmp/plugin_path/deployment_tasks.yaml', " \
336 "'role' is a required property, value path '0'"
337 for task in deployment_tasks_data: 336 for task in deployment_tasks_data:
338 self.check_raised_exception( 337 utils_mock.parse_yaml.return_value = [task]
339 utils_mock, [task], 338 logger_mock.warn.reset_mock()
340 err_msg, self.validator.check_deployment_tasks) 339 self.validator.check_deployment_tasks()
340 self.assertEqual(logger_mock.warn.call_count, 1)
341 341
342 # This is the section of tests inherited from the v3 validator 342 # This is the section of tests inherited from the v3 validator
343 # where decorators is re-defined for module v4 343 # where decorators is re-defined for module v4
344 344
345 @mock.patch('fuel_plugin_builder.validators.validator_v4.utils') 345 @mock.patch('fuel_plugin_builder.validators.validator_v4.utils')
346 def test_check_deployment_task_role(self, utils_mock, *args):
347 super(TestValidatorV4, self).test_check_deployment_task_role(
348 utils_mock)
349
350 @mock.patch('fuel_plugin_builder.validators.validator_v4.utils')
351 @mock.patch('fuel_plugin_builder.validators.base.utils.exists') 346 @mock.patch('fuel_plugin_builder.validators.base.utils.exists')
352 def test_check_tasks_no_file(self, exists_mock, utils_mock, *args): 347 def test_check_tasks_no_file(self, exists_mock, utils_mock, *args):
353 super(TestValidatorV4, self).test_check_deployment_task_role( 348 super(TestValidatorV4, self).test_check_deployment_task_role(
354 exists_mock, utils_mock) 349 exists_mock, utils_mock)
355 350
356 @mock.patch('fuel_plugin_builder.validators.validator_v4.utils') 351 @mock.patch('fuel_plugin_builder.validators.validator_v4.utils')
352 def test_check_deployment_task_role(self, utils_mock, *args):
353 utils_mock.parse_yaml.return_value = [
354 {'id': 'plugin_name', 'type': 'group', 'groups': ['a', 'b']},
355 {'id': 'plugin_name', 'type': 'group', 'groups': '*'},
356 {'id': 'plugin_name', 'type': 'puppet', 'role': ['a', 'b']},
357 {'id': 'plugin_name', 'type': 'puppet', 'role': '*'},
358 {'id': 'plugin_name', 'type': 'shell', 'roles': ['a', 'b']},
359 {'id': 'plugin_name', 'type': 'shell', 'roles': '*'},
360 {'id': 'plugin_name', 'type': 'skipped', 'role': '/test/'},
361 {'id': 'plugin_name', 'type': 'stage'},
362 {'id': 'plugin_name', 'type': 'reboot', 'groups': 'contrail'},
363 {
364 'id': 'plugin_name',
365 'type': 'copy_files',
366 'role': '*',
367 'parameters': {
368 'files': [
369 {'src': 'some_source', 'dst': 'some_destination'}]}
370 },
371 {
372 'id': 'plugin_name',
373 'type': 'sync',
374 'role': 'plugin_name',
375 'parameters': {
376 'src': 'some_source', 'dst': 'some_destination'}
377 },
378 {
379 'id': 'plugin_name',
380 'type': 'upload_file',
381 'role': '/^.*plugin\w+name$/',
382 'parameters': {
383 'path': 'some_path', 'data': 'some_data'}
384 },
385 ]
386
387 self.validator.check_deployment_tasks()
388
389 @mock.patch('fuel_plugin_builder.validators.validator_v4.utils')
357 def test_check_deployment_task_role_failed(self, utils_mock, *args): 390 def test_check_deployment_task_role_failed(self, utils_mock, *args):
358 super(TestValidatorV4, self).test_check_deployment_task_role_failed( 391 mock_data = [{
359 utils_mock) 392 'id': 'plugin_name',
393 'type': 'group',
394 'role': ['plugin_n@me']}]
395 err_msg = "field should"
396 self.check_raised_exception(
397 utils_mock, mock_data,
398 err_msg, self.validator.check_deployment_tasks)
399
400 @mock.patch('fuel_plugin_builder.validators.validator_v4.utils')
401 def test_check_deployment_task_required_missing(self, utils_mock, *args):
402 mock_data = [{
403 'groups': 'plugin_name',
404 'type': 'puppet'}]
405 err_msg = 'required'
406 self.check_raised_exception(
407 utils_mock, mock_data,
408 err_msg, self.validator.check_deployment_tasks)
409
410 @mock.patch('fuel_plugin_builder.validators.validator_v4.utils')
411 def test_check_deployment_task_required_roles_missing_is_ok(
412 self, utils_mock, *args):
413 utils_mock.parse_yaml.return_value = [{
414 'id': 'plugin_name',
415 'type': 'stage'}]
416 self.validator.check_deployment_tasks()
417
418 @mock.patch('fuel_plugin_builder.validators.validator_v4.utils')
419 def test_check_deployment_task_role_regexp_failed(self, utils_mock, *args):
420 mock_data = [{
421 'id': 'plugin_name',
422 'type': 'group',
423 'role': '/[0-9]++/'}]
424 err_msg = "field should.*multiple repeat"
425 self.check_raised_exception(
426 utils_mock, mock_data,
427 err_msg, self.validator.check_deployment_tasks)
360 428
361 @mock.patch('fuel_plugin_builder.validators.validator_v4.utils') 429 @mock.patch('fuel_plugin_builder.validators.validator_v4.utils')
362 def test_check_group_type_deployment_task_does_not_contain_manifests( 430 def test_check_group_type_deployment_task_does_not_contain_manifests(
diff --git a/fuel_plugin_builder/validators/base.py b/fuel_plugin_builder/validators/base.py
index f5e45af..078c6b2 100644
--- a/fuel_plugin_builder/validators/base.py
+++ b/fuel_plugin_builder/validators/base.py
@@ -35,15 +35,16 @@ class BaseValidator(object):
35 def basic_version(self): 35 def basic_version(self):
36 pass 36 pass
37 37
38 def __init__(self, plugin_path): 38 def __init__(self, plugin_path, format_checker=jsonschema.FormatChecker):
39 self.plugin_path = plugin_path 39 self.plugin_path = plugin_path
40 self.format_checker = format_checker
40 41
41 def validate_schema(self, data, schema, file_path, value_path=None): 42 def validate_schema(self, data, schema, file_path, value_path=None):
42 logger.debug( 43 logger.debug(
43 'Start schema validation for %s file, %s', file_path, schema) 44 'Start schema validation for %s file, %s', file_path, schema)
44
45 try: 45 try:
46 jsonschema.validate(data, schema) 46 jsonschema.validate(data, schema,
47 format_checker=self.format_checker)
47 except jsonschema.exceptions.ValidationError as exc: 48 except jsonschema.exceptions.ValidationError as exc:
48 raise errors.ValidationError( 49 raise errors.ValidationError(
49 self._make_error_message(exc, file_path, value_path)) 50 self._make_error_message(exc, file_path, value_path))
@@ -104,6 +105,7 @@ class BaseValidator(object):
104 @abc.abstractmethod 105 @abc.abstractmethod
105 def validate(self): 106 def validate(self):
106 """Performs validation 107 """Performs validation
108
107 """ 109 """
108 110
109 def check_schemas(self): 111 def check_schemas(self):
@@ -169,9 +171,11 @@ class BaseValidator(object):
169 171
170 def check_compatibility(self): 172 def check_compatibility(self):
171 """Json schema doesn't have any conditions, so we have 173 """Json schema doesn't have any conditions, so we have
174
172 to make sure here, that this validation schema can be used 175 to make sure here, that this validation schema can be used
173 for described fuel releases 176 for described fuel releases
174 """ 177 """
178
175 meta = utils.parse_yaml(self.meta_path) 179 meta = utils.parse_yaml(self.meta_path)
176 for fuel_release in meta['fuel_version']: 180 for fuel_release in meta['fuel_version']:
177 if StrictVersion(fuel_release) < StrictVersion(self.basic_version): 181 if StrictVersion(fuel_release) < StrictVersion(self.basic_version):
diff --git a/fuel_plugin_builder/validators/formatchecker.py b/fuel_plugin_builder/validators/formatchecker.py
new file mode 100644
index 0000000..7d8d76e
--- /dev/null
+++ b/fuel_plugin_builder/validators/formatchecker.py
@@ -0,0 +1,53 @@
1# -*- coding: utf-8 -*-
2
3# Copyright 2016 Mirantis, Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
17import re
18from sre_constants import error as sre_error
19
20import jsonschema
21import six
22
23from fuel_plugin_builder import errors
24
25
26class FormatChecker(jsonschema.FormatChecker):
27
28 def __init__(self, role_patterns=(), *args, **kwargs):
29 super(FormatChecker, self).__init__()
30
31 @self.checks('fuel_task_role_format')
32 def _validate_role(instance):
33 sre_msg = None
34 if isinstance(instance, six.string_types):
35 if instance.startswith('/') and instance.endswith('/'):
36 try:
37 re.compile(instance[1:-1])
38 return True
39 except sre_error as e:
40 sre_msg = str(e)
41 else:
42 for role_pattern in role_patterns:
43 if re.match(role_pattern, instance):
44 return True
45 err_msg = "Role field should be either a valid " \
46 "regexp enclosed by " \
47 "slashes or a string of '{0}' or an " \
48 "array of those. " \
49 "Got '{1}' instead.".format(", ".join(role_patterns),
50 instance)
51 if sre_msg:
52 err_msg += "SRE error: \"{0}\"".format(sre_msg)
53 raise errors.TaskFieldError(err_msg)
diff --git a/fuel_plugin_builder/validators/schemas/v4.py b/fuel_plugin_builder/validators/schemas/v4.py
index 6f206e2..dbb370f 100644
--- a/fuel_plugin_builder/validators/schemas/v4.py
+++ b/fuel_plugin_builder/validators/schemas/v4.py
@@ -27,16 +27,26 @@ COMPATIBLE_COMPONENT_NAME_PATTERN = \
27 '^({0}):([0-9a-z_-]+:)*([0-9a-z_-]+|(\*)?)$'.format(COMPONENTS_TYPES_STR) 27 '^({0}):([0-9a-z_-]+:)*([0-9a-z_-]+|(\*)?)$'.format(COMPONENTS_TYPES_STR)
28 28
29 29
30TASK_NAME_PATTERN = TASK_ROLE_PATTERN = '^[0-9a-zA-Z_-]+$' 30TASK_NAME_PATTERN = TASK_ROLE_PATTERN = '^[0-9a-zA-Z_-]+$|^\*$'
31NETWORK_ROLE_PATTERN = '^[0-9a-z_-]+$' 31NETWORK_ROLE_PATTERN = '^[0-9a-z_-]+$'
32FILE_PERMISSIONS_PATTERN = '^[0-7]{4}$' 32FILE_PERMISSIONS_PATTERN = '^[0-7]{4}$'
33TASK_VERSION_PATTERN = '^\d+.\d+.\d+$' 33TASK_VERSION_PATTERN = '^\d+.\d+.\d+$'
34STAGE_PATTERN = '^(post_deployment|pre_deployment)' \ 34STAGE_PATTERN = '^(post_deployment|pre_deployment)' \
35 '(/[-+]?([0-9]*\.[0-9]+|[0-9]+))?$' 35 '(/[-+]?([0-9]*\.[0-9]+|[0-9]+))?$'
36 36
37ROLE_ALIASES = ('roles', 'groups', 'role')
38TASK_OBLIGATORY_FIELDS = ['id', 'type']
39ROLELESS_TASKS = ('stage')
40
37 41
38class SchemaV4(SchemaV3): 42class SchemaV4(SchemaV3):
39 43
44 def __init__(self):
45 super(SchemaV4, self).__init__()
46 self.role_pattern = TASK_ROLE_PATTERN
47 self.roleless_tasks = ROLELESS_TASKS
48 self.role_aliases = ROLE_ALIASES
49
40 @property 50 @property
41 def _task_relation(self): 51 def _task_relation(self):
42 return { 52 return {
@@ -58,13 +68,13 @@ class SchemaV4(SchemaV3):
58 'oneOf': [ 68 'oneOf': [
59 { 69 {
60 'type': 'string', 70 'type': 'string',
61 'enum': ['*', 'master', 'self'] 71 'format': 'fuel_task_role_format'
62 }, 72 },
63 { 73 {
64 'type': 'array', 74 'type': 'array',
65 'items': { 75 'items': {
66 'type': 'string', 76 'type': 'string',
67 'pattern': TASK_ROLE_PATTERN 77 'format': 'fuel_task_role_format'
68 } 78 }
69 } 79 }
70 ] 80 ]
@@ -95,7 +105,8 @@ class SchemaV4(SchemaV3):
95 } 105 }
96 } 106 }
97 107
98 def _gen_task_schema(self, task_types, required=None, parameters=None): 108 def _gen_task_schema(self, task_types, required=None,
109 parameters=None):
99 """Generate deployment task schema using prototype. 110 """Generate deployment task schema using prototype.
100 111
101 :param task_types: task types 112 :param task_types: task types
@@ -119,11 +130,12 @@ class SchemaV4(SchemaV3):
119 } 130 }
120 parameters.setdefault("properties", {}) 131 parameters.setdefault("properties", {})
121 parameters["properties"].setdefault("strategy", self._task_strategy) 132 parameters["properties"].setdefault("strategy", self._task_strategy)
122 133 task_specific_req_fields = list(set(TASK_OBLIGATORY_FIELDS +
134 (required or [])))
123 return { 135 return {
124 '$schema': 'http://json-schema.org/draft-04/schema#', 136 '$schema': 'http://json-schema.org/draft-04/schema#',
125 'type': 'object', 137 'type': 'object',
126 'required': list(set(['id', 'type'] + (required or []))), 138 'required': task_specific_req_fields,
127 'properties': { 139 'properties': {
128 'type': {'enum': task_types}, 140 'type': {'enum': task_types},
129 'id': { 141 'id': {
@@ -132,6 +144,8 @@ class SchemaV4(SchemaV3):
132 'version': { 144 'version': {
133 'type': 'string', "pattern": TASK_VERSION_PATTERN}, 145 'type': 'string', "pattern": TASK_VERSION_PATTERN},
134 'role': self._task_role, 146 'role': self._task_role,
147 'groups': self._task_role,
148 'roles': self._task_role,
135 'required_for': self.task_group, 149 'required_for': self.task_group,
136 'requires': self.task_group, 150 'requires': self.task_group,
137 'cross-depends': { 151 'cross-depends': {
@@ -148,7 +162,7 @@ class SchemaV4(SchemaV3):
148 'pattern': TASK_ROLE_PATTERN}}, 162 'pattern': TASK_ROLE_PATTERN}},
149 'reexecute_on': self._task_reexecute, 163 'reexecute_on': self._task_reexecute,
150 'parameters': parameters or {}, 164 'parameters': parameters or {},
151 } 165 },
152 } 166 }
153 167
154 @property 168 @property
@@ -180,7 +194,7 @@ class SchemaV4(SchemaV3):
180 def copy_files_task(self): 194 def copy_files_task(self):
181 return self._gen_task_schema( 195 return self._gen_task_schema(
182 "copy_files", 196 "copy_files",
183 ['role', 'parameters'], 197 ['parameters'],
184 { 198 {
185 'type': 'object', 199 'type': 'object',
186 'required': ['files'], 200 'required': ['files'],
@@ -203,7 +217,7 @@ class SchemaV4(SchemaV3):
203 217
204 @property 218 @property
205 def group_task(self): 219 def group_task(self):
206 return self._gen_task_schema("group", ['role']) 220 return self._gen_task_schema("group", [])
207 221
208 @property 222 @property
209 def puppet_task(self): 223 def puppet_task(self):
@@ -242,7 +256,7 @@ class SchemaV4(SchemaV3):
242 def shell_task(self): 256 def shell_task(self):
243 return self._gen_task_schema( 257 return self._gen_task_schema(
244 "shell", 258 "shell",
245 ['role'], 259 [],
246 { 260 {
247 'type': 'object', 261 'type': 'object',
248 'required': ['cmd'], 262 'required': ['cmd'],
@@ -271,7 +285,7 @@ class SchemaV4(SchemaV3):
271 def sync_task(self): 285 def sync_task(self):
272 return self._gen_task_schema( 286 return self._gen_task_schema(
273 "sync", 287 "sync",
274 ['role', 'parameters'], 288 ['parameters'],
275 { 289 {
276 'type': 'object', 290 'type': 'object',
277 'required': ['src', 'dst'], 291 'required': ['src', 'dst'],
@@ -287,7 +301,7 @@ class SchemaV4(SchemaV3):
287 def upload_file_task(self): 301 def upload_file_task(self):
288 return self._gen_task_schema( 302 return self._gen_task_schema(
289 "upload_file", 303 "upload_file",
290 ['role', 'parameters'], 304 ['parameters'],
291 { 305 {
292 'type': 'object', 306 'type': 'object',
293 'required': ['path', 'data'], 307 'required': ['path', 'data'],
diff --git a/fuel_plugin_builder/validators/validator_v4.py b/fuel_plugin_builder/validators/validator_v4.py
index bb1ebb9..45839e6 100644
--- a/fuel_plugin_builder/validators/validator_v4.py
+++ b/fuel_plugin_builder/validators/validator_v4.py
@@ -19,9 +19,11 @@ from os.path import join as join_path
19 19
20from fuel_plugin_builder import errors 20from fuel_plugin_builder import errors
21from fuel_plugin_builder import utils 21from fuel_plugin_builder import utils
22from fuel_plugin_builder.validators.formatchecker import FormatChecker
22from fuel_plugin_builder.validators.schemas import SchemaV4 23from fuel_plugin_builder.validators.schemas import SchemaV4
23from fuel_plugin_builder.validators import ValidatorV3 24from fuel_plugin_builder.validators import ValidatorV3
24 25
26
25logger = logging.getLogger(__name__) 27logger = logging.getLogger(__name__)
26 28
27 29
@@ -30,7 +32,8 @@ class ValidatorV4(ValidatorV3):
30 schema = SchemaV4() 32 schema = SchemaV4()
31 33
32 def __init__(self, *args, **kwargs): 34 def __init__(self, *args, **kwargs):
33 super(ValidatorV4, self).__init__(*args, **kwargs) 35 super(ValidatorV4, self).__init__(format_checker=FormatChecker(
36 role_patterns=[self.schema.role_pattern]), *args, **kwargs)
34 self.components_path = join_path(self.plugin_path, 'components.yaml') 37 self.components_path = join_path(self.plugin_path, 'components.yaml')
35 38
36 @property 39 @property
@@ -89,6 +92,18 @@ class ValidatorV4(ValidatorV3):
89 error_msg = 'There is no such task type:' \ 92 error_msg = 'There is no such task type:' \
90 '{0}'.format(deployment_task['type']) 93 '{0}'.format(deployment_task['type'])
91 raise errors.ValidationError(error_msg) 94 raise errors.ValidationError(error_msg)
95 if deployment_task['type'] not in self.schema.roleless_tasks:
96 for role_alias in self.schema.role_aliases:
97 deployment_role = deployment_task.get(role_alias)
98 if deployment_role:
99 break
100 else:
101 logger.warn(
102 'Task {0} does not contain {1} fields. That '
103 'may lead to tasks being unassigned to nodes.'.
104 format(deployment_task['id'], '/'.
105 join(self.schema.role_aliases)))
106
92 self.validate_schema( 107 self.validate_schema(
93 deployment_task, 108 deployment_task,
94 schemas[deployment_task['type']], 109 schemas[deployment_task['type']],