Fuel UI
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_restriction.py 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2015 Mirantis, Inc.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. # not use this file except in compliance with the License. You may obtain
  6. # a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations
  14. # under the License.
  15. import six
  16. import yaml
  17. from nailgun import errors
  18. from nailgun import objects
  19. from nailgun.settings import settings
  20. from nailgun.test import base
  21. from nailgun.utils.restrictions import AttributesRestriction
  22. from nailgun.utils.restrictions import ComponentsRestrictions
  23. from nailgun.utils.restrictions import LimitsMixin
  24. from nailgun.utils.restrictions import RestrictionBase
  25. from nailgun.utils.restrictions import VmwareAttributesRestriction
  26. DATA = """
  27. attributes:
  28. group:
  29. attribute_1:
  30. name: attribute_1
  31. value: true
  32. restrictions:
  33. - condition: 'settings:group.attribute_2.value == true'
  34. message: 'Only one of attributes 1 and 2 allowed'
  35. - condition: 'settings:group.attribute_3.value == "spam"'
  36. message: 'Only one of attributes 1 and 3 allowed'
  37. attribute_2:
  38. name: attribute_2
  39. value: true
  40. attribute_3:
  41. name: attribute_3
  42. value: spam
  43. restrictions:
  44. - condition: 'settings:group.attribute_3.value ==
  45. settings:group.attribute_4.value'
  46. message: 'Only one of attributes 3 and 4 allowed'
  47. action: enable
  48. attribute_4:
  49. name: attribute_4
  50. value: spam
  51. attribute_5:
  52. name: attribute_5
  53. value: 4
  54. roles_meta:
  55. cinder:
  56. limits:
  57. min: 1
  58. overrides:
  59. - condition: 'settings:group.attribute_2.value == true'
  60. message: 'At most one role_1 node can be added'
  61. max: 1
  62. controller:
  63. limits:
  64. recommended: 'settings:group.attribute_5.value'
  65. mongo:
  66. limits:
  67. max: 12
  68. message: 'At most 12 MongoDB node should be added'
  69. overrides:
  70. - condition: 'settings:group.attribute_3.value == "spam"'
  71. min: 4
  72. message: 'At least 4 MongoDB node can be added if spam'
  73. - condition: 'settings:group.attribute_3.value == "egg"'
  74. recommended: 3
  75. message: "At least 3 MongoDB nodes are recommended"
  76. """
  77. class TestRestriction(base.BaseTestCase):
  78. def setUp(self):
  79. super(TestRestriction, self).setUp()
  80. self.data = yaml.load(DATA)
  81. def test_check_restrictions(self):
  82. attributes = self.data.get('attributes')
  83. for gkey, gvalue in six.iteritems(attributes):
  84. for key, value in six.iteritems(gvalue):
  85. result = RestrictionBase.check_restrictions(
  86. models={'settings': attributes},
  87. restrictions=value.get('restrictions', []))
  88. # check when couple restrictions true for some item
  89. if key == 'attribute_1':
  90. self.assertTrue(result.get('result'))
  91. self.assertEqual(
  92. result.get('message'),
  93. 'Only one of attributes 1 and 2 allowed. ' +
  94. 'Only one of attributes 1 and 3 allowed')
  95. # check when different values uses in restriction
  96. if key == 'attribute_3':
  97. self.assertTrue(result.get('result'))
  98. self.assertEqual(
  99. result.get('message'),
  100. 'Only one of attributes 3 and 4 allowed')
  101. def test_expand_restriction_format(self):
  102. string_restriction = 'settings.some_attribute.value != true'
  103. dict_restriction_long_format = {
  104. 'condition': 'settings.some_attribute.value != true',
  105. 'message': 'Another attribute required'
  106. }
  107. dict_restriction_short_format = {
  108. 'settings.some_attribute.value != true':
  109. 'Another attribute required'
  110. }
  111. result = {
  112. 'action': 'disable',
  113. 'condition': 'settings.some_attribute.value != true',
  114. }
  115. invalid_format = ['some_condition']
  116. # check string format
  117. self.assertDictEqual(
  118. RestrictionBase._expand_restriction(
  119. string_restriction), result)
  120. result['message'] = 'Another attribute required'
  121. # check long format
  122. self.assertDictEqual(
  123. RestrictionBase._expand_restriction(
  124. dict_restriction_long_format), result)
  125. # check short format
  126. self.assertDictEqual(
  127. RestrictionBase._expand_restriction(
  128. dict_restriction_short_format), result)
  129. # check invalid format
  130. self.assertRaises(
  131. errors.InvalidData,
  132. RestrictionBase._expand_restriction,
  133. invalid_format)
  134. class TestLimits(base.BaseTestCase):
  135. def setUp(self):
  136. super(TestLimits, self).setUp()
  137. self.data = yaml.load(DATA)
  138. self.env.create(
  139. nodes_kwargs=[
  140. {"status": "ready", "roles": ["cinder"]},
  141. {"status": "ready", "roles": ["controller"]},
  142. {"status": "ready", "roles": ["mongo"]},
  143. {"status": "ready", "roles": ["mongo"]},
  144. ]
  145. )
  146. def test_check_node_limits(self):
  147. roles = self.data.get('roles_meta')
  148. attributes = self.data.get('attributes')
  149. for role, data in six.iteritems(roles):
  150. result = LimitsMixin().check_node_limits(
  151. models={'settings': attributes},
  152. nodes=self.env.nodes,
  153. role=role,
  154. limits=data.get('limits'))
  155. if role == 'cinder':
  156. self.assertTrue(result.get('valid'))
  157. if role == 'controller':
  158. self.assertFalse(result.get('valid'))
  159. self.assertEqual(
  160. result.get('messages'),
  161. 'Default message')
  162. if role == 'mongo':
  163. self.assertFalse(result.get('valid'))
  164. self.assertEqual(
  165. result.get('messages'),
  166. 'At least 4 MongoDB node can be added if spam')
  167. def test_check_override(self):
  168. roles = self.data.get('roles_meta')
  169. attributes = self.data.get('attributes')
  170. limits = LimitsMixin()
  171. # Set nodes count to 4
  172. limits.count = 4
  173. limits.limit_reached = True
  174. limits.models = {'settings': attributes}
  175. limits.nodes = self.env.nodes
  176. # Set "cinder" role to working on
  177. limits.role = 'cinder'
  178. limits.limit_types = ['max']
  179. limits.checked_limit_types = {}
  180. limits.limit_values = {'max': None}
  181. override_data = roles['cinder']['limits']['overrides'][0]
  182. result = limits._check_override(override_data)
  183. self.assertEqual(
  184. result[0]['message'], 'At most one role_1 node can be added')
  185. def test_get_proper_message(self):
  186. limits = LimitsMixin()
  187. limits.messages = [
  188. {'type': 'min', 'value': '1', 'message': 'Message for min_1'},
  189. {'type': 'min', 'value': '2', 'message': 'Message for min_2'},
  190. {'type': 'max', 'value': '5', 'message': 'Message for max_5'},
  191. {'type': 'max', 'value': '8', 'message': 'Message for max_8'}
  192. ]
  193. self.assertEqual(
  194. limits._get_message('min'), 'Message for min_2')
  195. self.assertEqual(
  196. limits._get_message('max'), 'Message for max_5')
  197. class TestAttributesRestriction(base.BaseTestCase):
  198. def setUp(self):
  199. super(TestAttributesRestriction, self).setUp()
  200. self.cluster = self.env.create(
  201. cluster_kwargs={
  202. 'api': False
  203. }
  204. )
  205. attributes_metadata = """
  206. editable:
  207. access:
  208. user:
  209. value: ""
  210. type: "text"
  211. regex:
  212. source: '\S'
  213. error: "Invalid username"
  214. email:
  215. value: "admin@localhost"
  216. type: "text"
  217. regex:
  218. source: '\S'
  219. error: "Invalid email"
  220. tenant:
  221. value: [""]
  222. type: "text_list"
  223. regex:
  224. source: '\S'
  225. error: "Invalid tenant name"
  226. another_tenant:
  227. value: ["test"]
  228. type: "text_list"
  229. min: 2
  230. max: 2
  231. regex:
  232. source: '\S'
  233. error: "Invalid tenant name"
  234. another_tenant_2:
  235. value: ["test1", "test2", "test3"]
  236. type: "text_list"
  237. min: 2
  238. max: 2
  239. regex:
  240. source: '\S'
  241. error: "Invalid tenant name"
  242. password:
  243. value: "secret"
  244. type: "password"
  245. regex:
  246. source: '\S'
  247. error: "Empty password"
  248. nullable_text:
  249. label: "Nullable text"
  250. value: null
  251. nullable: True
  252. type: "text"
  253. regex:
  254. source: '\S'
  255. error: "Empty value"
  256. not_nullable_text:
  257. label: "Not nullable text"
  258. value: null
  259. type: "text"
  260. nullable_number:
  261. label: "Nullable number"
  262. value: null
  263. nullable: True
  264. type: "number"
  265. not_nullable_number:
  266. label: "Not nullable number"
  267. value: null
  268. type: "number"
  269. """
  270. self.attributes_data = yaml.load(attributes_metadata)
  271. def test_check_with_invalid_values(self):
  272. objects.Cluster.update_attributes(
  273. self.cluster, self.attributes_data)
  274. attributes = objects.Cluster.get_editable_attributes(self.cluster)
  275. models = {
  276. 'settings': attributes,
  277. 'default': attributes,
  278. }
  279. errs = AttributesRestriction.check_data(models, attributes)
  280. self.assertItemsEqual(
  281. errs, ['Invalid username', ['Invalid tenant name'],
  282. "Value ['test'] should have at least 2 items",
  283. "Value ['test1', 'test2', 'test3'] "
  284. "should not have more than 2 items",
  285. "Null value is forbidden for 'Not nullable text'",
  286. "Null value is forbidden for 'Not nullable number'"])
  287. def test_check_with_valid_values(self):
  288. access = self.attributes_data['editable']['access']
  289. access['user']['value'] = 'admin'
  290. access['tenant']['value'] = ['test']
  291. access['another_tenant']['value'] = ['test1', 'test2']
  292. access['another_tenant_2']['value'] = ['test1', 'test2']
  293. access['not_nullable_text']['value'] = 'test'
  294. access['not_nullable_number']['value'] = 123
  295. objects.Cluster.update_attributes(
  296. self.cluster, self.attributes_data)
  297. attributes = objects.Cluster.get_editable_attributes(self.cluster)
  298. models = {
  299. 'settings': attributes,
  300. 'default': attributes,
  301. }
  302. errs = AttributesRestriction.check_data(models, attributes)
  303. self.assertListEqual(errs, [])
  304. class TestVmwareAttributesRestriction(base.BaseTestCase):
  305. def setUp(self):
  306. super(TestVmwareAttributesRestriction, self).setUp()
  307. self.cluster = self.env.create(
  308. cluster_kwargs={
  309. 'api': False
  310. }
  311. )
  312. self.vm_data = self.env.read_fixtures(['vmware_attributes'])[0]
  313. def _get_models(self, attributes, vmware_attributes):
  314. return {
  315. 'settings': attributes,
  316. 'default': vmware_attributes['editable'],
  317. 'current_vcenter': vmware_attributes['editable']['value'].get(
  318. 'availability_zones')[0],
  319. 'glance': vmware_attributes['editable']['value'].get('glance'),
  320. 'cluster': self.cluster,
  321. 'version': settings.VERSION,
  322. 'networking_parameters': self.cluster.network_config
  323. }
  324. def test_check_data_with_empty_values_without_restrictions(self):
  325. attributes = objects.Cluster.get_editable_attributes(self.cluster)
  326. attributes['common']['use_vcenter']['value'] = True
  327. attributes['storage']['images_vcenter']['value'] = True
  328. vmware_attributes = self.vm_data.copy()
  329. empty_values = {
  330. "availability_zones": [
  331. {
  332. "az_name": "",
  333. "vcenter_host": "",
  334. "vcenter_username": "",
  335. "vcenter_password": "",
  336. "vcenter_security_disabled": "",
  337. "vcenter_ca_file": {},
  338. "nova_computes": [
  339. {
  340. "vsphere_cluster": "",
  341. "service_name": "",
  342. "datastore_regex": ""
  343. }
  344. ]
  345. }
  346. ],
  347. "network": {
  348. "esxi_vlan_interface": ""
  349. },
  350. "glance": {
  351. "vcenter_host": "",
  352. "vcenter_username": "",
  353. "vcenter_password": "",
  354. "datacenter": "",
  355. "datastore": "",
  356. "vcenter_security_disabled": "",
  357. "ca_file": {}
  358. }
  359. }
  360. # Update value with empty value
  361. vmware_attributes['editable']['value'] = empty_values
  362. models = self._get_models(attributes, vmware_attributes)
  363. errs = VmwareAttributesRestriction.check_data(
  364. models=models,
  365. metadata=vmware_attributes['editable']['metadata'],
  366. data=vmware_attributes['editable']['value'])
  367. self.assertItemsEqual(
  368. errs,
  369. ['Empty cluster', 'Empty host', 'Empty username',
  370. 'Empty password', 'Empty datacenter', 'Empty datastore'])
  371. def test_check_data_with_invalid_values_without_restrictions(self):
  372. # Disable restrictions
  373. attributes = objects.Cluster.get_editable_attributes(self.cluster)
  374. attributes['common']['use_vcenter']['value'] = True
  375. attributes['storage']['images_vcenter']['value'] = True
  376. # value data taken from fixture one cluster of
  377. # nova computes left empty
  378. vmware_attributes = self.vm_data.copy()
  379. models = self._get_models(attributes, vmware_attributes)
  380. errs = VmwareAttributesRestriction.check_data(
  381. models=models,
  382. metadata=vmware_attributes['editable']['metadata'],
  383. data=vmware_attributes['editable']['value'])
  384. self.assertItemsEqual(errs, ['Empty cluster'])
  385. def test_check_data_with_invalid_values_and_with_restrictions(self):
  386. attributes = objects.Cluster.get_editable_attributes(self.cluster)
  387. # fixture have restrictions enabled for glance that's why
  388. # only 'Empty cluster' should returned
  389. vmware_attributes = self.vm_data.copy()
  390. models = self._get_models(attributes, vmware_attributes)
  391. errs = VmwareAttributesRestriction.check_data(
  392. models=models,
  393. metadata=vmware_attributes['editable']['metadata'],
  394. data=vmware_attributes['editable']['value'])
  395. self.assertItemsEqual(errs, ['Empty cluster'])
  396. def test_check_data_with_valid_values_and_with_restrictions(self):
  397. attributes = objects.Cluster.get_editable_attributes(self.cluster)
  398. vmware_attributes = self.vm_data.copy()
  399. # Set valid data for clusters
  400. for i, azone in enumerate(
  401. vmware_attributes['editable']['value']['availability_zones']):
  402. for j, ncompute in enumerate(azone['nova_computes']):
  403. ncompute['vsphere_cluster'] = 'cluster-{0}-{1}'.format(i, j)
  404. models = self._get_models(attributes, vmware_attributes)
  405. errs = VmwareAttributesRestriction.check_data(
  406. models=models,
  407. metadata=vmware_attributes['editable']['metadata'],
  408. data=vmware_attributes['editable']['value'])
  409. self.assertItemsEqual(errs, [])
  410. def test_check_data_with_valid_values_and_without_restrictions(self):
  411. # Disable restrictions
  412. attributes = objects.Cluster.get_editable_attributes(self.cluster)
  413. attributes['common']['use_vcenter']['value'] = True
  414. attributes['storage']['images_vcenter']['value'] = True
  415. vmware_attributes = self.vm_data.copy()
  416. # Set valid data for clusters
  417. for i, azone in enumerate(
  418. vmware_attributes['editable']['value']['availability_zones']):
  419. for j, ncompute in enumerate(azone['nova_computes']):
  420. ncompute['vsphere_cluster'] = 'cluster-{0}-{1}'.format(i, j)
  421. # Set valid data for glance
  422. glance = vmware_attributes['editable']['value']['glance']
  423. glance['datacenter'] = 'test_datacenter'
  424. glance['datastore'] = 'test_datastore'
  425. models = self._get_models(attributes, vmware_attributes)
  426. errs = VmwareAttributesRestriction.check_data(
  427. models=models,
  428. metadata=vmware_attributes['editable']['metadata'],
  429. data=vmware_attributes['editable']['value'])
  430. self.assertItemsEqual(errs, [])
  431. class TestComponentsRestrictions(base.BaseTestCase):
  432. def setUp(self):
  433. super(TestComponentsRestrictions, self).setUp()
  434. self.required_components_types = ['hypervisor', 'network', 'storage']
  435. self.components_metadata = [
  436. {
  437. 'name': 'hypervisor:test_hypervisor'
  438. },
  439. {
  440. 'name': 'network:core:test_network_1',
  441. 'incompatible': [
  442. {'name': 'hypervisor:test_hypervisor'}
  443. ]
  444. },
  445. {
  446. 'name': 'network:core:test_network_2'
  447. },
  448. {
  449. 'name': 'network:ml2:test_network_3'
  450. },
  451. {
  452. 'name': 'storage:test_storage',
  453. 'compatible': [
  454. {'name': 'hypervisor:test_hypervisor'}
  455. ],
  456. 'requires': [
  457. {'name': 'hypervisor:test_hypervisor'}
  458. ]
  459. },
  460. {
  461. 'name': 'storage:test_storage_2'
  462. }
  463. ]
  464. def test_components_not_in_available_components(self):
  465. self._validate_with_expected_errors(
  466. ['storage:not_existing_component'],
  467. "['storage:not_existing_component'] components are not related to "
  468. "used release."
  469. )
  470. def test_not_all_required_types_components(self):
  471. selected_components_list = [
  472. 'hypervisor:test_hypervisor',
  473. 'network:core:test_network_2',
  474. 'storage:test_storage_2'
  475. ]
  476. ComponentsRestrictions.validate_components(
  477. selected_components_list, self.components_metadata,
  478. self.required_components_types)
  479. while selected_components_list:
  480. selected_components_list.pop()
  481. self._validate_with_expected_errors(
  482. selected_components_list,
  483. "Components with {0} types are required but wasn't found "
  484. "in data.".format(sorted(
  485. set(self.required_components_types) - set(
  486. [x.split(':')[0] for x in selected_components_list])
  487. ))
  488. )
  489. def test_incompatible_components_found(self):
  490. self._validate_with_expected_errors(
  491. ['hypervisor:test_hypervisor', 'network:core:test_network_1'],
  492. "Incompatible components were found: 'network:core:test_network_1'"
  493. " incompatible with ['hypervisor:test_hypervisor']."
  494. )
  495. def test_requires_components_not_found(self):
  496. self._validate_with_expected_errors(
  497. ['storage:test_storage'],
  498. "Component 'storage:test_storage' requires any of components from "
  499. "['hypervisor:test_hypervisor'] set."
  500. )
  501. def test_requires_mixed_format(self):
  502. self.components_metadata.append({
  503. 'name': 'storage:wrong_storage',
  504. 'requires': [
  505. {'any_of': {
  506. 'items': ['network:core:*']
  507. }},
  508. {'name': 'hypervisor:test_hypervisor'}
  509. ]
  510. })
  511. self._validate_with_expected_errors(
  512. ['storage:wrong_storage'],
  513. "Component 'storage:wrong_storage' has mixed format of requires."
  514. )
  515. def test_requires_any_of_predicate(self):
  516. self.components_metadata.append({
  517. 'name': 'additional_service:test_service',
  518. 'requires': [
  519. {'any_of': {
  520. 'items': ['network:core:*']
  521. }},
  522. {'any_of': {
  523. 'items': [
  524. 'storage:test_storage_2', 'hypervisor:test_hypervisor'
  525. ],
  526. }}
  527. ]
  528. })
  529. self._validate_with_expected_errors(
  530. ['additional_service:test_service', 'network:ml2:test_network_3'],
  531. "Requirements was not satisfied for component "
  532. "'additional_service:test_service': any_of(['network:core:*'])"
  533. )
  534. self._validate_with_expected_errors(
  535. ['additional_service:test_service', 'network:core:test_network_2'],
  536. "Requirements was not satisfied for component "
  537. "'additional_service:test_service': "
  538. "any_of(['hypervisor:test_hypervisor', 'storage:test_storage_2'])"
  539. )
  540. ComponentsRestrictions.validate_components(
  541. ['additional_service:test_service', 'network:core:test_network_2',
  542. 'hypervisor:test_hypervisor', 'storage:test_storage_2'],
  543. self.components_metadata,
  544. self.required_components_types
  545. )
  546. def test_requires_one_of_predicate(self):
  547. self.components_metadata.append({
  548. 'name': 'additional_service:test_service',
  549. 'requires': [
  550. {'one_of': {
  551. 'items': ['network:core:*']
  552. }},
  553. {'one_of': {
  554. 'items': [
  555. 'storage:test_storage_2', 'hypervisor:test_hypervisor'
  556. ]
  557. }}
  558. ]
  559. })
  560. selected_components_list = ['additional_service:test_service',
  561. 'network:core:test_network_1',
  562. 'network:core:test_network_2',
  563. 'storage:test_storage_2']
  564. self._validate_with_expected_errors(
  565. selected_components_list,
  566. "Requirements was not satisfied for component "
  567. "'additional_service:test_service': one_of(['network:core:*'])"
  568. )
  569. self._validate_with_expected_errors(
  570. ['additional_service:test_service', 'network:core:test_network_1'],
  571. "Requirements was not satisfied for component "
  572. "'additional_service:test_service': "
  573. "one_of(['hypervisor:test_hypervisor', 'storage:test_storage_2'])"
  574. )
  575. ComponentsRestrictions.validate_components(
  576. ['additional_service:test_service', 'network:core:test_network_2',
  577. 'hypervisor:test_hypervisor', 'storage:test_storage'],
  578. self.components_metadata,
  579. self.required_components_types
  580. )
  581. def test_requires_none_of_predicate(self):
  582. self.components_metadata.append({
  583. 'name': 'additional_service:test_service',
  584. 'requires': [{
  585. 'none_of': {
  586. 'items': ['network:core:*', 'storage:test_storage']
  587. }
  588. }]
  589. })
  590. selected_components_list = ['additional_service:test_service',
  591. 'network:core:test_network_1']
  592. self._validate_with_expected_errors(
  593. selected_components_list,
  594. "Requirements was not satisfied for component "
  595. "'additional_service:test_service': "
  596. "none_of(['network:core:*', 'storage:test_storage'])"
  597. )
  598. ComponentsRestrictions.validate_components(
  599. ['additional_service:test_service', 'network:ml2:test_network_3',
  600. 'storage:test_storage_2', 'hypervisor:test_hypervisor'],
  601. self.components_metadata,
  602. self.required_components_types
  603. )
  604. def test_requires_all_of_predicate(self):
  605. self.components_metadata.append({
  606. 'name': 'additional_service:test_service',
  607. 'requires': [{
  608. 'all_of': {
  609. 'items': [
  610. 'network:core:test_network_2',
  611. 'storage:*',
  612. 'hypervisor:test_hypervisor'
  613. ]
  614. }
  615. }]
  616. })
  617. selected_components_list = ['additional_service:test_service',
  618. 'network:core:test_network_2',
  619. 'storage:test_storage_2',
  620. 'hypervisor:test_hypervisor']
  621. self._validate_with_expected_errors(
  622. selected_components_list,
  623. "Requirements was not satisfied for component "
  624. "'additional_service:test_service': all_of(["
  625. "'hypervisor:test_hypervisor', 'network:core:test_network_2', "
  626. "'storage:*'])"
  627. )
  628. selected_components_list.append('storage:test_storage')
  629. ComponentsRestrictions.validate_components(
  630. selected_components_list, self.components_metadata,
  631. self.required_components_types)
  632. def _validate_with_expected_errors(self, components_list, error_msg):
  633. with self.assertRaises(errors.InvalidData) as exc_cm:
  634. ComponentsRestrictions.validate_components(
  635. components_list, self.components_metadata,
  636. self.required_components_types)
  637. self.assertEqual(exc_cm.exception.message, error_msg)