Fuel tests
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_net_templates_base.py 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. # Copyright 2015 Mirantis, Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  4. # not use this file except in compliance with the License. You may obtain
  5. # a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  11. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. # License for the specific language governing permissions and limitations
  13. # under the License.
  14. import netaddr
  15. from proboscis.asserts import assert_equal
  16. from proboscis.asserts import assert_true
  17. from proboscis.asserts import fail
  18. from core.helpers.log_helpers import logwrap
  19. from fuelweb_test import logger
  20. from fuelweb_test.helpers.utils import get_ip_listen_stats
  21. from fuelweb_test.tests.base_test_case import TestBasic
  22. class TestNetworkTemplatesBase(TestBasic):
  23. """Base class to store all utility methods for network templates tests."""
  24. @logwrap
  25. def generate_networks_for_template(self, template, ip_nets,
  26. ip_prefixlen):
  27. """Slice network to subnets for template.
  28. Generate networks from network template and ip_nets descriptions
  29. for node groups and value to slice that descriptions. ip_nets is a
  30. dict with key named as nodegroup and strings values for with
  31. description of network for that nodegroup in format '127.0.0.1/24'
  32. to be sliced in pieces for networks. ip_prefixlen - the amount the
  33. network prefix length should be sliced by. 24 will create networks
  34. '127.0.0.1/24' from network '127.0.0.1/16'.
  35. :param template: Yaml template with network assignments on interfaces.
  36. :param ip_nets: Dict with network descriptions.
  37. :param ip_prefixlen: Integer for slicing network prefix.
  38. :return: Data to be used to assign networks to nodes
  39. """
  40. networks_data = []
  41. nodegroups = self.fuel_web.client.get_nodegroups()
  42. for nodegroup, section in template['adv_net_template'].items():
  43. assert_true(any(n['name'] == nodegroup for n in nodegroups),
  44. 'Network templates contains settings for Node Group '
  45. '"{0}", which does not exist!'.format(nodegroup))
  46. group_id = [n['id'] for n in nodegroups if
  47. n['name'] == nodegroup][0]
  48. ip_network = netaddr.IPNetwork(str(ip_nets[nodegroup]))
  49. ip_subnets = list(ip_network.subnet(int(ip_prefixlen)))
  50. for network in section['network_assignments']:
  51. ip_subnet = ip_subnets.pop()
  52. networks_data.append(
  53. {
  54. 'name': network,
  55. 'cidr': str(ip_subnet),
  56. 'group_id': group_id,
  57. 'gateway': None,
  58. 'meta': {
  59. "notation": "ip_ranges",
  60. "render_type": None,
  61. "map_priority": 0,
  62. "configurable": True,
  63. "unmovable": False,
  64. "use_gateway": False,
  65. "render_addr_mask": None,
  66. 'ip_range': [str(ip_subnet[1]), str(ip_subnet[-2])]
  67. }
  68. }
  69. )
  70. return networks_data
  71. @logwrap
  72. def map_group_by_iface_and_network(self, template):
  73. """ Map groip id, iface name and network name
  74. :param template: Yaml template with network assignments on interfaces.
  75. :return: Data to be used for check of ip assignment
  76. """
  77. mapped_data = {}
  78. nodegroups = self.fuel_web.client.get_nodegroups()
  79. for nodegroup, section in template['adv_net_template'].items():
  80. networks = [(n, section['network_assignments'][n]['ep'])
  81. for n in section['network_assignments']]
  82. assert_true(any(n['name'] == nodegroup for n in nodegroups),
  83. 'Network templates contains settings for Node Group '
  84. '"{0}", which does not exist!'.format(nodegroup))
  85. group_id = [n['id'] for n in nodegroups if
  86. n['name'] == nodegroup][0]
  87. mapped_data[group_id] = dict(networks)
  88. return mapped_data
  89. @staticmethod
  90. @logwrap
  91. def get_template_ep_for_role(template, role, nodegroup='default',
  92. skip_net_roles=None):
  93. if skip_net_roles is None:
  94. skip_net_roles = set()
  95. tmpl = template['adv_net_template'][nodegroup]
  96. endpoints = set()
  97. networks = set()
  98. network_types = tmpl['templates_for_node_role'][role]
  99. for network_type in network_types:
  100. endpoints.update(tmpl['network_scheme'][network_type]['endpoints'])
  101. for scheme_type in tmpl['network_scheme']:
  102. for net_role in tmpl['network_scheme'][scheme_type]['roles']:
  103. if net_role in skip_net_roles:
  104. endpoints.discard(
  105. tmpl['network_scheme'][scheme_type]['roles'][net_role])
  106. for net in tmpl['network_assignments']:
  107. if tmpl['network_assignments'][net]['ep'] in endpoints:
  108. networks.add(net)
  109. return networks
  110. @staticmethod
  111. @logwrap
  112. def get_template_netroles_for_role(template, role, nodegroup='default'):
  113. tmpl = template['adv_net_template'][nodegroup]
  114. netroles = dict()
  115. network_types = tmpl['templates_for_node_role'][role]
  116. for network_type in network_types:
  117. netroles.update(tmpl['network_scheme'][network_type]['roles'])
  118. return netroles
  119. @logwrap
  120. def create_custom_networks(self, networks, existing_networks):
  121. for custom_net in networks:
  122. if not any([custom_net['name'] == n['name'] and
  123. # ID of 'fuelweb_admin' default network group is None
  124. custom_net['group_id'] == (n['group_id'] or 1)
  125. for n in existing_networks]):
  126. self.fuel_web.client.add_network_group(custom_net)
  127. else:
  128. # Copying settings from existing network
  129. net = [n for n in existing_networks if
  130. custom_net['name'] == n['name'] and
  131. custom_net['group_id'] == (n['group_id'] or 1)][0]
  132. custom_net['cidr'] = net['cidr']
  133. custom_net['meta'] = net['meta']
  134. custom_net['gateway'] = net['gateway']
  135. return networks
  136. @staticmethod
  137. @logwrap
  138. def get_interface_ips(remote, iface_name):
  139. cmd = ("set -o pipefail; "
  140. "ip -o -4 address show dev {0} | sed -rn "
  141. "'s/^.*\sinet\s+([0-9\.]+\/[0-9]{{1,2}})\s.*$/\\1/p'").format(
  142. iface_name)
  143. result = remote.execute(cmd)
  144. logger.debug("Checking interface IP result: {0}".format(result))
  145. assert_equal(result['exit_code'], 0,
  146. "Device {0} not found on remote node!".format(iface_name))
  147. return [line.strip() for line in result['stdout']]
  148. @logwrap
  149. def check_interface_ip_exists(self, remote, iface_name, cidr):
  150. raw_addresses = self.get_interface_ips(remote, iface_name)
  151. raw_ips = [raw_addr.split('/')[0] for raw_addr in raw_addresses]
  152. try:
  153. ips = [netaddr.IPAddress(str(raw_ip)) for raw_ip in raw_ips]
  154. except ValueError:
  155. fail('Device {0} on remote node does not have a valid '
  156. 'IPv4 address assigned!'.format(iface_name))
  157. return
  158. actual_networks = [netaddr.IPNetwork(str(raw_addr)) for
  159. raw_addr in raw_addresses]
  160. network = netaddr.IPNetwork(str(cidr))
  161. assert_true(network in actual_networks,
  162. 'Network(s) on {0} device differs than {1}: {2}'.format(
  163. iface_name, cidr, raw_addresses))
  164. assert_true(any(ip in network for ip in ips),
  165. 'IP address on {0} device is not from {1} network!'.format(
  166. iface_name, cidr))
  167. @logwrap
  168. def check_ipconfig_for_template(self, cluster_id, network_template,
  169. networks):
  170. logger.info("Checking that IP addresses configuration on nodes "
  171. "corresponds to used networking template...")
  172. # Network for Neutron is configured in namespaces (l3/dhcp agents)
  173. # and a bridge for it doesn't have IP, so skipping it for now
  174. skip_roles = {'neutron/private'}
  175. mapped_data = self.map_group_by_iface_and_network(network_template)
  176. for node in self.fuel_web.client.list_cluster_nodes(cluster_id):
  177. node_networks = set()
  178. node_group_name = [ng['name'] for ng in
  179. self.fuel_web.client.get_nodegroups()
  180. if ng['id'] == node['group_id']][0]
  181. for role in node['roles']:
  182. node_networks.update(
  183. self.get_template_ep_for_role(template=network_template,
  184. role=role,
  185. nodegroup=node_group_name,
  186. skip_net_roles=skip_roles))
  187. with self.env.d_env.get_ssh_to_remote(node['ip']) as remote:
  188. for network in networks:
  189. if network['name'] not in node_networks or \
  190. network['group_id'] != node['group_id']:
  191. continue
  192. logger.debug(
  193. 'Checking interface "{0}" for IP network '
  194. '"{1}" on "{2}"'.format(
  195. mapped_data[node['group_id']][network['name']],
  196. network['cidr'],
  197. node['hostname']))
  198. self.check_interface_ip_exists(
  199. remote,
  200. mapped_data[node['group_id']][network['name']],
  201. network['cidr'])
  202. @staticmethod
  203. @logwrap
  204. def get_port_listen_ips(listen_stats, port):
  205. ips = set()
  206. for socket in listen_stats:
  207. hexip, hexport = socket.split(':')
  208. if int(port) == int(hexport, 16):
  209. ips.add('.'.join([str(int(hexip[n:n + 2], 16))
  210. for n in range(0, len(hexip), 2)][::-1]))
  211. return ips
  212. @logwrap
  213. def check_services_networks(self, cluster_id, net_template):
  214. logger.info("Checking that OpenStack services on nodes are listening "
  215. "on IP networks according to used networking template...")
  216. services = [
  217. {
  218. 'name': 'keystone_api',
  219. 'network_roles': ['keystone/api'],
  220. 'tcp_ports': [5000, 35357],
  221. 'udp_ports': [],
  222. # check is disabled because access to API is restricted
  223. # using firewall (see LP#1489057,
  224. # https://review.openstack.org/#/c/218853/)
  225. 'enabled': False
  226. },
  227. {
  228. 'name': 'nova-api',
  229. 'network_roles': ['nova/api'],
  230. 'tcp_ports': [8773, 8774],
  231. 'udp_ports': [],
  232. 'enabled': True
  233. },
  234. {
  235. 'name': 'neutron-api',
  236. 'network_roles': ['neutron/api'],
  237. 'tcp_ports': [9696],
  238. 'udp_ports': [],
  239. 'enabled': True
  240. },
  241. {
  242. 'name': 'swift-api',
  243. 'network_roles': ['swift/api'],
  244. 'tcp_ports': [8080],
  245. 'udp_ports': [],
  246. 'enabled': True
  247. },
  248. {
  249. 'name': 'swift-replication',
  250. 'network_roles': ['swift/replication'],
  251. 'tcp_ports': [6000, 6001, 6002],
  252. 'udp_ports': [],
  253. 'enabled': True
  254. },
  255. {
  256. 'name': 'sahara-api',
  257. 'network_roles': ['sahara/api'],
  258. 'tcp_ports': [8386],
  259. 'udp_ports': [],
  260. 'enabled': True
  261. },
  262. {
  263. 'name': 'ceilometer-api',
  264. 'network_roles': ['ceilometer/api'],
  265. 'tcp_ports': [8777],
  266. 'udp_ports': [],
  267. 'enabled': True
  268. },
  269. {
  270. 'name': 'cinder-api',
  271. 'network_roles': ['cinder/api'],
  272. 'tcp_ports': [8776],
  273. 'udp_ports': [],
  274. 'enabled': True
  275. },
  276. {
  277. 'name': 'glance-api',
  278. 'network_roles': ['glance/api'],
  279. 'tcp_ports': [5509],
  280. 'udp_ports': [],
  281. 'enabled': True
  282. },
  283. {
  284. 'name': 'heat-api',
  285. 'network_roles': ['heat/api'],
  286. 'tcp_ports': [8000, 8003, 8004],
  287. 'udp_ports': [],
  288. 'enabled': True
  289. },
  290. {
  291. 'name': 'murano-api',
  292. 'network_roles': ['murano/api'],
  293. 'tcp_ports': [8082],
  294. 'udp_ports': [],
  295. 'enabled': True
  296. },
  297. {
  298. 'name': 'ceph',
  299. 'network_roles': ['ceph/replication', 'ceph/public'],
  300. 'tcp_ports': [6804, 6805, 6806, 6807],
  301. 'udp_ports': [],
  302. 'enabled': True
  303. },
  304. {
  305. 'name': 'ceph-radosgw',
  306. 'network_roles': ['ceph/radosgw'],
  307. 'tcp_ports': [7480],
  308. 'udp_ports': [],
  309. 'enabled': True
  310. },
  311. {
  312. 'name': 'mongo-db',
  313. 'network_roles': ['mongo/db'],
  314. 'tcp_ports': [27017],
  315. 'udp_ports': [],
  316. 'enabled': True
  317. },
  318. {
  319. 'name': 'mgmt-messaging',
  320. 'network_roles': ['mgmt/messaging'],
  321. 'tcp_ports': [5673],
  322. 'udp_ports': [],
  323. 'enabled': True
  324. },
  325. {
  326. 'name': 'mgmt-corosync',
  327. 'network_roles': ['mgmt/corosync'],
  328. 'tcp_ports': [],
  329. 'udp_ports': [5405],
  330. 'enabled': True
  331. },
  332. {
  333. 'name': 'mgmt-memcache',
  334. 'network_roles': ['mgmt/memcache'],
  335. 'tcp_ports': [11211],
  336. 'udp_ports': [11211],
  337. 'enabled': True
  338. },
  339. {
  340. 'name': 'mgmt-database',
  341. 'network_roles': ['mgmt/database'],
  342. 'tcp_ports': [3307, 4567],
  343. 'udp_ports': [],
  344. 'enabled': True
  345. },
  346. {
  347. 'name': 'cinder-iscsi',
  348. 'network_roles': ['cinder/iscsi'],
  349. 'tcp_ports': [3260],
  350. 'udp_ports': [],
  351. # ISCSI daemon is started automatically because cinder-volume
  352. # package installs it by dependencies (LP#1491518)
  353. 'enabled': False
  354. },
  355. ]
  356. check_passed = True
  357. for node in self.fuel_web.client.list_cluster_nodes(cluster_id):
  358. node_netroles = dict()
  359. node_group_name = [ng['name'] for ng in
  360. self.fuel_web.client.get_nodegroups()
  361. if ng['id'] == node['group_id']][0]
  362. for role in node['roles']:
  363. node_netroles.update(self.get_template_netroles_for_role(
  364. template=net_template,
  365. role=role,
  366. nodegroup=node_group_name))
  367. with self.env.d_env.get_ssh_to_remote(node['ip']) as remote:
  368. tcp_listen_stats = get_ip_listen_stats(remote, 'tcp')
  369. udp_listen_stats = get_ip_listen_stats(remote, 'udp')
  370. for service in services:
  371. if any(net_role not in node_netroles.keys()
  372. for net_role in service['network_roles']) \
  373. or not service['enabled']:
  374. continue
  375. ips = set()
  376. for service_net_role in service['network_roles']:
  377. iface_name = node_netroles[service_net_role]
  378. ips.update([cidr.split('/')[0] for cidr in
  379. self.get_interface_ips(remote,
  380. iface_name)])
  381. for port in service['tcp_ports']:
  382. listen_ips = self.get_port_listen_ips(tcp_listen_stats,
  383. port)
  384. if not listen_ips:
  385. logger.debug('Service "{0}" is not found on '
  386. '"{1}".'.format(service['name'],
  387. node['hostname']))
  388. continue
  389. if any(lip not in ips for lip in listen_ips):
  390. check_passed = False
  391. logger.error('Service "{0}" (port {4}/tcp) is '
  392. 'listening on wrong IP address(es) '
  393. 'on "{1}": expected "{2}", got '
  394. '"{3}"!'.format(service['name'],
  395. node['hostname'],
  396. ips,
  397. listen_ips,
  398. port))
  399. for port in service['udp_ports']:
  400. listen_ips = self.get_port_listen_ips(udp_listen_stats,
  401. port)
  402. if not listen_ips:
  403. logger.debug('Service "{0}" is not found on '
  404. '"{1}".'.format(service['name'],
  405. node['hostname']))
  406. continue
  407. if any(lip not in ips for lip in listen_ips):
  408. check_passed = False
  409. logger.error('Service "{0}" (port {4}/udp) is '
  410. 'listening on wrong IP address(es) '
  411. 'on "{1}": expected "{2}", got '
  412. '"{3}"!'.format(service['name'],
  413. node['hostname'],
  414. ips,
  415. listen_ips,
  416. port))
  417. assert_true(check_passed,
  418. 'Some services are listening on wrong IPs! '
  419. 'Please check logs for details!')
  420. @staticmethod
  421. def get_modified_ranges(net_dict, net_name, group_id):
  422. for net in net_dict['networks']:
  423. if net_name in net['name'] and net['group_id'] == group_id:
  424. cidr = net['cidr']
  425. sliced_list = list(netaddr.IPNetwork(str(cidr)))[5:-5]
  426. return [str(sliced_list[0]), str(sliced_list[-1])]
  427. @staticmethod
  428. def change_default_admin_range(networks, number_excluded_ips):
  429. """Change IP range for admin network by excluding N of first addresses
  430. from default range
  431. :param networks: list, environment networks configuration
  432. :param number_excluded_ips: int, number of IPs to remove from range
  433. """
  434. default_admin_network = [n for n in networks
  435. if (n['name'] == "fuelweb_admin" and
  436. n['group_id'] is None)]
  437. assert_true(len(default_admin_network) == 1,
  438. "Default 'admin/pxe' network not found "
  439. "in cluster network configuration!")
  440. default_admin_range = [netaddr.IPAddress(str(ip)) for ip
  441. in default_admin_network[0]["ip_ranges"][0]]
  442. new_admin_range = [default_admin_range[0] + number_excluded_ips,
  443. default_admin_range[1]]
  444. default_admin_network[0]["ip_ranges"][0] = [str(ip)
  445. for ip in new_admin_range]
  446. return default_admin_network[0]["ip_ranges"][0]
  447. @staticmethod
  448. def is_ip_in_range(ip_addr, ip_range_start, ip_range_end):
  449. return netaddr.IPAddress(str(ip_addr)) in netaddr.iter_iprange(
  450. str(ip_range_start), str(ip_range_end))
  451. @staticmethod
  452. def is_update_dnsmasq_running(tasks):
  453. for task in tasks:
  454. if task['name'] == "update_dnsmasq" and \
  455. task["status"] == "running":
  456. return True
  457. return False
  458. @staticmethod
  459. def update_network_ranges(net_data, update_data):
  460. for net in net_data['networks']:
  461. for group in update_data:
  462. for net_name in update_data[group]:
  463. if net_name in net['name'] and net['group_id'] == group:
  464. net['ip_ranges'] = update_data[group][net_name]
  465. net['meta']['notation'] = 'ip_ranges'
  466. return net_data
  467. @staticmethod
  468. def get_ranges(net_data, net_name, group_id):
  469. return [net['ip_ranges'] for net in net_data['networks'] if
  470. net_name in net['name'] and group_id == net['group_id']][0]