OpenStack Compute (Nova)
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.

netutils.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. # Copyright 2010 United States Government as represented by the
  2. # Administrator of the National Aeronautics and Space Administration.
  3. # All Rights Reserved.
  4. # Copyright (c) 2010 Citrix Systems, Inc.
  5. # Copyright 2013 IBM Corp.
  6. #
  7. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  8. # not use this file except in compliance with the License. You may obtain
  9. # a copy of the License at
  10. #
  11. # http://www.apache.org/licenses/LICENSE-2.0
  12. #
  13. # Unless required by applicable law or agreed to in writing, software
  14. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  15. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  16. # License for the specific language governing permissions and limitations
  17. # under the License.
  18. """Network-related utilities for supporting libvirt connection code."""
  19. import os
  20. import jinja2
  21. import netaddr
  22. from oslo_utils import strutils
  23. import nova.conf
  24. from nova.network import model
  25. CONF = nova.conf.CONF
  26. def get_net_and_mask(cidr):
  27. net = netaddr.IPNetwork(cidr)
  28. return str(net.ip), str(net.netmask)
  29. def get_net_and_prefixlen(cidr):
  30. net = netaddr.IPNetwork(cidr)
  31. return str(net.ip), str(net._prefixlen)
  32. def get_ip_version(cidr):
  33. net = netaddr.IPNetwork(cidr)
  34. return int(net.version)
  35. def _get_first_network(network, version):
  36. # Using a generator expression with a next() call for the first element
  37. # of a list since we don't want to evaluate the whole list as we can
  38. # have a lot of subnets
  39. try:
  40. return next(i for i in network['subnets']
  41. if i['version'] == version)
  42. except StopIteration:
  43. pass
  44. def get_injected_network_template(network_info, template=None,
  45. libvirt_virt_type=None):
  46. """Returns a rendered network template for the given network_info.
  47. :param network_info:
  48. :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
  49. :param template: Path to the interfaces template file.
  50. :param libvirt_virt_type: The Libvirt `virt_type`, will be `None` for
  51. other hypervisors..
  52. """
  53. if not template:
  54. template = CONF.injected_network_template
  55. if not (network_info and template):
  56. return
  57. nets = []
  58. ifc_num = -1
  59. ipv6_is_available = False
  60. for vif in network_info:
  61. if not vif['network'] or not vif['network']['subnets']:
  62. continue
  63. network = vif['network']
  64. # NOTE(bnemec): The template only supports a single subnet per
  65. # interface and I'm not sure how/if that can be fixed, so this
  66. # code only takes the first subnet of the appropriate type.
  67. subnet_v4 = _get_first_network(network, 4)
  68. subnet_v6 = _get_first_network(network, 6)
  69. ifc_num += 1
  70. if not network.get_meta('injected'):
  71. continue
  72. hwaddress = vif.get('address')
  73. address = None
  74. netmask = None
  75. gateway = ''
  76. broadcast = None
  77. dns = None
  78. routes = []
  79. if subnet_v4:
  80. if subnet_v4.get_meta('dhcp_server') is not None:
  81. continue
  82. if subnet_v4['ips']:
  83. ip = subnet_v4['ips'][0]
  84. address = ip['address']
  85. netmask = model.get_netmask(ip, subnet_v4)
  86. if subnet_v4['gateway']:
  87. gateway = subnet_v4['gateway']['address']
  88. broadcast = str(subnet_v4.as_netaddr().broadcast)
  89. dns = ' '.join([i['address'] for i in subnet_v4['dns']])
  90. for route_ref in subnet_v4['routes']:
  91. (net, mask) = get_net_and_mask(route_ref['cidr'])
  92. route = {'gateway': str(route_ref['gateway']['address']),
  93. 'cidr': str(route_ref['cidr']),
  94. 'network': net,
  95. 'netmask': mask}
  96. routes.append(route)
  97. address_v6 = None
  98. gateway_v6 = ''
  99. netmask_v6 = None
  100. dns_v6 = None
  101. if subnet_v6:
  102. if subnet_v6.get_meta('dhcp_server') is not None:
  103. continue
  104. if subnet_v6['ips']:
  105. ipv6_is_available = True
  106. ip_v6 = subnet_v6['ips'][0]
  107. address_v6 = ip_v6['address']
  108. netmask_v6 = model.get_netmask(ip_v6, subnet_v6)
  109. if subnet_v6['gateway']:
  110. gateway_v6 = subnet_v6['gateway']['address']
  111. dns_v6 = ' '.join([i['address'] for i in subnet_v6['dns']])
  112. net_info = {'name': 'eth%d' % ifc_num,
  113. 'hwaddress': hwaddress,
  114. 'address': address,
  115. 'netmask': netmask,
  116. 'gateway': gateway,
  117. 'broadcast': broadcast,
  118. 'dns': dns,
  119. 'routes': routes,
  120. 'address_v6': address_v6,
  121. 'gateway_v6': gateway_v6,
  122. 'netmask_v6': netmask_v6,
  123. 'dns_v6': dns_v6,
  124. }
  125. nets.append(net_info)
  126. if not nets:
  127. return
  128. tmpl_path, tmpl_file = os.path.split(template)
  129. env = jinja2.Environment( # nosec
  130. loader=jinja2.FileSystemLoader(tmpl_path), # nosec
  131. trim_blocks=True)
  132. template = env.get_template(tmpl_file)
  133. return template.render({'interfaces': nets,
  134. 'use_ipv6': ipv6_is_available,
  135. 'libvirt_virt_type': libvirt_virt_type})
  136. def get_network_metadata(network_info):
  137. """Gets a more complete representation of the instance network information.
  138. This data is exposed as network_data.json in the metadata service and
  139. the config drive.
  140. :param network_info: `nova.network.models.NetworkInfo` object describing
  141. the network metadata.
  142. """
  143. if not network_info:
  144. return
  145. # IPv4 or IPv6 networks
  146. nets = []
  147. # VIFs, physical NICs, or VLANs. Physical NICs will have type 'phy'.
  148. links = []
  149. # Non-network bound services, such as DNS
  150. services = []
  151. ifc_num = -1
  152. net_num = -1
  153. for vif in network_info:
  154. if not vif.get('network') or not vif['network'].get('subnets'):
  155. continue
  156. network = vif['network']
  157. # NOTE(JoshNang) currently, only supports the first IPv4 and first
  158. # IPv6 subnet on network, a limitation that also exists in the
  159. # network template.
  160. subnet_v4 = _get_first_network(network, 4)
  161. subnet_v6 = _get_first_network(network, 6)
  162. ifc_num += 1
  163. link = None
  164. # Get the VIF or physical NIC data
  165. if subnet_v4 or subnet_v6:
  166. link = _get_eth_link(vif, ifc_num)
  167. links.append(link)
  168. # Add IPv4 and IPv6 networks if they exist
  169. if subnet_v4 and subnet_v4.get('ips'):
  170. net_num += 1
  171. nets.append(_get_nets(vif, subnet_v4, 4, net_num, link['id']))
  172. services += [dns for dns in _get_dns_services(subnet_v4)
  173. if dns not in services]
  174. if subnet_v6 and subnet_v6.get('ips'):
  175. net_num += 1
  176. nets.append(_get_nets(vif, subnet_v6, 6, net_num, link['id']))
  177. services += [dns for dns in _get_dns_services(subnet_v6)
  178. if dns not in services]
  179. return {
  180. "links": links,
  181. "networks": nets,
  182. "services": services
  183. }
  184. def get_ec2_ip_info(network_info):
  185. if not isinstance(network_info, model.NetworkInfo):
  186. network_info = model.NetworkInfo.hydrate(network_info)
  187. ip_info = {}
  188. fixed_ips = network_info.fixed_ips()
  189. ip_info['fixed_ips'] = [
  190. ip['address'] for ip in fixed_ips if ip['version'] == 4]
  191. ip_info['fixed_ip6s'] = [
  192. ip['address'] for ip in fixed_ips if ip['version'] == 6]
  193. ip_info['floating_ips'] = [
  194. ip['address'] for ip in network_info.floating_ips()]
  195. return ip_info
  196. def _get_eth_link(vif, ifc_num):
  197. """Get a VIF or physical NIC representation.
  198. :param vif: Neutron VIF
  199. :param ifc_num: Interface index for generating name if the VIF's
  200. 'devname' isn't defined.
  201. :return: A dict with 'id', 'vif_id', 'type', 'mtu' and
  202. 'ethernet_mac_address' as keys
  203. """
  204. link_id = vif.get('devname')
  205. if not link_id:
  206. link_id = 'interface%d' % ifc_num
  207. # Use 'phy' for physical links. Ethernet can be confusing
  208. if vif.get('type') in model.LEGACY_EXPOSED_VIF_TYPES:
  209. nic_type = vif.get('type')
  210. else:
  211. nic_type = 'phy'
  212. link = {
  213. 'id': link_id,
  214. 'vif_id': vif['id'],
  215. 'type': nic_type,
  216. 'mtu': vif['network']['meta'].get('mtu'),
  217. 'ethernet_mac_address': vif.get('address'),
  218. }
  219. return link
  220. def _get_nets(vif, subnet, version, net_num, link_id):
  221. """Get networks for the given VIF and subnet
  222. :param vif: Neutron VIF
  223. :param subnet: Neutron subnet
  224. :param version: IP version as an int, either '4' or '6'
  225. :param net_num: Network index for generating name of each network
  226. :param link_id: Arbitrary identifier for the link the networks are
  227. attached to
  228. """
  229. net_type = ''
  230. if subnet.get_meta('ipv6_address_mode') is not None:
  231. net_type = '_%s' % subnet.get_meta('ipv6_address_mode')
  232. elif subnet.get_meta('dhcp_server') is not None:
  233. net_info = {
  234. 'id': 'network%d' % net_num,
  235. 'type': 'ipv%d_dhcp' % version,
  236. 'link': link_id,
  237. 'network_id': vif['network']['id']
  238. }
  239. return net_info
  240. ip = subnet['ips'][0]
  241. address = ip['address']
  242. if version == 4:
  243. netmask = model.get_netmask(ip, subnet)
  244. elif version == 6:
  245. netmask = str(subnet.as_netaddr().netmask)
  246. net_info = {
  247. 'id': 'network%d' % net_num,
  248. 'type': 'ipv%d%s' % (version, net_type),
  249. 'link': link_id,
  250. 'ip_address': address,
  251. 'netmask': netmask,
  252. 'routes': _get_default_route(version, subnet),
  253. 'network_id': vif['network']['id']
  254. }
  255. # Add any additional routes beyond the default route
  256. for route in subnet['routes']:
  257. route_addr = netaddr.IPNetwork(route['cidr'])
  258. new_route = {
  259. 'network': str(route_addr.network),
  260. 'netmask': str(route_addr.netmask),
  261. 'gateway': route['gateway']['address']
  262. }
  263. net_info['routes'].append(new_route)
  264. net_info['services'] = _get_dns_services(subnet)
  265. return net_info
  266. def _get_default_route(version, subnet):
  267. """Get a default route for a network
  268. :param version: IP version as an int, either '4' or '6'
  269. :param subnet: Neutron subnet
  270. """
  271. if subnet.get('gateway') and subnet['gateway'].get('address'):
  272. gateway = subnet['gateway']['address']
  273. else:
  274. return []
  275. if version == 4:
  276. return [{
  277. 'network': '0.0.0.0',
  278. 'netmask': '0.0.0.0',
  279. 'gateway': gateway
  280. }]
  281. elif version == 6:
  282. return [{
  283. 'network': '::',
  284. 'netmask': '::',
  285. 'gateway': gateway
  286. }]
  287. def _get_dns_services(subnet):
  288. """Get the DNS servers for the subnet."""
  289. services = []
  290. if not subnet.get('dns'):
  291. return services
  292. return [{'type': 'dns', 'address': ip.get('address')}
  293. for ip in subnet['dns']]
  294. def get_cached_vifs_with_vlan(network_info):
  295. """Generates a dict from a list of VIFs that has a vlan tag, with
  296. MAC, VLAN as a key, value.
  297. """
  298. if network_info is None:
  299. return {}
  300. return {vif['address']: vif['details']['vlan'] for vif in network_info
  301. if vif.get('details', {}).get('vlan')}
  302. def get_cached_vifs_with_trusted(network_info):
  303. """Generates a dict from a list of VIFs that trusted, MAC as key"""
  304. if network_info is None:
  305. return {}
  306. return {vif['address']: strutils.bool_from_string(
  307. vif['profile'].get('trusted', 'False')) for vif in network_info
  308. if vif.get('profile')}