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_node_collection_handlers.py 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  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. import copy
  16. from oslo_serialization import jsonutils
  17. from nailgun.db.sqlalchemy.models import Node
  18. from nailgun.db.sqlalchemy.models import Notification
  19. from nailgun.extensions.network_manager.models.network import NodeNICInterface
  20. from nailgun.test.base import BaseIntegrationTest
  21. from nailgun.utils import reverse
  22. class TestHandlers(BaseIntegrationTest):
  23. def test_node_list_empty(self):
  24. resp = self.app.get(
  25. reverse('NodeCollectionHandler'),
  26. headers=self.default_headers
  27. )
  28. self.assertEqual(200, resp.status_code)
  29. self.assertEqual([], resp.json_body)
  30. def test_notification_node_id(self):
  31. node = self.env.create_node(
  32. api=True,
  33. meta=self.env.default_metadata()
  34. )
  35. notif = self.db.query(Notification).first()
  36. self.assertEqual(node['id'], notif.node_id)
  37. resp = self.app.get(
  38. reverse('NotificationCollectionHandler'),
  39. headers=self.default_headers
  40. )
  41. notif_api = resp.json_body[0]
  42. self.assertEqual(node['id'], notif_api['node_id'])
  43. def test_node_get_with_cluster(self):
  44. cluster = self.env.create(
  45. cluster_kwargs={"api": True},
  46. nodes_kwargs=[
  47. {"cluster_id": None},
  48. {},
  49. ]
  50. )
  51. resp = self.app.get(
  52. reverse('NodeCollectionHandler'),
  53. params={'cluster_id': cluster.id},
  54. headers=self.default_headers
  55. )
  56. self.assertEqual(200, resp.status_code)
  57. self.assertEqual(1, len(resp.json_body))
  58. self.assertEqual(
  59. self.env.nodes[1].id,
  60. resp.json_body[0]['id']
  61. )
  62. def test_node_get_with_cluster_None(self):
  63. self.env.create(
  64. cluster_kwargs={"api": True},
  65. nodes_kwargs=[
  66. {"cluster_id": None},
  67. {},
  68. ]
  69. )
  70. resp = self.app.get(
  71. reverse('NodeCollectionHandler'),
  72. params={'cluster_id': ''},
  73. headers=self.default_headers
  74. )
  75. self.assertEqual(200, resp.status_code)
  76. self.assertEqual(1, len(resp.json_body))
  77. self.assertEqual(self.env.nodes[0].id, resp.json_body[0]['id'])
  78. def test_node_get_without_cluster_specification(self):
  79. self.env.create(
  80. cluster_kwargs={"api": True},
  81. nodes_kwargs=[
  82. {"cluster_id": None},
  83. {},
  84. ]
  85. )
  86. resp = self.app.get(
  87. reverse('NodeCollectionHandler'),
  88. headers=self.default_headers
  89. )
  90. self.assertEqual(200, resp.status_code)
  91. self.assertEqual(2, len(resp.json_body))
  92. def test_node_get_with_cluster_and_assigned_ip_addrs(self):
  93. self.env.create(
  94. cluster_kwargs={},
  95. nodes_kwargs=[
  96. {"pending_addition": True, "api": True},
  97. {"pending_addition": True, "api": True}
  98. ]
  99. )
  100. self.env.network_manager.assign_ips(
  101. self.env.clusters[-1],
  102. self.env.nodes,
  103. "management"
  104. )
  105. resp = self.app.get(
  106. reverse('NodeCollectionHandler'),
  107. headers=self.default_headers
  108. )
  109. self.assertEqual(200, resp.status_code)
  110. self.assertEqual(2, len(resp.json_body))
  111. def test_node_creation(self):
  112. resp = self.app.post(
  113. reverse('NodeCollectionHandler'),
  114. jsonutils.dumps({'mac': self.env.generate_random_mac(),
  115. 'meta': self.env.default_metadata(),
  116. 'status': 'discover'}),
  117. headers=self.default_headers)
  118. self.assertEqual(resp.status_code, 201)
  119. self.assertEqual('discover', resp.json_body['status'])
  120. def test_node_update(self):
  121. node = self.env.create_node(api=False)
  122. resp = self.app.put(
  123. reverse('NodeCollectionHandler'),
  124. jsonutils.dumps([{'mac': node.mac, 'manufacturer': 'new'}]),
  125. headers=self.default_headers)
  126. self.assertEqual(resp.status_code, 200)
  127. resp = self.app.get(
  128. reverse('NodeCollectionHandler'),
  129. headers=self.default_headers
  130. )
  131. node = self.db.query(Node).get(node.id)
  132. self.assertEqual('new', node.manufacturer)
  133. def test_node_update_empty_mac_or_id(self):
  134. node = self.env.create_node(api=False)
  135. resp = self.app.put(
  136. reverse('NodeCollectionHandler'),
  137. jsonutils.dumps([{'manufacturer': 'man0'}]),
  138. headers=self.default_headers,
  139. expect_errors=True)
  140. self.assertEqual(resp.status_code, 400)
  141. self.assertEqual(
  142. resp.json_body["message"],
  143. "Neither MAC nor ID is specified"
  144. )
  145. resp = self.app.put(
  146. reverse('NodeCollectionHandler'),
  147. jsonutils.dumps([{'id': node.id,
  148. 'mac': None,
  149. 'manufacturer': 'man4'}]),
  150. headers=self.default_headers,
  151. expect_errors=True)
  152. self.assertEqual(resp.status_code, 400)
  153. self.assertIn(
  154. "schema['properties']['mac']",
  155. resp.json_body["message"]
  156. )
  157. self.assertIn(
  158. "None is not of type 'string'",
  159. resp.json_body["message"]
  160. )
  161. resp = self.app.put(
  162. reverse('NodeCollectionHandler'),
  163. jsonutils.dumps([{'mac': node.mac,
  164. 'manufacturer': 'man5'}]),
  165. headers=self.default_headers
  166. )
  167. self.assertEqual(resp.status_code, 200)
  168. resp = self.app.put(
  169. reverse('NodeCollectionHandler'),
  170. jsonutils.dumps([{'id': node.id,
  171. 'manufacturer': 'man6'}]),
  172. headers=self.default_headers
  173. )
  174. self.assertEqual(resp.status_code, 200)
  175. resp = self.app.put(
  176. reverse('NodeCollectionHandler'),
  177. jsonutils.dumps([{'mac': node.mac,
  178. 'manufacturer': 'man7'}]),
  179. headers=self.default_headers)
  180. self.assertEqual(resp.status_code, 200)
  181. resp = self.app.put(
  182. reverse('NodeCollectionHandler'),
  183. jsonutils.dumps([{'id': node.id,
  184. 'mac': node.mac,
  185. 'manufacturer': 'man8'}]),
  186. headers=self.default_headers)
  187. self.assertEqual(resp.status_code, 200)
  188. def node_update_with_invalid_id(self):
  189. node = self.env.create_node(api=False)
  190. resp = self.app.put(
  191. reverse('NodeCollectionHandler'),
  192. jsonutils.dumps([{'id': 'new_id',
  193. 'mac': node.mac}]),
  194. headers=self.default_headers,
  195. expect_errors=True)
  196. self.assertEqual(resp.status_code, 400)
  197. self.assertEqual(
  198. resp.json_body["message"],
  199. "Invalid ID specified"
  200. )
  201. def test_node_update_agent_discover(self):
  202. self.env.create_node(
  203. api=False,
  204. status='provisioning',
  205. meta=self.env.default_metadata()
  206. )
  207. node_db = self.env.nodes[0]
  208. resp = self.app.put(
  209. reverse('NodeAgentHandler'),
  210. jsonutils.dumps(
  211. {'mac': node_db.mac,
  212. 'status': 'discover', 'manufacturer': 'new'}
  213. ),
  214. headers=self.default_headers
  215. )
  216. self.assertEqual(resp.status_code, 200)
  217. resp = self.app.get(
  218. reverse('NodeCollectionHandler'),
  219. headers=self.default_headers
  220. )
  221. node_db = self.db.query(Node).get(node_db.id)
  222. self.assertEqual('new', node_db.manufacturer)
  223. self.assertEqual('provisioning', node_db.status)
  224. def test_stopped_node_network_update_restricted_for_agent(self):
  225. node = self.env.create_node(
  226. api=False,
  227. status='stopped',
  228. meta=self.env.default_metadata()
  229. )
  230. node_db = self.env.nodes[0]
  231. interfaces = node.meta['interfaces']
  232. new_interfaces = copy.deepcopy(interfaces)
  233. new_interfaces[1]['mac'] = '2a:00:0d:0d:00:2a'
  234. resp = self.app.put(
  235. reverse('NodeAgentHandler'),
  236. jsonutils.dumps(
  237. {
  238. 'mac': node_db.mac,
  239. 'meta': {
  240. 'interfaces': new_interfaces
  241. }
  242. }
  243. ),
  244. headers=self.default_headers
  245. )
  246. self.assertEqual(resp.status_code, 200)
  247. node_db = self.db.query(Node).get(node_db.id)
  248. interface_db = self.db.query(NodeNICInterface).filter_by(
  249. node_id=node_db.id,
  250. name=new_interfaces[1]['name']
  251. ).first()
  252. self.assertNotEqual(
  253. interface_db.mac,
  254. '2a:00:0d:0d:00:2a')
  255. def test_stopped_node_network_update_allowed_for_ui(self):
  256. node = self.env.create_node(
  257. api=False,
  258. status='stopped',
  259. meta=self.env.default_metadata()
  260. )
  261. node_db = self.env.nodes[0]
  262. interfaces = node.meta['interfaces']
  263. new_interfaces = copy.deepcopy(interfaces)
  264. new_interfaces[1]['mac'] = '2a:00:0d:0d:00:2a'
  265. resp = self.app.put(
  266. reverse('NodeCollectionHandler'),
  267. jsonutils.dumps([
  268. {
  269. 'mac': node_db.mac,
  270. 'meta': {
  271. 'interfaces': new_interfaces
  272. }
  273. }
  274. ]),
  275. headers=self.default_headers
  276. )
  277. self.assertEqual(resp.status_code, 200)
  278. interface_db = self.db.query(NodeNICInterface).filter_by(
  279. node_id=node_db.id,
  280. name=new_interfaces[1]['name']
  281. ).first()
  282. self.assertEqual(
  283. interface_db.mac,
  284. '2a:00:0d:0d:00:2a')
  285. def test_node_timestamp_updated_only_by_agent(self):
  286. node = self.env.create_node(api=False)
  287. timestamp = node.timestamp
  288. resp = self.app.put(
  289. reverse('NodeCollectionHandler'),
  290. jsonutils.dumps([
  291. {'mac': node.mac, 'status': 'discover',
  292. 'manufacturer': 'old'}
  293. ]),
  294. headers=self.default_headers)
  295. self.assertEqual(resp.status_code, 200)
  296. node = self.db.query(Node).get(node.id)
  297. self.assertEqual(node.timestamp, timestamp)
  298. resp = self.app.put(
  299. reverse('NodeAgentHandler'),
  300. jsonutils.dumps(
  301. {'mac': node.mac, 'status': 'discover',
  302. 'manufacturer': 'new'}
  303. ),
  304. headers=self.default_headers)
  305. self.assertEqual(resp.status_code, 200)
  306. node = self.db.query(Node).get(node.id)
  307. self.assertNotEqual(node.timestamp, timestamp)
  308. self.assertEqual('new', node.manufacturer)
  309. def test_agent_caching(self):
  310. node = self.env.create_node(api=False)
  311. resp = self.app.put(
  312. reverse('NodeAgentHandler'),
  313. jsonutils.dumps({
  314. 'mac': node.mac,
  315. 'manufacturer': 'new',
  316. 'agent_checksum': 'test'
  317. }),
  318. headers=self.default_headers)
  319. response = resp.json_body
  320. self.assertEqual(resp.status_code, 200)
  321. self.assertFalse('cached' in response and response['cached'])
  322. resp = self.app.put(
  323. reverse('NodeAgentHandler'),
  324. jsonutils.dumps({
  325. 'mac': node.mac,
  326. 'manufacturer': 'new',
  327. 'agent_checksum': 'test'
  328. }),
  329. headers=self.default_headers)
  330. response = resp.json_body
  331. self.assertEqual(resp.status_code, 200)
  332. self.assertTrue('cached' in response and response['cached'])
  333. def test_agent_updates_node_by_interfaces(self):
  334. node = self.env.create_node(api=False)
  335. interface = node.meta['interfaces'][0]
  336. resp = self.app.put(
  337. reverse('NodeAgentHandler'),
  338. jsonutils.dumps({
  339. 'mac': '00:00:00:00:00:00',
  340. 'meta': {
  341. 'interfaces': [interface]},
  342. }),
  343. headers=self.default_headers)
  344. self.assertEqual(resp.status_code, 200)
  345. def test_node_create_ip_not_in_admin_range(self):
  346. node = self.env.create_node(api=False)
  347. # Set IP outside of admin network range on eth1
  348. meta = copy.deepcopy(node.meta)
  349. meta['interfaces'][1]['ip'] = '10.21.0.3'
  350. self.app.put(
  351. reverse('NodeAgentHandler'),
  352. jsonutils.dumps({
  353. 'mac': node.mac,
  354. 'meta': meta,
  355. }),
  356. headers=self.default_headers)
  357. self.env.network_manager.update_interfaces_info(node)
  358. # node.mac == eth0 mac so eth0 should now be admin interface
  359. admin_iface = self.env.network_manager.get_admin_interface(node)
  360. self.assertEqual(admin_iface.name, 'eth0')
  361. def test_node_create_ext_mac(self):
  362. node1 = self.env.create_node(
  363. api=False
  364. )
  365. node2_json = {
  366. "mac": self.env.generate_random_mac(),
  367. "meta": self.env.default_metadata(),
  368. "status": "discover"
  369. }
  370. node2_json["meta"]["interfaces"][0]["mac"] = node1.mac
  371. resp = self.app.post(
  372. reverse('NodeCollectionHandler'),
  373. jsonutils.dumps(node2_json),
  374. headers=self.default_headers,
  375. expect_errors=True)
  376. self.assertEqual(resp.status_code, 409)
  377. def test_node_create_without_mac(self):
  378. node = self.env.create_node(
  379. api=True,
  380. exclude=["mac"],
  381. expect_http=400,
  382. expected_error="No mac address specified"
  383. )
  384. self.assertEqual(node, None)
  385. def test_node_create_with_invalid_disk_model(self):
  386. meta = self.env.default_metadata()
  387. meta['disks'][0]['model'] = None
  388. node = self.env.create_node(
  389. api=True,
  390. expect_http=201,
  391. meta=meta
  392. )
  393. self.assertIsNotNone(node)
  394. def test_node_create_mac_validation(self):
  395. # entry format: (mac_address, http_response_code)
  396. maccaddresses = (
  397. # invalid macaddresses
  398. ('60a44c3528ff', 400),
  399. ('60:a4:4c:35:28', 400),
  400. ('60:a4:4c:35:28:fg', 400),
  401. ('76:DC:7C:CA:G4:75', 400),
  402. ('76-DC-7C-CA-G4-75', 400),
  403. # valid macaddresses
  404. ('60:a4:4c:35:28:ff', 201),
  405. ('48-2C-6A-1E-59-3D', 201),
  406. )
  407. for mac, http_code in maccaddresses:
  408. response = self.app.post(
  409. reverse('NodeCollectionHandler'),
  410. jsonutils.dumps({
  411. 'mac': mac,
  412. 'status': 'discover',
  413. }),
  414. headers=self.default_headers,
  415. expect_errors=(http_code != 201)
  416. )
  417. self.assertEqual(response.status_code, http_code)
  418. def test_node_update_ext_mac(self):
  419. meta = self.env.default_metadata()
  420. node1 = self.env.create_node(
  421. api=False,
  422. mac=meta["interfaces"][0]["mac"],
  423. meta={}
  424. )
  425. node1_json = {
  426. "mac": self.env.generate_random_mac(),
  427. "meta": meta
  428. }
  429. # We want to be sure that new mac is not equal to old one
  430. self.assertNotEqual(node1.mac, node1_json["mac"])
  431. # Here we are trying to update node
  432. resp = self.app.put(
  433. reverse('NodeCollectionHandler'),
  434. jsonutils.dumps([node1_json]),
  435. headers=self.default_headers,
  436. expect_errors=True
  437. )
  438. self.assertEqual(resp.status_code, 200)
  439. # Here we are checking if node mac is successfully updated
  440. self.assertEqual(node1_json["mac"], resp.json_body[0]["mac"])
  441. self.assertEqual(meta, resp.json_body[0]["meta"])
  442. def test_duplicated_node_create_fails(self):
  443. node = self.env.create_node(api=False)
  444. resp = self.app.post(
  445. reverse('NodeCollectionHandler'),
  446. jsonutils.dumps({'mac': node.mac, 'status': 'discover'}),
  447. headers=self.default_headers,
  448. expect_errors=True)
  449. self.assertEqual(409, resp.status_code)
  450. def test_node_creation_fail(self):
  451. resp = self.app.post(
  452. reverse('NodeCollectionHandler'),
  453. jsonutils.dumps({'mac': self.env.generate_random_mac(),
  454. 'meta': self.env.default_metadata(),
  455. 'status': 'error'}),
  456. headers=self.default_headers,
  457. expect_errors=True)
  458. self.assertEqual(resp.status_code, 403)
  459. def test_reset_cluster_name_when_unassign_node(self):
  460. node_name = 'new_node_name'
  461. self.env.create(
  462. nodes_kwargs=[
  463. {'pending_roles': ['controller'],
  464. 'pending_addition': True,
  465. 'name': node_name}])
  466. node = self.env.nodes[0]
  467. resp = self.app.put(
  468. reverse('NodeCollectionHandler'),
  469. jsonutils.dumps([{'id': node.id,
  470. 'cluster_id': None,
  471. 'pending_roles': []}]),
  472. headers=self.default_headers)
  473. self.assertEqual(200, resp.status_code)
  474. self.assertEqual(1, len(resp.json_body))
  475. self.assertEqual(node.id, resp.json_body[0]['id'])
  476. self.assertEqual(node.name, node_name)
  477. self.assertEqual(node.cluster, None)
  478. self.assertEqual(node.pending_roles, [])
  479. def test_discovered_node_unified_name(self):
  480. node_mac = self.env.generate_random_mac()
  481. def node_name_test(mac):
  482. self.env.create_node(
  483. api=True,
  484. **{'mac': mac}
  485. )
  486. node = self.app.get(reverse('NodeCollectionHandler')).json_body[0]
  487. self.assertEqual(node['name'],
  488. 'Untitled ({0})'.format(node_mac[-5:]))
  489. node_name_test(node_mac.upper())
  490. node_id = self.app.get(
  491. reverse('NodeCollectionHandler')
  492. ).json_body[0]['id']
  493. self.app.delete(
  494. reverse('NodeHandler', {'obj_id': node_id})
  495. )
  496. node_name_test(node_mac.lower())
  497. def check_pending_roles(self, to_check, msg):
  498. node = self.env.create_node(api=False)
  499. data = {'id': node.id,
  500. 'cluster_id': 1}
  501. data.update(to_check)
  502. resp = self.app.put(
  503. reverse('NodeCollectionHandler'),
  504. jsonutils.dumps([data]),
  505. headers=self.default_headers,
  506. expect_errors=True)
  507. self.assertEqual(400, resp.status_code)
  508. self.assertIn(msg, resp.json_body["message"])
  509. def test_pending_role_non_existing(self):
  510. cluster = self.env.create()
  511. self.check_pending_roles({'pending_roles': ['qwe'],
  512. 'cluster_id': cluster.id},
  513. 'are not valid for node')
  514. def test_pending_role_duplicates(self):
  515. self.check_pending_roles({'pending_roles': ['cinder', 'cinder']},
  516. 'contains duplicates')
  517. def test_pending_role_not_list(self):
  518. self.check_pending_roles({'pending_roles': 'cinder'},
  519. "Failed validating 'type'")
  520. def test_pending_role_not_strings(self):
  521. self.check_pending_roles({'pending_roles': ['cinder', 1]},
  522. "Failed validating 'type'")
  523. def test_role_non_existing(self):
  524. cluster = self.env.create()
  525. self.check_pending_roles({'roles': ['qwe'],
  526. 'cluster_id': cluster.id},
  527. 'are not valid for node')
  528. def test_role_duplicates(self):
  529. self.check_pending_roles({'roles': ['cinder', 'cinder']},
  530. 'contains duplicates')
  531. def test_roles_not_list(self):
  532. self.check_pending_roles({'roles': 'cinder'},
  533. 'Failed validating')
  534. def test_roles_not_strings(self):
  535. self.check_pending_roles({'roles': ['cinder', 1]},
  536. 'Failed validating')
  537. def check_update_role_no_cluster_id(self, data_to_check):
  538. self.env.create()
  539. node = self.env.create_node(api=False)
  540. data = {'id': node.id}
  541. data.update(data_to_check)
  542. resp = self.app.put(
  543. reverse('NodeCollectionHandler'),
  544. jsonutils.dumps([data]),
  545. headers=self.default_headers,
  546. expect_errors=True)
  547. self.assertEqual(400, resp.status_code)
  548. self.assertIn("doesn't belong to any cluster",
  549. resp.json_body["message"])
  550. def test_update_role_no_cluster_id(self):
  551. self.check_update_role_no_cluster_id({'pending_roles': ['compute']})
  552. def test_update_pending_role_no_cluster_id(self):
  553. self.check_update_role_no_cluster_id({'roles': ['compute']})