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_nic_handlers_w_bonding.py 36KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002
  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 mock
  16. import uuid
  17. from oslo_serialization import jsonutils
  18. from nailgun.consts import BOND_MODES
  19. from nailgun.consts import BOND_TYPES
  20. from nailgun.consts import BOND_XMIT_HASH_POLICY
  21. from nailgun.consts import CLUSTER_MODES
  22. from nailgun.consts import HYPERVISORS
  23. from nailgun.consts import NETWORK_INTERFACE_TYPES
  24. from nailgun.db import db
  25. from nailgun.extensions.network_manager.validators import network
  26. from nailgun import objects
  27. from nailgun.settings import settings
  28. from nailgun.test.base import BaseIntegrationTest
  29. from nailgun.utils import reverse
  30. class TestNodeNICsBonding(BaseIntegrationTest):
  31. def setUp(self):
  32. super(TestNodeNICsBonding, self).setUp()
  33. meta = self.env.default_metadata()
  34. self.env.set_interfaces_in_meta(meta, [
  35. {"name": "eth0",
  36. "mac": "00:00:00:00:00:66",
  37. "pxe": True,
  38. "offloading_modes": [
  39. {
  40. "name": "mode_1",
  41. "state": None,
  42. "sub": []
  43. },
  44. {
  45. "name": "mode_common",
  46. "state": None,
  47. "sub": []
  48. }
  49. ]
  50. },
  51. {"name": "eth1",
  52. "mac": "00:00:00:00:00:77",
  53. "offloading_modes": [
  54. {
  55. "name": "mode_2",
  56. "state": None,
  57. "sub": []
  58. },
  59. {
  60. "name": "mode_common",
  61. "state": None,
  62. "sub": []
  63. }
  64. ]
  65. },
  66. {"name": "eth2",
  67. "mac": "00:00:00:00:00:88",
  68. "offloading_modes": [
  69. {
  70. "name": "mode_3",
  71. "state": None,
  72. "sub": []
  73. },
  74. {
  75. "name": "mode_4",
  76. "state": None,
  77. "sub": []
  78. },
  79. {
  80. "name": "mode_common",
  81. "state": None,
  82. "sub": []
  83. }
  84. ]},
  85. {"name": "eth3",
  86. "mac": "00:00:00:00:00:99",
  87. 'interface_properties': {
  88. 'sriov': {
  89. 'sriov_totalvfs': 8,
  90. 'available': True,
  91. 'pci_id': '1234:5678'
  92. }
  93. }}
  94. ])
  95. self.cluster = self.env.create(
  96. cluster_kwargs={
  97. "net_provider": "neutron",
  98. "net_segment_type": "gre"
  99. },
  100. nodes_kwargs=[
  101. {"api": True,
  102. "pending_addition": True,
  103. "meta": meta}
  104. ]
  105. )
  106. self.get_node_nics_info()
  107. def get_node_nics_info(self):
  108. resp = self.app.get(
  109. reverse("NodeNICsHandler",
  110. kwargs={"node_id": self.env.nodes[0]["id"]}),
  111. headers=self.default_headers)
  112. self.assertEqual(resp.status_code, 200)
  113. self.data = resp.json_body
  114. self.admin_nic, self.other_nic, self.empty_nic = None, None, None
  115. self.sriov_nic = None
  116. for nic in self.data:
  117. net_names = [n["name"] for n in nic["assigned_networks"]]
  118. if "fuelweb_admin" in net_names:
  119. self.admin_nic = nic
  120. elif net_names:
  121. self.other_nic = nic
  122. elif nic['meta']['sriov']['available']:
  123. self.sriov_nic = nic
  124. else:
  125. self.empty_nic = nic
  126. self.assertTrue(self.admin_nic and self.other_nic and
  127. self.empty_nic and self.sriov_nic)
  128. def put_single(self):
  129. return self.env.node_nics_put(self.env.nodes[0]["id"], self.data,
  130. expect_errors=True)
  131. def put_collection(self):
  132. nodes_list = [{"id": self.env.nodes[0]["id"],
  133. "interfaces": self.data}]
  134. return self.env.node_collection_nics_put(nodes_list,
  135. expect_errors=True)
  136. def node_nics_put_check_error(self, message):
  137. for put_func in (self.put_single, self.put_collection):
  138. resp = put_func()
  139. self.assertEqual(resp.status_code, 400)
  140. self.assertEqual(resp.json_body["message"], message)
  141. def nics_bond_create_and_check(self, put_func):
  142. bond_name = 'bond0'
  143. self.prepare_bond_w_props(bond_name=bond_name)
  144. self.check_bond_creation(put_func, bond_name=bond_name)
  145. def prepare_bond_w_props(self, bond_name='bond0',
  146. bond_type=BOND_TYPES.linux,
  147. bond_mode=BOND_MODES.l_802_3ad,
  148. iface_props=None):
  149. if iface_props is None:
  150. iface_props = {}
  151. attributes = {
  152. 'mode': {'value': {'value': bond_mode}},
  153. 'xmit_hash_policy': {
  154. 'value': {'value': BOND_XMIT_HASH_POLICY.layer2_3}},
  155. 'lacp_rate': {'value': {'value': 'slow'}},
  156. 'type__': {'value': bond_type},
  157. 'offloading': {'modes': {'value': {'mode_common': None}}}
  158. }
  159. attributes.update(iface_props)
  160. self.data.append({
  161. 'name': bond_name,
  162. 'type': NETWORK_INTERFACE_TYPES.bond,
  163. 'attributes': attributes,
  164. 'slaves': [
  165. {'name': self.other_nic['name']},
  166. {'name': self.empty_nic['name']}],
  167. 'assigned_networks': self.other_nic['assigned_networks']
  168. })
  169. self.other_nic["assigned_networks"] = []
  170. def check_bond_creation(self, put_func, bond_name='bond0'):
  171. resp = put_func()
  172. self.assertEqual(resp.status_code, 200)
  173. resp = self.env.node_nics_get(self.env.nodes[0]["id"])
  174. self.assertEqual(resp.status_code, 200)
  175. bonds = filter(
  176. lambda iface: iface["type"] == NETWORK_INTERFACE_TYPES.bond,
  177. resp.json_body)
  178. self.assertEqual(len(bonds), 1)
  179. self.assertEqual(bonds[0]["name"], bond_name)
  180. modes = bonds[0]['attributes']['offloading']['modes']['value']
  181. self.assertDictEqual(
  182. modes, {'mode_common': None})
  183. def nics_bond_remove(self, put_func):
  184. resp = self.env.node_nics_get(self.env.nodes[0]["id"])
  185. self.assertEqual(resp.status_code, 200)
  186. self.data = resp.json_body
  187. for nic in self.data:
  188. if nic["type"] == NETWORK_INTERFACE_TYPES.bond:
  189. bond = nic
  190. break
  191. else:
  192. raise Exception("No bond was found unexpectedly")
  193. for nic in self.data:
  194. if nic["name"] == bond["slaves"][0]["name"]:
  195. nic["assigned_networks"] = bond["assigned_networks"]
  196. break
  197. else:
  198. raise Exception("NIC from bond wasn't found unexpectedly")
  199. self.data.remove(bond)
  200. resp = put_func()
  201. self.assertEqual(resp.status_code, 200)
  202. def test_nics_bond_delete(self):
  203. for put_func in (self.put_single, self.put_collection):
  204. self.get_node_nics_info()
  205. self.nics_bond_create_and_check(put_func)
  206. self.nics_bond_remove(put_func)
  207. resp = self.env.node_nics_get(self.env.nodes[0]["id"])
  208. self.assertEqual(resp.status_code, 200)
  209. for nic in resp.json_body:
  210. self.assertNotEqual(nic["type"], NETWORK_INTERFACE_TYPES.bond)
  211. def test_nics_linux_bond_create_delete(self):
  212. bond_name = 'bond0'
  213. for put_func in (self.put_single, self.put_collection):
  214. self.get_node_nics_info()
  215. self.prepare_bond_w_props(bond_name=bond_name)
  216. self.check_bond_creation(put_func, bond_name=bond_name)
  217. self.nics_bond_remove(put_func)
  218. resp = self.env.node_nics_get(self.env.nodes[0]["id"])
  219. self.assertEqual(resp.status_code, 200)
  220. for nic in resp.json_body:
  221. self.assertNotEqual(nic["type"], NETWORK_INTERFACE_TYPES.bond)
  222. def test_nics_ovs_bond_create_failed_without_dpdk(self):
  223. bond_name = 'bond0'
  224. self.prepare_bond_w_props(bond_name=bond_name,
  225. bond_type=BOND_TYPES.dpdkovs,
  226. bond_mode=BOND_MODES.balance_tcp)
  227. self.node_nics_put_check_error("Bond interface '{0}': DPDK should be"
  228. " enabled for 'dpdkovs' bond type".
  229. format(bond_name))
  230. @mock.patch.object(objects.NIC, 'dpdk_available')
  231. def test_nics_lnx_bond_create_failed_with_dpdk(self, m_dpdk_available):
  232. m_dpdk_available.return_value = True
  233. bond_name = 'bond0'
  234. self.prepare_bond_w_props(
  235. bond_name=bond_name,
  236. bond_type=BOND_TYPES.linux,
  237. iface_props={'dpdk': {'enabled': {'value': True}}})
  238. self.node_nics_put_check_error("Bond interface '{0}': DPDK can be"
  239. " enabled only for 'dpdkovs' bond type".
  240. format(bond_name))
  241. def test_nics_ovs_bond_update_failed_without_dpdk(self):
  242. bond_name = 'bond0'
  243. node = self.env.nodes[0]
  244. self.prepare_bond_w_props(bond_name=bond_name,
  245. bond_type=BOND_TYPES.linux)
  246. resp = self.put_single()
  247. self.assertEqual(resp.status_code, 200)
  248. self.data = self.env.node_nics_get(node.id).json_body
  249. bond = [iface for iface in self.data
  250. if iface['type'] == NETWORK_INTERFACE_TYPES.bond][0]
  251. bond['attributes']['type__']['value'] = BOND_TYPES.dpdkovs
  252. bond['attributes']['mode']['value']['value'] = BOND_MODES.balance_tcp
  253. self.node_nics_put_check_error(
  254. "Bond interface '{0}': DPDK should be enabled for 'dpdkovs' bond"
  255. " type".format(bond_name))
  256. @mock.patch.object(objects.Bond, 'dpdk_available')
  257. def test_nics_lnx_bond_update_failed_with_dpdk(self, m_dpdk_available):
  258. m_dpdk_available.return_value = True
  259. bond_name = 'bond0'
  260. node = self.env.nodes[0]
  261. self.prepare_bond_w_props(bond_name=bond_name,
  262. bond_type=BOND_TYPES.linux)
  263. resp = self.put_single()
  264. self.assertEqual(resp.status_code, 200)
  265. self.data = self.env.node_nics_get(node.id).json_body
  266. bond = [iface for iface in self.data
  267. if iface['type'] == NETWORK_INTERFACE_TYPES.bond][0]
  268. bond['attributes'] = {
  269. 'type__': {'value': BOND_TYPES.linux},
  270. 'dpdk': {'enabled': {'value': True}}}
  271. self.node_nics_put_check_error(
  272. "Bond interface '{0}': DPDK can be enabled only for 'dpdkovs' bond"
  273. " type".format(bond_name))
  274. def test_nics_bond_removed_on_node_unassign(self):
  275. self.get_node_nics_info()
  276. self.nics_bond_create_and_check(self.put_single)
  277. node = self.env.nodes[0]
  278. resp = self.app.post(
  279. reverse(
  280. 'NodeUnassignmentHandler',
  281. kwargs={'cluster_id': self.cluster.id}
  282. ),
  283. jsonutils.dumps([{'id': node.id}]),
  284. headers=self.default_headers
  285. )
  286. self.assertEqual(200, resp.status_code)
  287. self.assertEqual(node.cluster, None)
  288. resp = self.env.node_nics_get(node.id)
  289. self.assertEqual(resp.status_code, 200)
  290. for nic in resp.json_body:
  291. self.assertNotEqual(nic["type"], NETWORK_INTERFACE_TYPES.bond)
  292. def test_nics_bond_removed_on_remove_node_from_cluster(self):
  293. self.get_node_nics_info()
  294. self.nics_bond_create_and_check(self.put_single)
  295. node = self.env.nodes[0]
  296. resp = self.app.put(
  297. reverse('ClusterHandler',
  298. kwargs={'obj_id': self.cluster.id}),
  299. jsonutils.dumps({'nodes': []}),
  300. headers=self.default_headers,
  301. expect_errors=True
  302. )
  303. self.assertEqual(resp.status_code, 200)
  304. self.assertEqual(node.cluster, None)
  305. resp = self.env.node_nics_get(node.id)
  306. self.assertEqual(resp.status_code, 200)
  307. for nic in resp.json_body:
  308. self.assertNotEqual(nic["type"], NETWORK_INTERFACE_TYPES.bond)
  309. def test_nics_bond_create_failed_no_type(self):
  310. self.data.append({
  311. "name": 'ovs-bond0'
  312. })
  313. self.node_nics_put_check_error(
  314. "Node '{0}': each interface must have a "
  315. "type".format(self.env.nodes[0]["id"])
  316. )
  317. def test_nics_bond_create_failed_not_have_enough_data(self):
  318. self.data.append({
  319. "type": NETWORK_INTERFACE_TYPES.bond
  320. })
  321. self.other_nic["assigned_networks"] = []
  322. self.node_nics_put_check_error(
  323. "Node '{0}': each bond interface must have "
  324. "name".format(self.env.nodes[0]["id"])
  325. )
  326. def test_nics_bond_create_failed_unknown_mode(self):
  327. self.data.append({
  328. "name": 'ovs-bond0',
  329. "type": NETWORK_INTERFACE_TYPES.bond,
  330. "mode": "unknown",
  331. "attributes": {
  332. 'type__': {'value': BOND_TYPES.linux}
  333. },
  334. "slaves": [
  335. {"name": self.other_nic["name"]},
  336. {"name": self.empty_nic["name"]}],
  337. "assigned_networks": self.other_nic["assigned_networks"]
  338. })
  339. self.other_nic["assigned_networks"] = []
  340. self.node_nics_put_check_error(
  341. "Node '{0}': bond interface 'ovs-bond0' has unknown mode "
  342. "'unknown'".format(self.env.nodes[0]["id"])
  343. )
  344. def test_nics_bond_create_failed_no_mode(self):
  345. self.data.append({
  346. "name": 'ovs-bond0',
  347. "type": NETWORK_INTERFACE_TYPES.bond,
  348. "attributes": {
  349. 'type__': {'value': BOND_TYPES.linux}
  350. },
  351. "slaves": [
  352. {"name": self.other_nic["name"]},
  353. {"name": self.empty_nic["name"]}],
  354. "assigned_networks": self.other_nic["assigned_networks"]
  355. })
  356. self.other_nic["assigned_networks"] = []
  357. self.node_nics_put_check_error(
  358. "Node '{0}': bond interface 'ovs-bond0' doesn't have mode".format(
  359. self.env.nodes[0]["id"]))
  360. def test_nics_bond_create_failed_no_mode_in_properties(self):
  361. self.data.append({
  362. "name": 'bond0',
  363. "type": NETWORK_INTERFACE_TYPES.bond,
  364. "attributes": {
  365. 'xmit_hash_policy': {
  366. 'value': {'value': BOND_XMIT_HASH_POLICY.layer2_3}},
  367. 'type__': {'value': BOND_TYPES.linux}
  368. },
  369. "slaves": [
  370. {"name": self.other_nic["name"]},
  371. {"name": self.empty_nic["name"]}],
  372. "assigned_networks": self.other_nic["assigned_networks"]
  373. })
  374. self.other_nic["assigned_networks"] = []
  375. self.node_nics_put_check_error(
  376. "Node '{0}': bond interface 'bond0' doesn't have mode".format(
  377. self.env.nodes[0]["id"]))
  378. def test_nics_bond_create_failed_unknown_mode_in_properties(self):
  379. self.data.append({
  380. "name": 'bond0',
  381. "type": NETWORK_INTERFACE_TYPES.bond,
  382. "attributes": {
  383. 'type__': {'value': BOND_TYPES.linux},
  384. 'mode': {'value': {'value': 'unknown'}}
  385. },
  386. "slaves": [
  387. {"name": self.other_nic["name"]},
  388. {"name": self.empty_nic["name"]}],
  389. "assigned_networks": self.other_nic["assigned_networks"]
  390. })
  391. self.other_nic["assigned_networks"] = []
  392. self.node_nics_put_check_error(
  393. "Node '{0}': bond interface 'bond0' has unknown mode "
  394. "'unknown'".format(self.env.nodes[0]["id"]))
  395. def test_nics_bond_create_failed_no_slaves(self):
  396. self.data.append({
  397. "name": 'ovs-bond0',
  398. "type": NETWORK_INTERFACE_TYPES.bond,
  399. "mode": BOND_MODES.balance_slb,
  400. "assigned_networks": self.other_nic["assigned_networks"]
  401. })
  402. self.other_nic["assigned_networks"] = []
  403. self.node_nics_put_check_error(
  404. "Node '{0}': each bond interface must have "
  405. "two or more slaves".format(self.env.nodes[0]["id"])
  406. )
  407. def test_nics_bond_create_failed_one_slave(self):
  408. self.data.append({
  409. "name": 'ovs-bond0',
  410. "type": NETWORK_INTERFACE_TYPES.bond,
  411. "mode": BOND_MODES.balance_slb,
  412. "slaves": [
  413. {"name": self.other_nic["name"]}],
  414. "assigned_networks": self.other_nic["assigned_networks"]
  415. })
  416. self.other_nic["assigned_networks"] = []
  417. self.node_nics_put_check_error(
  418. "Node '{0}': each bond interface must have "
  419. "two or more slaves".format(self.env.nodes[0]["id"])
  420. )
  421. def test_nics_bond_create_failed_no_assigned_networks(self):
  422. self.data.append({
  423. "name": 'ovs-bond0',
  424. "type": NETWORK_INTERFACE_TYPES.bond,
  425. "attributes": {
  426. "type__": {'value': BOND_TYPES.ovs}
  427. },
  428. "mode": BOND_MODES.balance_slb,
  429. "slaves": [
  430. {"name": self.other_nic["name"]},
  431. {"name": self.empty_nic["name"]}],
  432. })
  433. self.other_nic["assigned_networks"] = []
  434. self.node_nics_put_check_error(
  435. "Node '{0}', interface 'ovs-bond0': there is no "
  436. "'assigned_networks' list".format(self.env.nodes[0]["id"])
  437. )
  438. def test_nics_bond_create_failed_nic_is_used_twice(self):
  439. self.data.append({
  440. "name": 'ovs-bond0',
  441. "type": NETWORK_INTERFACE_TYPES.bond,
  442. "attributes": {
  443. "type__": {'value': BOND_TYPES.ovs}
  444. },
  445. "mode": BOND_MODES.balance_slb,
  446. "slaves": [
  447. {"name": self.other_nic["name"]},
  448. {"name": self.other_nic["name"]}],
  449. "assigned_networks": self.other_nic["assigned_networks"]
  450. })
  451. self.other_nic["assigned_networks"] = []
  452. self.node_nics_put_check_error(
  453. "Node '{0}': interface '{1}' is used in bonds more "
  454. "than once".format(self.env.nodes[0]["id"], self.other_nic["id"])
  455. )
  456. def test_nics_bond_create_failed_duplicated_assigned_networks(self):
  457. self.data.append({
  458. "name": 'ovs-bond0',
  459. "type": NETWORK_INTERFACE_TYPES.bond,
  460. "mode": BOND_MODES.balance_slb,
  461. "attributes": {
  462. "type__": {'value': BOND_TYPES.ovs}
  463. },
  464. "slaves": [
  465. {"name": self.other_nic["name"]},
  466. {"name": self.empty_nic["name"]}],
  467. "assigned_networks": self.other_nic["assigned_networks"]
  468. })
  469. self.node_nics_put_check_error(
  470. "Node '{0}': there is a duplicated network '{1}' in "
  471. "assigned networks (second occurrence is in interface "
  472. "'ovs-bond0')".format(
  473. self.env.nodes[0]["id"],
  474. self.other_nic["assigned_networks"][0]["id"])
  475. )
  476. def test_nics_bond_create_failed_unknown_interface(self):
  477. self.data.append({
  478. "name": 'ovs-bond0',
  479. "type": NETWORK_INTERFACE_TYPES.bond,
  480. "mode": BOND_MODES.balance_slb,
  481. "attributes": {
  482. "type__": {'value': BOND_TYPES.ovs}
  483. },
  484. "slaves": [
  485. {"name": self.other_nic["name"]},
  486. {"name": "some_nic"}],
  487. "assigned_networks": self.other_nic["assigned_networks"]
  488. })
  489. self.other_nic["assigned_networks"] = []
  490. self.node_nics_put_check_error(
  491. "Node '{0}': there is no interface 'some_nic' found for bond "
  492. "'ovs-bond0' in DB".format(self.env.nodes[0]["id"])
  493. )
  494. def test_nics_bond_create_failed_slave_has_assigned_networks(self):
  495. self.data.append({
  496. "name": 'ovs-bond0',
  497. "type": NETWORK_INTERFACE_TYPES.bond,
  498. "attributes": {
  499. "type__": {'value': BOND_TYPES.ovs}
  500. },
  501. "mode": BOND_MODES.balance_slb,
  502. "slaves": [
  503. {"name": self.other_nic["name"]},
  504. {"name": self.empty_nic["name"]}],
  505. "assigned_networks": []
  506. })
  507. self.node_nics_put_check_error(
  508. "Node '{0}': interface '{1}' cannot have assigned networks as it "
  509. "is used in bond".format(self.env.nodes[0]["id"],
  510. self.other_nic["id"])
  511. )
  512. def test_nics_bond_create_failed_slave_has_no_name(self):
  513. self.data.append({
  514. "name": 'ovs-bond0',
  515. "type": NETWORK_INTERFACE_TYPES.bond,
  516. "attributes": {
  517. "type__": {'value': BOND_TYPES.ovs}
  518. },
  519. "mode": BOND_MODES.balance_slb,
  520. "slaves": [
  521. {"name": self.other_nic["name"]},
  522. {"nic": self.empty_nic["name"]}],
  523. "assigned_networks": self.other_nic["assigned_networks"]
  524. })
  525. self.other_nic["assigned_networks"] = []
  526. self.node_nics_put_check_error(
  527. "Node '{0}', interface 'ovs-bond0': each bond slave "
  528. "must have name".format(self.env.nodes[0]["id"])
  529. )
  530. @mock.patch.dict(settings.VERSION, {'feature_groups': []})
  531. def test_nics_bond_create_failed_admin_net_w_lacp_lnx(self):
  532. mode = BOND_MODES.l_802_3ad
  533. bond_nets = self.admin_nic["assigned_networks"] + \
  534. self.other_nic["assigned_networks"]
  535. self.data.append({
  536. "name": 'lnx-bond0',
  537. "type": NETWORK_INTERFACE_TYPES.bond,
  538. "attributes": {
  539. "type__": {'value': BOND_TYPES.linux}
  540. },
  541. "mode": mode,
  542. "slaves": [
  543. {"name": self.admin_nic["name"]},
  544. {"name": self.other_nic["name"]}],
  545. "assigned_networks": bond_nets
  546. })
  547. self.admin_nic["assigned_networks"] = []
  548. self.other_nic["assigned_networks"] = []
  549. self.node_nics_put_check_error(
  550. "Node '{0}': interface 'lnx-bond0' belongs to admin network "
  551. "and has lacp mode '{1}'".format(self.env.nodes[0]["id"], mode)
  552. )
  553. @mock.patch.dict(settings.VERSION, {'feature_groups': []})
  554. def test_nics_bond_create_failed_admin_net_w_lacp_ovs(self):
  555. mode = BOND_MODES.lacp_balance_tcp
  556. bond_nets = self.admin_nic["assigned_networks"] + \
  557. self.other_nic["assigned_networks"]
  558. self.data.append({
  559. "name": 'ovs-bond0',
  560. "type": NETWORK_INTERFACE_TYPES.bond,
  561. "attributes": {
  562. "type__": {'value': BOND_TYPES.ovs}
  563. },
  564. "mode": mode,
  565. "slaves": [
  566. {"name": self.admin_nic["name"]},
  567. {"name": self.other_nic["name"]}],
  568. "assigned_networks": bond_nets
  569. })
  570. self.admin_nic["assigned_networks"] = []
  571. self.other_nic["assigned_networks"] = []
  572. self.node_nics_put_check_error(
  573. "Node '{0}': interface 'ovs-bond0' belongs to admin network "
  574. "and has lacp mode '{1}'".format(self.env.nodes[0]["id"], mode)
  575. )
  576. def test_nics_bond_create_admin_net_w_lacp_experimental_mode(self):
  577. mode = BOND_MODES.lacp_balance_tcp
  578. bond_nets = self.admin_nic["assigned_networks"] + \
  579. self.other_nic["assigned_networks"]
  580. self.data.append({
  581. "name": 'ovs-bond0',
  582. "type": NETWORK_INTERFACE_TYPES.bond,
  583. "attributes": {
  584. "type__": {'value': BOND_TYPES.ovs}
  585. },
  586. "mode": mode,
  587. "slaves": [
  588. {"name": self.admin_nic["name"]},
  589. {"name": self.other_nic["name"]}],
  590. "assigned_networks": bond_nets
  591. })
  592. self.admin_nic["assigned_networks"] = []
  593. self.other_nic["assigned_networks"] = []
  594. resp = self.put_single()
  595. self.assertEqual(resp.status_code, 200)
  596. def test_nics_bond_create_failed_admin_net_w_o_pxe_iface(self):
  597. mode = BOND_MODES.balance_slb
  598. bond_nets = [self.admin_nic["assigned_networks"][0]] + \
  599. self.other_nic["assigned_networks"]
  600. del self.admin_nic["assigned_networks"][0]
  601. self.data.append({
  602. "name": 'ovs-bond0',
  603. "type": NETWORK_INTERFACE_TYPES.bond,
  604. "attributes": {
  605. "type__": {'value': BOND_TYPES.ovs}
  606. },
  607. "mode": mode,
  608. "slaves": [
  609. {"name": self.empty_nic["name"]},
  610. {"name": self.other_nic["name"]}],
  611. "assigned_networks": bond_nets
  612. })
  613. self.other_nic["assigned_networks"] = []
  614. self.node_nics_put_check_error(
  615. "Node '{0}': interface 'ovs-bond0' belongs to admin network "
  616. "and doesn't contain node's pxe interface 'eth0'".format(
  617. self.env.nodes[0]["id"])
  618. )
  619. def test_nics_bond_change_offloading_modes(self):
  620. self.get_node_nics_info()
  621. self.nics_bond_create_and_check(self.put_single)
  622. resp = self.app.get(
  623. reverse("NodeNICsHandler",
  624. kwargs={"node_id": self.env.nodes[0]["id"]}),
  625. headers=self.default_headers)
  626. self.assertEqual(200, resp.status_code)
  627. body = resp.json_body
  628. bonds = filter(
  629. lambda iface: iface["type"] == NETWORK_INTERFACE_TYPES.bond,
  630. body)
  631. self.assertEqual(1, len(bonds))
  632. bond_offloading_modes = bonds[0]['attributes'][
  633. 'offloading']['modes']['value']
  634. self.assertEqual(len(bond_offloading_modes), 1)
  635. slaves = bonds[0]['slaves']
  636. self.assertEqual(2, len(slaves))
  637. self.assertIsNone(bond_offloading_modes['mode_common'])
  638. bond_offloading_modes['mode_common'] = True
  639. resp = self.env.node_nics_put(
  640. self.env.nodes[0]["id"],
  641. body)
  642. body = resp.json_body
  643. bonds = filter(
  644. lambda iface: iface["type"] == NETWORK_INTERFACE_TYPES.bond,
  645. body)
  646. self.assertEqual(1, len(bonds))
  647. bond_offloading_modes = bonds[0]['attributes'][
  648. 'offloading']['modes']['value']
  649. self.assertEqual(len(bond_offloading_modes), 1)
  650. slaves = bonds[0]['slaves']
  651. self.assertEqual(2, len(slaves))
  652. self.assertTrue(bond_offloading_modes['mode_common'])
  653. def test_nics_bond_cannot_contain_sriov_enabled_interfaces(self):
  654. self.data.append({
  655. "name": 'ovs-bond0',
  656. "type": NETWORK_INTERFACE_TYPES.bond,
  657. "attributes": {
  658. "type__": {'value': BOND_TYPES.ovs}
  659. },
  660. "mode": BOND_MODES.balance_slb,
  661. "slaves": [
  662. {"name": self.admin_nic["name"]},
  663. {"name": self.sriov_nic["name"]}],
  664. "assigned_networks": self.sriov_nic["assigned_networks"]
  665. })
  666. self.sriov_nic['attributes']['sriov']['enabled']['value'] = True
  667. self.sriov_nic['attributes']['sriov']['numvfs']['value'] = 2
  668. cluster_db = self.env.clusters[-1]
  669. cluster_attrs = objects.Cluster.get_editable_attributes(cluster_db)
  670. cluster_attrs['common']['libvirt_type']['value'] = HYPERVISORS.kvm
  671. objects.Cluster.update_attributes(
  672. cluster_db, {'editable': cluster_attrs})
  673. db().commit()
  674. self.node_nics_put_check_error(
  675. "Node '{0}': bond 'ovs-bond0' cannot contain SRIOV "
  676. "enabled interface '{1}'".format(self.env.nodes[0]["id"],
  677. self.sriov_nic['name'])
  678. )
  679. def test_nics_bond_create_failed_without_type__(self):
  680. self.data.append({
  681. "name": 'ovs-bond0',
  682. "type": NETWORK_INTERFACE_TYPES.bond,
  683. "attributes": {
  684. "mode": {'value': BOND_MODES.balance_slb}
  685. },
  686. "slaves": [
  687. {"name": self.admin_nic["name"]},
  688. {"name": self.sriov_nic["name"]}],
  689. "assigned_networks": self.sriov_nic["assigned_networks"]
  690. })
  691. self.node_nics_put_check_error(
  692. "Node '{0}', bond interface 'ovs-bond0': doesn't have "
  693. "attributes.type__".format(self.env.nodes[0]["id"]))
  694. def test_nics_bond_create_failed_without_attributes(self):
  695. self.data.append({
  696. "name": 'ovs-bond0',
  697. "type": NETWORK_INTERFACE_TYPES.bond,
  698. "mode": BOND_MODES.balance_slb,
  699. "slaves": [
  700. {"name": self.admin_nic["name"]},
  701. {"name": self.sriov_nic["name"]}],
  702. "assigned_networks": self.sriov_nic["assigned_networks"]
  703. })
  704. self.node_nics_put_check_error(
  705. "Node '{0}', bond interface 'ovs-bond0': doesn't have "
  706. "attributes".format(self.env.nodes[0]["id"]))
  707. def test_nics_bond_create_failed_with_unexpected_type__(self):
  708. self.data.append({
  709. "name": 'ovs-bond0',
  710. "type": NETWORK_INTERFACE_TYPES.bond,
  711. "attributes": {
  712. "mode": {'value': {'value': BOND_MODES.balance_slb}},
  713. "type__": {'value': 'unexpected_type'},
  714. },
  715. "slaves": [
  716. {"name": self.admin_nic["name"]},
  717. {"name": self.sriov_nic["name"]}],
  718. "assigned_networks": self.sriov_nic["assigned_networks"]
  719. })
  720. self.node_nics_put_check_error(
  721. "Node '{0}', interface 'ovs-bond0': unknown type__ "
  722. "'unexpected_type'. type__ should be in '{1}'".format(
  723. self.env.nodes[0]["id"], ','.join([k for k in BOND_TYPES])))
  724. def test_each_bond_type_has_allowed_mode(self):
  725. for bond_type in BOND_TYPES:
  726. self.assertIsNotNone(
  727. network.NetAssignmentValidator.get_allowed_modes_for_bond_type(
  728. bond_type)
  729. )
  730. def test_nics_bond_create_failed_with_incorrect_mode_for_type(self):
  731. self.data.append({
  732. "name": 'ovs-bond0',
  733. "type": NETWORK_INTERFACE_TYPES.bond,
  734. "attributes": {
  735. "mode": {'value': {'value': BOND_MODES.balance_rr}},
  736. "type__": {'value': BOND_TYPES.ovs},
  737. },
  738. "slaves": [
  739. {"name": self.admin_nic["name"]},
  740. {"name": self.sriov_nic["name"]}],
  741. "assigned_networks": self.sriov_nic["assigned_networks"]
  742. })
  743. allowed_modes = (BOND_MODES.active_backup,
  744. BOND_MODES.balance_slb,
  745. BOND_MODES.balance_tcp,
  746. BOND_MODES.lacp_balance_tcp,
  747. )
  748. self.node_nics_put_check_error(
  749. "Node '{0}', bond interface 'ovs-bond0': mode '{1}' is not "
  750. "allowed for type '{2}'. Allowed modes for '{2}' "
  751. "type: '{3}'".format(
  752. self.env.nodes[0]["id"], BOND_MODES.balance_rr, BOND_TYPES.ovs,
  753. allowed_modes))
  754. class TestBondAttributesDefaultsHandler(BaseIntegrationTest):
  755. EXPECTED_ATTRIBUTES = {
  756. 'type__': {
  757. 'value': None,
  758. 'type': 'hidden'
  759. },
  760. 'mode': {
  761. 'value': {
  762. 'weight': 10,
  763. 'type': 'select',
  764. 'value': '',
  765. 'label': 'Mode'
  766. },
  767. 'metadata': {
  768. 'weight': 10,
  769. 'label': 'Mode'
  770. }
  771. },
  772. 'offloading': {
  773. 'metadata': {
  774. 'weight': 20,
  775. 'label': 'Offloading'
  776. },
  777. 'disable': {
  778. 'weight': 10,
  779. 'type': 'checkbox',
  780. 'value': False,
  781. 'label': 'Disable Offloading'
  782. },
  783. 'modes': {
  784. 'weight': 20,
  785. 'type': 'offloading_modes',
  786. 'value': {},
  787. 'label': 'Offloading Modes'
  788. }
  789. },
  790. 'mtu': {
  791. 'metadata': {
  792. 'weight': 30,
  793. 'label': 'MTU'
  794. },
  795. 'value': {
  796. 'weight': 10,
  797. 'type': 'number',
  798. 'nullable': True,
  799. 'value': None,
  800. 'label': 'Use Custom MTU',
  801. 'min': 42,
  802. 'max': 65536
  803. }
  804. },
  805. 'dpdk': {
  806. 'enabled': {
  807. 'value': False,
  808. 'label': 'Enable DPDK',
  809. 'description': 'The Data Plane Development Kit (DPDK) '
  810. 'provides high-performance packet processing '
  811. 'libraries and user space drivers.',
  812. 'type': 'checkbox',
  813. 'weight': 10,
  814. 'restrictions': [{
  815. "settings:common.libvirt_type.value != 'kvm'":
  816. "Only KVM hypervisor works with DPDK"
  817. }]
  818. },
  819. 'metadata': {
  820. 'label': 'DPDK',
  821. 'weight': 40,
  822. 'restrictions': [{
  823. 'condition':
  824. "not ('experimental' in version:feature_groups)",
  825. 'action': "hide"
  826. }]
  827. }
  828. },
  829. 'lacp': {
  830. 'metadata': {
  831. 'weight': 50,
  832. 'label': 'Lacp'
  833. },
  834. 'value': {
  835. 'weight': 10,
  836. 'type': 'select',
  837. 'value': '',
  838. 'label': 'Lacp'
  839. }
  840. },
  841. 'lacp_rate': {
  842. 'metadata': {
  843. 'weight': 60,
  844. 'label': 'Lacp rate'
  845. },
  846. 'value': {
  847. 'weight': 10,
  848. 'type': 'select',
  849. 'value': '',
  850. 'label': 'Lacp rate'
  851. }
  852. },
  853. 'xmit_hash_policy': {
  854. 'metadata': {
  855. 'weight': 70,
  856. 'label': 'Xmit hash policy'
  857. },
  858. 'value': {
  859. 'weight': 10,
  860. 'type': 'select',
  861. 'value': '',
  862. 'label': 'Xmit hash policy'
  863. }
  864. },
  865. 'plugin_a_with_bond_attributes': {
  866. 'metadata': {
  867. 'label': 'Test base plugin',
  868. 'class': 'plugin'
  869. },
  870. 'plugin_name_text': {
  871. 'value': 'value',
  872. 'type': 'text',
  873. 'description': 'Some description',
  874. 'weight': 25,
  875. 'label': 'label'
  876. }
  877. }
  878. }
  879. def setUp(self):
  880. super(TestBondAttributesDefaultsHandler, self).setUp()
  881. self.cluster = self.env.create(
  882. release_kwargs={
  883. 'name': uuid.uuid4().get_hex(),
  884. 'version': 'newton-10.0',
  885. 'operating_system': 'Ubuntu',
  886. 'modes': [CLUSTER_MODES.ha_compact]},
  887. nodes_kwargs=[{'roles': ['controller']}])
  888. self.node = self.env.nodes[0]
  889. self.env.create_plugin(
  890. name='plugin_a_with_bond_attributes',
  891. package_version='5.0.0',
  892. cluster=self.cluster,
  893. bond_attributes_metadata=self.env.get_default_plugin_bond_config())
  894. def test_get_bond_default_attributes(self):
  895. resp = self.app.get(
  896. reverse(
  897. "NodeBondAttributesDefaultsHandler",
  898. kwargs={"node_id": self.node.id}),
  899. headers=self.default_headers)
  900. self.assertEqual(resp.status_code, 200)
  901. self.assertDictEqual(self.EXPECTED_ATTRIBUTES, resp.json_body)