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_attributes.py 35KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2013 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. from mock import patch
  16. from oslo_serialization import jsonutils
  17. import six
  18. from nailgun import consts
  19. from nailgun.db.sqlalchemy.models import Release
  20. from nailgun import objects
  21. from nailgun.settings import settings
  22. from nailgun.test.base import BaseIntegrationTest
  23. from nailgun.utils import reverse
  24. class TestClusterAttributes(BaseIntegrationTest):
  25. ATTRIBUTES_WITH_RESTRICTIONS = {
  26. 'editable': {
  27. 'test': {
  28. 'comp1': {
  29. 'description': 'desc',
  30. 'label': 'Comp 1',
  31. 'type': 'checkbox',
  32. 'value': False,
  33. 'weight': 10,
  34. },
  35. 'comp2': {
  36. 'description': 'desc',
  37. 'label': 'Comp 2',
  38. 'type': 'checkbox',
  39. 'value': False,
  40. 'weight': 20,
  41. 'restrictions': ["settings:test.comp1.value == true"],
  42. },
  43. 'comp3': {
  44. 'description': 'desc',
  45. 'label': 'Comp 3',
  46. 'type': 'text',
  47. 'value': '',
  48. 'weight': 30,
  49. 'restrictions': [
  50. {
  51. 'condition': "settings:test.comp1.value == true",
  52. 'action': "disable"
  53. }
  54. ],
  55. 'regex': {
  56. 'source': '^[a-zA-Z\d][a-zA-Z\d_\-.]+(:[0-9]+)?$',
  57. 'error': "Wrong Comp 3 value"
  58. }
  59. }
  60. }
  61. }
  62. }
  63. def test_attributes_creation(self):
  64. cluster = self.env.create_cluster(api=True)
  65. resp = self.app.get(
  66. reverse(
  67. 'ClusterAttributesHandler',
  68. kwargs={'cluster_id': cluster['id']}),
  69. headers=self.default_headers
  70. )
  71. release = objects.Release.get_by_uid(cluster['release_id'])
  72. self.assertEqual(200, resp.status_code)
  73. self._compare_editable(
  74. release.attributes_metadata['editable'],
  75. resp.json_body['editable'],
  76. cluster
  77. )
  78. attrs = objects.Cluster.get_attributes(cluster)
  79. self._compare_generated(
  80. release.attributes_metadata['generated'],
  81. attrs['generated'],
  82. cluster
  83. )
  84. def test_500_if_no_attributes(self):
  85. cluster = self.env.create_cluster(api=False)
  86. self.db.delete(cluster.attributes)
  87. self.db.commit()
  88. resp = self.app.put(
  89. reverse(
  90. 'ClusterAttributesHandler',
  91. kwargs={'cluster_id': cluster.id}),
  92. params=jsonutils.dumps({
  93. 'editable': {
  94. "foo": "bar"
  95. },
  96. }),
  97. headers=self.default_headers,
  98. expect_errors=True
  99. )
  100. self.assertEqual(500, resp.status_code)
  101. def test_attributes_update_put(self):
  102. cluster = self.env.create_cluster(api=True)
  103. cluster_id = cluster['id']
  104. resp = self.app.get(
  105. reverse(
  106. 'ClusterAttributesHandler',
  107. kwargs={'cluster_id': cluster_id}),
  108. headers=self.default_headers
  109. )
  110. self.assertEqual(200, resp.status_code)
  111. resp = self.app.put(
  112. reverse(
  113. 'ClusterAttributesHandler',
  114. kwargs={'cluster_id': cluster_id}),
  115. params=jsonutils.dumps({
  116. 'editable': {
  117. 'foo': {'bar': None}
  118. },
  119. }),
  120. headers=self.default_headers
  121. )
  122. self.assertEqual(200, resp.status_code)
  123. attrs = objects.Cluster.get_editable_attributes(cluster)
  124. self.assertEqual({'bar': None}, attrs["foo"])
  125. attrs.pop('foo')
  126. # 400 on generated update
  127. resp = self.app.put(
  128. reverse(
  129. 'ClusterAttributesHandler',
  130. kwargs={'cluster_id': cluster_id}),
  131. params=jsonutils.dumps({
  132. 'generated': {
  133. "foo": "bar"
  134. },
  135. }),
  136. headers=self.default_headers,
  137. expect_errors=True
  138. )
  139. self.assertEqual(400, resp.status_code)
  140. # 400 if editable is not dict
  141. resp = self.app.put(
  142. reverse(
  143. 'ClusterAttributesHandler',
  144. kwargs={'cluster_id': cluster_id}),
  145. params=jsonutils.dumps({
  146. 'editable': ["foo", "bar"],
  147. }),
  148. headers=self.default_headers,
  149. expect_errors=True
  150. )
  151. self.assertEqual(400, resp.status_code)
  152. def test_attributes_update_patch(self):
  153. cluster = self.env.create_cluster(api=True)
  154. resp = self.app.get(
  155. reverse(
  156. 'ClusterAttributesHandler',
  157. kwargs={'cluster_id': cluster['id']}),
  158. headers=self.default_headers
  159. )
  160. self.assertEqual(200, resp.status_code)
  161. resp = self.app.patch(
  162. reverse(
  163. 'ClusterAttributesHandler',
  164. kwargs={'cluster_id': cluster['id']}),
  165. params=jsonutils.dumps({
  166. 'editable': {
  167. 'foo': {'bar': None}
  168. },
  169. }),
  170. headers=self.default_headers
  171. )
  172. self.assertEqual(200, resp.status_code)
  173. attrs = objects.Cluster.get_editable_attributes(cluster)
  174. self.assertEqual({'bar': None}, attrs["foo"])
  175. attrs.pop('foo')
  176. self.assertNotEqual(attrs, {})
  177. def test_failing_attributes_put(self):
  178. cluster_id = self.env.create_cluster(api=True)['id']
  179. resp = self.app.get(
  180. reverse(
  181. 'ClusterAttributesHandler',
  182. kwargs={'cluster_id': cluster_id}),
  183. headers=self.default_headers
  184. )
  185. self.assertEqual(200, resp.status_code)
  186. resp = self.app.patch(
  187. reverse(
  188. 'ClusterAttributesHandler',
  189. kwargs={'cluster_id': cluster_id}),
  190. params=jsonutils.dumps({
  191. 'editable': {
  192. 'storage': {
  193. 'osd_pool_size': {
  194. 'description': 'desc',
  195. 'label': 'OSD Pool Size',
  196. 'type': 'text',
  197. 'value': True,
  198. 'weight': 80,
  199. },
  200. },
  201. },
  202. }),
  203. headers=self.default_headers,
  204. expect_errors=True
  205. )
  206. self.assertEqual(400, resp.status_code)
  207. def test_failing_attributes_with_restrictions(self):
  208. cluster = self.env.create_cluster(api=False)
  209. objects.Cluster.patch_attributes(
  210. cluster, self.ATTRIBUTES_WITH_RESTRICTIONS)
  211. resp = self.app.patch(
  212. reverse(
  213. 'ClusterAttributesHandler',
  214. kwargs={'cluster_id': cluster.id}),
  215. params=jsonutils.dumps({
  216. 'editable': {
  217. 'test': {
  218. 'comp1': {
  219. 'value': True
  220. },
  221. 'comp2': {
  222. 'value': True
  223. }
  224. }
  225. }
  226. }),
  227. headers=self.default_headers,
  228. expect_errors=True
  229. )
  230. self.assertEqual(400, resp.status_code)
  231. extended_restr = {
  232. 'condition': "settings:test.comp1.value == true",
  233. 'action': 'disable',
  234. }
  235. self.assertIn(
  236. "Validation failed for attribute 'Comp 2': restriction with action"
  237. "='{}' and condition='{}' failed due to attribute value='True'"
  238. .format(extended_restr['action'], extended_restr['condition']),
  239. resp.json_body['message'])
  240. def test_disabled_attributes_with_restrictions_not_fail(self):
  241. cluster = self.env.create_cluster(api=False)
  242. objects.Cluster.patch_attributes(
  243. cluster, self.ATTRIBUTES_WITH_RESTRICTIONS)
  244. resp = self.app.patch(
  245. reverse(
  246. 'ClusterAttributesHandler',
  247. kwargs={'cluster_id': cluster.id}),
  248. params=jsonutils.dumps({
  249. 'editable': {
  250. 'test': {
  251. 'comp1': {
  252. 'value': True
  253. },
  254. 'comp3': {
  255. 'value': ''
  256. }
  257. }
  258. }
  259. }),
  260. headers=self.default_headers
  261. )
  262. self.assertEqual(200, resp.status_code)
  263. def test_enabled_attributes_raise_regex_exception(self):
  264. cluster = self.env.create_cluster(api=False)
  265. objects.Cluster.patch_attributes(
  266. cluster, self.ATTRIBUTES_WITH_RESTRICTIONS)
  267. resp = self.app.patch(
  268. reverse(
  269. 'ClusterAttributesHandler',
  270. kwargs={'cluster_id': cluster.id}),
  271. params=jsonutils.dumps({
  272. 'editable': {
  273. 'test': {
  274. 'comp1': {
  275. 'value': False
  276. },
  277. 'comp3': {
  278. 'value': ''
  279. }
  280. }
  281. }
  282. }),
  283. headers=self.default_headers,
  284. expect_errors=True
  285. )
  286. self.assertEqual(400, resp.status_code)
  287. self.assertEqual(
  288. "Some restrictions didn't pass verification: "
  289. "['Wrong Comp 3 value']",
  290. resp.json_body['message']
  291. )
  292. def test_get_default_attributes(self):
  293. cluster = self.env.create_cluster(api=True)
  294. release = self.db.query(Release).get(
  295. cluster['release_id']
  296. )
  297. resp = self.app.put(
  298. reverse(
  299. 'ClusterAttributesDefaultsHandler',
  300. kwargs={'cluster_id': cluster['id']}),
  301. headers=self.default_headers
  302. )
  303. self.assertEqual(200, resp.status_code)
  304. self._compare_editable(
  305. release.attributes_metadata['editable'],
  306. resp.json_body['editable'],
  307. cluster
  308. )
  309. def test_get_last_deployed_attributes(self):
  310. cluster = self.env.create_cluster(api=True)
  311. cluster_attrs = objects.Cluster.get_editable_attributes(
  312. self.env.clusters[-1]
  313. )
  314. transaction = objects.Transaction.create({
  315. 'cluster_id': cluster.id,
  316. 'status': consts.TASK_STATUSES.ready,
  317. 'name': consts.TASK_NAMES.deployment
  318. })
  319. objects.Transaction.attach_cluster_settings(
  320. transaction, {'editable': cluster_attrs}
  321. )
  322. self.assertIsNotNone(
  323. objects.TransactionCollection.get_last_succeed_run(cluster)
  324. )
  325. resp = self.app.get(
  326. reverse(
  327. 'ClusterAttributesDeployedHandler',
  328. kwargs={'cluster_id': cluster.id}),
  329. headers=self.default_headers
  330. )
  331. self.assertEqual(200, resp.status_code)
  332. self.datadiff(cluster_attrs, resp.json_body['editable'])
  333. def test_get_deployed_attributes_fails_if_no_attrs(self):
  334. cluster = self.env.create_cluster(api=True)
  335. resp = self.app.get(
  336. reverse(
  337. 'ClusterAttributesDeployedHandler',
  338. kwargs={'cluster_id': cluster['id']}),
  339. headers=self.default_headers,
  340. expect_errors=True,
  341. )
  342. self.assertEqual(404, resp.status_code)
  343. def test_attributes_set_defaults(self):
  344. cluster = self.env.create_cluster(api=True)
  345. # Change editable attributes.
  346. resp = self.app.put(
  347. reverse(
  348. 'ClusterAttributesHandler',
  349. kwargs={'cluster_id': cluster['id']}),
  350. params=jsonutils.dumps({
  351. 'editable': {
  352. 'foo': {'bar': None}
  353. },
  354. }),
  355. headers=self.default_headers,
  356. expect_errors=True
  357. )
  358. self.assertEqual(200, resp.status_code, resp.body)
  359. attrs = objects.Cluster.get_editable_attributes(cluster)
  360. self.assertEqual({'bar': None}, attrs['foo'])
  361. # Set attributes to defaults.
  362. resp = self.app.put(
  363. reverse(
  364. 'ClusterAttributesDefaultsHandler',
  365. kwargs={'cluster_id': cluster['id']}),
  366. headers=self.default_headers
  367. )
  368. self.assertEqual(200, resp.status_code)
  369. release = self.db.query(Release).get(
  370. cluster['release_id']
  371. )
  372. self._compare_editable(
  373. release.attributes_metadata['editable'],
  374. resp.json_body['editable'],
  375. cluster
  376. )
  377. def test_attributes_merged_values(self):
  378. cluster = self.env.create_cluster(api=True)
  379. cluster_db = objects.Cluster.get_by_uid(cluster['id'])
  380. orig_attrs = objects.Attributes.merged_attrs(cluster_db.attributes)
  381. attrs = objects.Attributes.merged_attrs_values(cluster_db.attributes)
  382. for group, group_attrs in six.iteritems(orig_attrs):
  383. for attr, orig_value in six.iteritems(group_attrs):
  384. if group == 'common':
  385. value = attrs[attr]
  386. elif group == 'additional_components':
  387. for c, val in six.iteritems(group_attrs):
  388. self.assertIn(c, attrs)
  389. if 'value' in val:
  390. self.assertEqual(val["value"],
  391. attrs[c]["enabled"])
  392. continue
  393. else:
  394. value = attrs[group][attr]
  395. if isinstance(orig_value, dict) and 'value' in orig_value:
  396. self.assertEqual(orig_value['value'], value)
  397. else:
  398. self.assertEqual(orig_value, value)
  399. def _compare_generated(self, d1, d2, cluster):
  400. if isinstance(d1, dict) and isinstance(d2, dict):
  401. for s_field, s_value in six.iteritems(d1):
  402. if s_field not in d2:
  403. raise KeyError()
  404. self._compare_generated(s_value, d2[s_field], cluster)
  405. elif isinstance(d1, six.string_types):
  406. if d1 in [u"", ""]:
  407. self.assertEqual(len(d2), 8)
  408. else:
  409. self.assertEqual(
  410. d1.format(settings=settings, cluster=cluster),
  411. d2.format(settings=settings, cluster=cluster))
  412. def _compare_editable(self, r_attrs, c_attrs, cluster=None):
  413. """Compare editable attributes omitting the check of generated values
  414. :param r_attrs: attributes from release
  415. :param c_attrs: attributes from cluster
  416. """
  417. # TODO(ikalnitsky):
  418. # This code should be rewritten completely. We have to use one
  419. # function for comparing both generated and editable attributes.
  420. # Moreover, I'm not sure we have keep that code, since it duplicated
  421. # traverse function in many cases.
  422. if isinstance(r_attrs, dict) and isinstance(c_attrs, dict):
  423. for s_field, s_value in six.iteritems(r_attrs):
  424. if s_field not in c_attrs:
  425. self.fail("'{0}' not found in '{1}'".format(s_field,
  426. c_attrs))
  427. if s_field != 'regex':
  428. self._compare_editable(s_value, c_attrs[s_field], cluster)
  429. else:
  430. self.assertEqual(s_value, c_attrs[s_field])
  431. elif isinstance(r_attrs, (list, tuple)) and \
  432. isinstance(c_attrs, (list, tuple)):
  433. if len(r_attrs) != len(c_attrs):
  434. self.fail('Different number of elements: {0} vs {1}'.format(
  435. c_attrs, r_attrs))
  436. for index in range(0, len(r_attrs)):
  437. self._compare_editable(r_attrs[index], c_attrs[index], cluster)
  438. elif isinstance(c_attrs, six.string_types + (list, tuple)) and \
  439. isinstance(r_attrs, dict):
  440. self.assertIn("generator", r_attrs)
  441. elif isinstance(c_attrs, six.string_types) and \
  442. isinstance(r_attrs, six.string_types):
  443. self.assertEqual(
  444. c_attrs, r_attrs.format(settings=settings, cluster=cluster))
  445. else:
  446. self.assertEqual(c_attrs, r_attrs)
  447. def test_compare_editable(self):
  448. r_attrs = {
  449. 'section1': {
  450. 'value': 'string1'
  451. },
  452. 'section2': {
  453. 'subsection1': {
  454. 'value': 'string2'
  455. }
  456. }
  457. }
  458. c_attrs = {
  459. 'section1': {
  460. 'value': 'string1'
  461. },
  462. 'section2': {
  463. 'subsection1': {
  464. 'value': 'string2'
  465. }
  466. }
  467. }
  468. self._compare_editable(r_attrs, c_attrs)
  469. r_attrs['section1']['value'] = {
  470. 'generator': 'generator1'
  471. }
  472. self._compare_editable(r_attrs, c_attrs)
  473. r_attrs['section2']['subsection1']['value'] = {
  474. 'generator': 'generator2'
  475. }
  476. self._compare_editable(r_attrs, c_attrs)
  477. r_attrs['section1']['value'] = 'zzzzzzz'
  478. self.assertRaises(
  479. AssertionError, self._compare_editable, r_attrs, c_attrs)
  480. def test_editable_attributes_generators(self):
  481. cluster = self.env.create_cluster(api=True)
  482. editable = objects.Cluster.get_editable_attributes(cluster)
  483. self.assertEqual(
  484. editable["external_dns"]["dns_list"]["value"],
  485. settings.DNS_UPSTREAM
  486. )
  487. self.assertEqual(
  488. editable["external_ntp"]["ntp_list"]["value"],
  489. settings.NTP_UPSTREAM
  490. )
  491. def test_workloads_collector_attributes(self):
  492. cluster = self.env.create_cluster(api=True)
  493. editable = objects.Cluster.get_editable_attributes(cluster)
  494. self.assertEqual(
  495. editable["workloads_collector"]["enabled"]["value"],
  496. True
  497. )
  498. self.assertEqual(
  499. editable["workloads_collector"]["user"]["value"],
  500. "fuel_stats_user"
  501. )
  502. self.assertEqual(
  503. editable["workloads_collector"]["tenant"]["value"],
  504. "services"
  505. )
  506. self.assertEqual(
  507. len(editable["workloads_collector"]["password"]["value"]),
  508. 24
  509. )
  510. self.assertEqual(
  511. set(editable["workloads_collector"]["metadata"].keys()),
  512. set(["label", "weight", "restrictions", "group"])
  513. )
  514. class TestAlwaysEditable(BaseIntegrationTest):
  515. _reposetup = {
  516. 'repo_setup': {
  517. 'metadata': {
  518. 'label': 'Repositories',
  519. 'weight': 50,
  520. },
  521. 'repos': {
  522. 'type': 'custom_repo_configuration',
  523. 'extra_priority': 15,
  524. 'value': [
  525. {
  526. 'type': 'rpm',
  527. 'name': 'mos',
  528. 'uri': 'http://127.0.0.1:8080/myrepo'
  529. }
  530. ]
  531. }
  532. }}
  533. def setUp(self):
  534. super(TestAlwaysEditable, self).setUp()
  535. self.cluster = self.env.create(
  536. release_kwargs={
  537. 'version': 'liberty-8.0',
  538. 'operating_system': consts.RELEASE_OS.centos})
  539. def _put(self, data, expect_code=200):
  540. resp = self.app.put(
  541. reverse(
  542. 'ClusterAttributesHandler',
  543. kwargs={'cluster_id': self.cluster['id']}),
  544. params=jsonutils.dumps(data),
  545. expect_errors=True,
  546. headers=self.default_headers)
  547. self.assertEqual(expect_code, resp.status_code)
  548. return resp.json_body
  549. def test_can_change_repos_on_operational_cluster(self):
  550. self.cluster.status = consts.CLUSTER_STATUSES.operational
  551. self.db.flush()
  552. self.assertFalse(self.cluster.is_locked)
  553. data = {'editable': {}}
  554. data['editable'].update(self._reposetup)
  555. self._put(data, expect_code=200)
  556. attrs = self.cluster.attributes.editable
  557. self.assertEqual(attrs['repo_setup']['repos']['value'], [{
  558. 'type': 'rpm',
  559. 'name': 'mos',
  560. 'uri': 'http://127.0.0.1:8080/myrepo',
  561. }])
  562. class TestVmwareAttributes(BaseIntegrationTest):
  563. def setUp(self):
  564. super(TestVmwareAttributes, self).setUp()
  565. self.cluster = self.env.create_cluster(api=True)
  566. def test_vmware_attributes_creation(self):
  567. self._set_use_vcenter(self.cluster)
  568. resp = self.app.get(
  569. reverse(
  570. 'VmwareAttributesHandler',
  571. kwargs={'cluster_id': self.cluster['id']}),
  572. headers=self.default_headers
  573. )
  574. release = objects.Release.get_by_uid(self.cluster['release_id'])
  575. self.assertEqual(200, resp.status_code)
  576. attrs = objects.Cluster.get_vmware_attributes(self.cluster)
  577. # TODO(apopovych): use dictdiffer 0.3.0 to compare atttributes
  578. # one-by-one
  579. self.assertEqual(
  580. release.vmware_attributes_metadata['editable'],
  581. attrs.editable
  582. )
  583. def test_vmware_attributes_update(self):
  584. self._set_use_vcenter(self.cluster)
  585. resp = self.app.put(
  586. reverse(
  587. 'VmwareAttributesHandler',
  588. kwargs={'cluster_id': self.cluster['id']}),
  589. params=jsonutils.dumps({
  590. "editable": {
  591. "value": {"foo": "bar"}
  592. }
  593. }),
  594. headers=self.default_headers
  595. )
  596. self.assertEqual(200, resp.status_code)
  597. attrs = objects.Cluster.get_vmware_attributes(self.cluster)
  598. self.assertEqual('bar', attrs.editable.get('value', {}).get('foo'))
  599. attrs.editable.get('value', {}).pop('foo')
  600. self.assertEqual(attrs.editable.get('value'), {})
  601. def test_vmware_attributes_update_with_invalid_json_format(self):
  602. self._set_use_vcenter(self.cluster)
  603. resp = self.app.put(
  604. reverse(
  605. 'VmwareAttributesHandler',
  606. kwargs={'cluster_id': self.cluster['id']}),
  607. params=jsonutils.dumps({
  608. "value": {"foo": "bar"}
  609. }),
  610. headers=self.default_headers,
  611. expect_errors=True
  612. )
  613. self.assertEqual(400, resp.status_code)
  614. self.assertEqual(
  615. "'editable' is a required property", resp.json_body["message"])
  616. self._set_use_vcenter(self.cluster)
  617. resp = self.app.put(
  618. reverse(
  619. 'VmwareAttributesHandler',
  620. kwargs={'cluster_id': self.cluster['id']}),
  621. params=jsonutils.dumps({
  622. "editable": {
  623. "metadata": {},
  624. "value": {"foo": "bar"}
  625. }
  626. }),
  627. headers=self.default_headers,
  628. expect_errors=True
  629. )
  630. self.assertEqual(400, resp.status_code)
  631. self.assertEqual(
  632. "Metadata shouldn't change", resp.json_body["message"])
  633. def test_404_if_no_attributes(self):
  634. cluster = self.env.create_cluster(api=False)
  635. self._set_use_vcenter(cluster)
  636. self.db.delete(cluster.vmware_attributes)
  637. self.db.commit()
  638. resp = self.app.put(
  639. reverse(
  640. 'VmwareAttributesHandler',
  641. kwargs={'cluster_id': cluster.id}),
  642. params=jsonutils.dumps({
  643. "editable": {
  644. "value": {"foo": "bar"}
  645. }
  646. }),
  647. headers=self.default_headers,
  648. expect_errors=True
  649. )
  650. self.assertEqual(404, resp.status_code)
  651. def test_not_acceptable_if_cluster_has_not_support_vmware(self):
  652. resp = self.app.get(
  653. reverse(
  654. 'VmwareAttributesHandler',
  655. kwargs={'cluster_id': self.cluster['id']}),
  656. headers=self.default_headers,
  657. expect_errors=True
  658. )
  659. self.assertEqual(400, resp.status_code)
  660. self.assertEqual(
  661. "Cluster doesn't support vmware configuration",
  662. resp.json_body["message"]
  663. )
  664. resp = self.app.put(
  665. reverse(
  666. 'VmwareAttributesHandler',
  667. kwargs={'cluster_id': self.cluster['id']}),
  668. params=jsonutils.dumps({
  669. "editable": {
  670. "value": {"foo": "bar"}
  671. }
  672. }),
  673. headers=self.default_headers,
  674. expect_errors=True
  675. )
  676. self.assertEqual(400, resp.status_code)
  677. self.assertEqual(
  678. "Cluster doesn't support vmware configuration",
  679. resp.json_body["message"]
  680. )
  681. @patch('nailgun.db.sqlalchemy.models.Cluster.is_locked', return_value=True)
  682. def test_vmware_attributes_update_for_locked_cluster_403(self, locked):
  683. self._set_use_vcenter(self.cluster)
  684. resp = self.app.put(
  685. reverse(
  686. 'VmwareAttributesHandler',
  687. kwargs={'cluster_id': self.cluster.id}),
  688. params=jsonutils.dumps({
  689. "editable": {
  690. "value": {"foo": "bar"}
  691. }
  692. }),
  693. headers=self.default_headers,
  694. expect_errors=True
  695. )
  696. self.assertEqual(403, resp.status_code)
  697. self.assertEqual("Environment attributes can't be changed after or "
  698. "during deployment.", resp.json_body["message"])
  699. @patch('objects.Cluster.has_compute_vmware_changes', return_value=True)
  700. @patch('nailgun.db.sqlalchemy.models.Cluster.is_locked', return_value=True)
  701. def test_vmware_attributes_update_for_locked_cluster_200(
  702. self, is_locked_mock, has_compute_mock):
  703. self._set_use_vcenter(self.cluster)
  704. params = {
  705. "editable": {
  706. "value": {"foo": "bar"}
  707. }}
  708. with patch('nailgun.api.v1.handlers.cluster.VmwareAttributesHandler.'
  709. 'checked_data', return_value=params):
  710. resp = self.app.put(
  711. reverse(
  712. 'VmwareAttributesHandler',
  713. kwargs={'cluster_id': self.cluster.id}),
  714. params=jsonutils.dumps(params),
  715. headers=self.default_headers
  716. )
  717. self.assertEqual(200, resp.status_code)
  718. attrs = objects.Cluster.get_vmware_attributes(self.cluster)
  719. self.assertEqual('bar', attrs.editable.get('value', {}).get('foo'))
  720. attrs.editable.get('value', {}).pop('foo')
  721. self.assertEqual(attrs.editable.get('value'), {})
  722. def _set_use_vcenter(self, cluster):
  723. cluster_attrs = objects.Cluster.get_editable_attributes(cluster)
  724. cluster_attrs['common']['use_vcenter']['value'] = True
  725. objects.Cluster.update_attributes(
  726. cluster, {'editable': cluster_attrs})
  727. class TestVmwareAttributesDefaults(BaseIntegrationTest):
  728. def test_get_default_vmware_attributes(self):
  729. cluster = self.env.create_cluster(api=True)
  730. cluster_attrs = objects.Cluster.get_editable_attributes(cluster)
  731. cluster_attrs['common']['use_vcenter']['value'] = True
  732. objects.Cluster.update_attributes(
  733. cluster, {'editable': cluster_attrs})
  734. resp = self.app.get(
  735. reverse(
  736. 'VmwareAttributesDefaultsHandler',
  737. kwargs={'cluster_id': cluster['id']}),
  738. headers=self.default_headers
  739. )
  740. release = objects.Release.get_by_uid(cluster['release_id'])
  741. self.assertEqual(200, resp.status_code)
  742. self.assertEqual(
  743. release.vmware_attributes_metadata,
  744. jsonutils.loads(resp.testbody)
  745. )
  746. def test_not_acceptable_if_cluster_has_not_support_vmware(self):
  747. cluster = self.env.create_cluster(api=True)
  748. resp = self.app.get(
  749. reverse(
  750. 'VmwareAttributesDefaultsHandler',
  751. kwargs={'cluster_id': cluster['id']}),
  752. headers=self.default_headers,
  753. expect_errors=True
  754. )
  755. self.assertEqual(400, resp.status_code)
  756. self.assertEqual(
  757. "Cluster doesn't support vmware configuration",
  758. resp.json_body["message"]
  759. )
  760. class TestAttributesWithPlugins(BaseIntegrationTest):
  761. def setUp(self):
  762. super(TestAttributesWithPlugins, self).setUp()
  763. self.cluster = self.env.create(
  764. release_kwargs={
  765. 'operating_system': consts.RELEASE_OS.ubuntu,
  766. 'version': '2015.1.0-7.0',
  767. },
  768. cluster_kwargs={
  769. 'mode': consts.CLUSTER_MODES.ha_compact,
  770. 'net_provider': consts.CLUSTER_NET_PROVIDERS.neutron,
  771. 'net_segment_type': consts.NEUTRON_SEGMENT_TYPES.vlan,
  772. },
  773. nodes_kwargs=[
  774. {'roles': ['controller'], 'pending_addition': True},
  775. {'roles': ['compute'], 'pending_addition': True},
  776. ]
  777. )
  778. self.plugin_data = {
  779. 'releases': [
  780. {
  781. 'repository_path': 'repositories/ubuntu',
  782. 'version': self.cluster.release.version,
  783. 'os': self.cluster.release.operating_system.lower(),
  784. 'mode': [self.cluster.mode],
  785. }
  786. ]
  787. }
  788. def test_cluster_contains_plugins_attributes(self):
  789. self.env.create_plugin(cluster=self.cluster, **self.plugin_data)
  790. resp = self.app.get(
  791. reverse(
  792. 'ClusterAttributesHandler',
  793. kwargs={'cluster_id': self.cluster['id']}),
  794. headers=self.default_headers
  795. )
  796. self.assertEqual(200, resp.status_code)
  797. self.assertIn('testing_plugin', resp.json_body['editable'])
  798. def test_change_plugins_attributes(self):
  799. plugin = self.env.create_plugin(cluster=self.cluster,
  800. **self.plugin_data)
  801. def _modify_plugin(enabled=True):
  802. return self.app.put(
  803. reverse(
  804. 'ClusterAttributesHandler',
  805. kwargs={'cluster_id': self.cluster['id']}),
  806. params=jsonutils.dumps({
  807. 'editable': {
  808. plugin.name: {
  809. 'metadata': {
  810. 'class': 'plugin',
  811. 'label': 'Test plugin',
  812. 'toggleable': True,
  813. 'weight': 70,
  814. 'enabled': enabled,
  815. 'chosen_id': plugin.id,
  816. 'versions': [{
  817. 'metadata': {
  818. 'plugin_id': plugin.id,
  819. 'plugin_version': plugin.version
  820. },
  821. 'attr': {
  822. 'type': 'text',
  823. 'description': 'description',
  824. 'label': 'label',
  825. 'value': '1',
  826. 'weight': 25,
  827. 'restrictions': [{
  828. 'condition': 'true',
  829. 'action': 'hide'}]
  830. }
  831. }]
  832. },
  833. }
  834. }
  835. }),
  836. headers=self.default_headers
  837. )
  838. resp = _modify_plugin(enabled=True)
  839. self.assertEqual(200, resp.status_code)
  840. editable = objects.Cluster.get_editable_attributes(self.cluster)
  841. self.assertIn(plugin.name, editable)
  842. self.assertTrue(editable[plugin.name]['metadata']['enabled'])
  843. self.assertEqual('1', editable[plugin.name]['attr']['value'])
  844. resp = _modify_plugin(enabled=False)
  845. self.assertEqual(200, resp.status_code)
  846. editable = objects.Cluster.get_editable_attributes(self.cluster)
  847. self.assertNotIn(plugin.name, editable)
  848. def _modify_plugin(self, plugin, enabled):
  849. return self.app.put(
  850. reverse(
  851. 'ClusterAttributesHandler',
  852. kwargs={'cluster_id': self.cluster.id}
  853. ),
  854. params=jsonutils.dumps({
  855. 'editable': {
  856. plugin.name: {
  857. 'metadata': {
  858. 'class': 'plugin',
  859. 'enabled': enabled,
  860. 'chosen_id': plugin.id,
  861. 'versions': [{
  862. 'metadata': {
  863. 'plugin_id': plugin.id,
  864. 'plugin_version': plugin.version
  865. }
  866. }]
  867. }
  868. }
  869. }
  870. }),
  871. headers=self.default_headers,
  872. expect_errors=True
  873. )
  874. def test_install_plugins_after_deployment(self):
  875. self.cluster.status = consts.CLUSTER_STATUSES.operational
  876. self.assertFalse(self.cluster.is_locked)
  877. runtime_plugin = self.env.create_plugin(
  878. cluster=self.cluster,
  879. is_hotpluggable=True,
  880. version='1.0.1',
  881. enabled=False,
  882. **self.plugin_data
  883. )
  884. resp = self._modify_plugin(runtime_plugin, True)
  885. self.assertEqual(200, resp.status_code, resp.body)
  886. editable = objects.Cluster.get_editable_attributes(self.cluster)
  887. self.assertIn(runtime_plugin.name, editable)
  888. self.assertTrue(editable[runtime_plugin.name]['metadata']['enabled'])
  889. def test_enable_plugin_is_idempotent(self):
  890. plugin = self.env.create_plugin(
  891. cluster=self.cluster,
  892. version='1.0.1',
  893. is_hotpluggable=True,
  894. enabled=True,
  895. **self.plugin_data
  896. )
  897. self.cluster.status = consts.CLUSTER_STATUSES.operational
  898. self.assertFalse(self.cluster.is_locked)
  899. resp = self._modify_plugin(plugin, True)
  900. self.assertEqual(200, resp.status_code, resp.body)