Juju Charm - PLUMgrid Gateway
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.

pg_gw_utils.py 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. # Copyright (c) 2015, PLUMgrid Inc, http://plumgrid.com
  2. # This file contains functions used by the hooks to deploy PLUMgrid Gateway.
  3. import pg_gw_context
  4. import subprocess
  5. import time
  6. import os
  7. import json
  8. from collections import OrderedDict
  9. from socket import gethostname as get_unit_hostname
  10. from copy import deepcopy
  11. from charmhelpers.contrib.openstack.neutron import neutron_plugin_attribute
  12. from charmhelpers.contrib.storage.linux.ceph import modprobe
  13. from charmhelpers.contrib.openstack import templating
  14. from charmhelpers.core.hookenv import (
  15. log,
  16. config,
  17. unit_get,
  18. status_set
  19. )
  20. from charmhelpers.contrib.network.ip import (
  21. get_iface_from_addr,
  22. get_bridges,
  23. get_bridge_nics,
  24. is_address_in_network,
  25. get_iface_addr
  26. )
  27. from charmhelpers.core.host import (
  28. write_file,
  29. service_start,
  30. service_stop,
  31. service_running,
  32. path_hash,
  33. set_nic_mtu
  34. )
  35. from charmhelpers.fetch import (
  36. apt_cache,
  37. apt_install
  38. )
  39. from charmhelpers.contrib.openstack.utils import (
  40. os_release,
  41. )
  42. LXC_CONF = "/etc/libvirt/lxc.conf"
  43. TEMPLATES = 'templates/'
  44. PG_LXC_DATA_PATH = '/var/lib/libvirt/filesystems/plumgrid-data'
  45. PG_CONF = '%s/conf/pg/plumgrid.conf' % PG_LXC_DATA_PATH
  46. PG_HN_CONF = '%s/conf/etc/hostname' % PG_LXC_DATA_PATH
  47. PG_HS_CONF = '%s/conf/etc/hosts' % PG_LXC_DATA_PATH
  48. PG_IFCS_CONF = '%s/conf/pg/ifcs.conf' % PG_LXC_DATA_PATH
  49. OPS_CONF = '%s/conf/etc/00-pg.conf' % PG_LXC_DATA_PATH
  50. AUTH_KEY_PATH = '%s/root/.ssh/authorized_keys' % PG_LXC_DATA_PATH
  51. IFC_LIST_GW = '/var/run/plumgrid/lxc/ifc_list_gateway'
  52. SUDOERS_CONF = '/etc/sudoers.d/ifc_ctl_sudoers'
  53. BASE_RESOURCE_MAP = OrderedDict([
  54. (PG_CONF, {
  55. 'services': ['plumgrid'],
  56. 'contexts': [pg_gw_context.PGGwContext()],
  57. }),
  58. (PG_HN_CONF, {
  59. 'services': ['plumgrid'],
  60. 'contexts': [pg_gw_context.PGGwContext()],
  61. }),
  62. (PG_HS_CONF, {
  63. 'services': ['plumgrid'],
  64. 'contexts': [pg_gw_context.PGGwContext()],
  65. }),
  66. (OPS_CONF, {
  67. 'services': ['plumgrid'],
  68. 'contexts': [pg_gw_context.PGGwContext()],
  69. }),
  70. (PG_IFCS_CONF, {
  71. 'services': [],
  72. 'contexts': [pg_gw_context.PGGwContext()],
  73. }),
  74. ])
  75. def determine_packages():
  76. '''
  77. Returns list of packages required by PLUMgrid Gateway as specified
  78. in the neutron_plugins dictionary in charmhelpers.
  79. '''
  80. pkgs = []
  81. tag = 'latest'
  82. for pkg in neutron_plugin_attribute('plumgrid', 'packages', 'neutron'):
  83. if 'plumgrid' in pkg:
  84. tag = config('plumgrid-build')
  85. elif pkg == 'iovisor-dkms':
  86. tag = config('iovisor-build')
  87. if tag == 'latest':
  88. pkgs.append(pkg)
  89. else:
  90. if tag in [i.ver_str for i in apt_cache()[pkg].version_list]:
  91. pkgs.append('%s=%s' % (pkg, tag))
  92. else:
  93. error_msg = \
  94. "Build version '%s' for package '%s' not available" \
  95. % (tag, pkg)
  96. raise ValueError(error_msg)
  97. return pkgs
  98. def register_configs(release=None):
  99. '''
  100. Returns an object of the Openstack Tempating Class which contains the
  101. the context required for all templates of this charm.
  102. '''
  103. release = release or os_release('nova-compute', base='kilo')
  104. configs = templating.OSConfigRenderer(templates_dir=TEMPLATES,
  105. openstack_release=release)
  106. for cfg, rscs in resource_map().iteritems():
  107. configs.register(cfg, rscs['contexts'])
  108. return configs
  109. def resource_map():
  110. '''
  111. Dynamically generate a map of resources that will be managed for a single
  112. hook execution.
  113. '''
  114. resource_map = deepcopy(BASE_RESOURCE_MAP)
  115. return resource_map
  116. def restart_map():
  117. '''
  118. Constructs a restart map based on charm config settings and relation
  119. state.
  120. '''
  121. return {cfg: rscs['services'] for cfg, rscs in resource_map().iteritems()}
  122. def ensure_files():
  123. '''
  124. Ensures PLUMgrid specific files exist before templates are written.
  125. '''
  126. write_file(SUDOERS_CONF,
  127. "\nnova ALL=(root) NOPASSWD: /opt/pg/bin/ifc_ctl_pp *\n",
  128. owner='root', group='root', perms=0o644)
  129. _exec_cmd(cmd=['rm', '-f', IFC_LIST_GW])
  130. def restart_pg():
  131. '''
  132. Stops and Starts PLUMgrid service after flushing iptables.
  133. '''
  134. stop_pg()
  135. service_start('plumgrid')
  136. time.sleep(3)
  137. if not service_running('plumgrid'):
  138. if service_running('libvirt-bin'):
  139. raise ValueError("plumgrid service couldn't be started")
  140. else:
  141. if service_start('libvirt-bin'):
  142. time.sleep(8)
  143. if not service_running('plumgrid') \
  144. and not service_start('plumgrid'):
  145. raise ValueError("plumgrid service couldn't be started")
  146. else:
  147. raise ValueError("libvirt-bin service couldn't be started")
  148. status_set('active', 'Unit is ready')
  149. def stop_pg():
  150. '''
  151. Stops PLUMgrid service.
  152. '''
  153. service_stop('plumgrid')
  154. time.sleep(2)
  155. def load_iovisor():
  156. '''
  157. Loads iovisor kernel module.
  158. '''
  159. modprobe('iovisor')
  160. def remove_iovisor():
  161. '''
  162. Removes iovisor kernel module.
  163. '''
  164. _exec_cmd(cmd=['rmmod', 'iovisor'],
  165. error_msg='Error Removing IOVisor Kernel Module')
  166. time.sleep(1)
  167. def interface_exists(interface):
  168. '''
  169. Checks if interface exists on node.
  170. '''
  171. try:
  172. subprocess.check_call(['ip', 'link', 'show', interface],
  173. stdout=open(os.devnull, 'w'),
  174. stderr=subprocess.STDOUT)
  175. except subprocess.CalledProcessError:
  176. return False
  177. return True
  178. def get_mgmt_interface():
  179. '''
  180. Returns the managment interface.
  181. '''
  182. mgmt_interface = config('mgmt-interface')
  183. if interface_exists(mgmt_interface):
  184. return mgmt_interface
  185. else:
  186. log('Provided managment interface %s does not exist'
  187. % mgmt_interface)
  188. return get_iface_from_addr(unit_get('private-address'))
  189. def fabric_interface_changed():
  190. '''
  191. Returns true if interface for node changed.
  192. '''
  193. fabric_interface = get_fabric_interface()
  194. try:
  195. with open(PG_IFCS_CONF, 'r') as ifcs:
  196. for line in ifcs:
  197. if 'fabric_core' in line:
  198. if line.split()[0] == fabric_interface:
  199. return False
  200. except IOError:
  201. return True
  202. return True
  203. def get_fabric_interface():
  204. '''
  205. Returns the fabric interface.
  206. '''
  207. fabric_interfaces = config('fabric-interfaces')
  208. if fabric_interfaces == 'MANAGEMENT':
  209. return get_mgmt_interface()
  210. else:
  211. try:
  212. all_fabric_interfaces = json.loads(fabric_interfaces)
  213. except ValueError:
  214. raise ValueError('Invalid json provided for fabric interfaces')
  215. hostname = get_unit_hostname()
  216. if hostname in all_fabric_interfaces:
  217. node_fabric_interface = all_fabric_interfaces[hostname]
  218. elif 'DEFAULT' in all_fabric_interfaces:
  219. node_fabric_interface = all_fabric_interfaces['DEFAULT']
  220. else:
  221. raise ValueError('No fabric interface provided for node')
  222. if interface_exists(node_fabric_interface):
  223. if is_address_in_network(config('os-data-network'),
  224. get_iface_addr(node_fabric_interface)[0]):
  225. return node_fabric_interface
  226. else:
  227. raise ValueError('Fabric interface not in fabric network')
  228. else:
  229. log('Provided fabric interface %s does not exist'
  230. % node_fabric_interface)
  231. raise ValueError('Provided fabric interface does not exist')
  232. return node_fabric_interface
  233. def get_gw_interfaces():
  234. '''
  235. Gateway node can have multiple interfaces. This function parses json
  236. provided in config to get all gateway interfaces for this node.
  237. '''
  238. node_interfaces = []
  239. try:
  240. all_interfaces = json.loads(config('external-interfaces'))
  241. except ValueError:
  242. raise ValueError("Invalid json provided for gateway interfaces")
  243. hostname = get_unit_hostname()
  244. if hostname in all_interfaces:
  245. node_interfaces = all_interfaces[hostname].split(',')
  246. elif 'DEFAULT' in all_interfaces:
  247. node_interfaces = all_interfaces['DEFAULT'].split(',')
  248. for interface in node_interfaces:
  249. if not interface_exists(interface):
  250. log('Provided gateway interface %s does not exist'
  251. % interface)
  252. raise ValueError('Provided gateway interface does not exist')
  253. return node_interfaces
  254. def ensure_mtu():
  255. '''
  256. Ensures required MTU of the underlying networking of the node.
  257. '''
  258. interface_mtu = config('network-device-mtu')
  259. fabric_interface = get_fabric_interface()
  260. if fabric_interface in get_bridges():
  261. attached_interfaces = get_bridge_nics(fabric_interface)
  262. for interface in attached_interfaces:
  263. set_nic_mtu(interface, interface_mtu)
  264. set_nic_mtu(fabric_interface, interface_mtu)
  265. def _exec_cmd(cmd=None, error_msg='Command exited with ERRORs', fatal=False):
  266. '''
  267. Function to execute any bash command on the node.
  268. '''
  269. if cmd is None:
  270. log("No command specified")
  271. else:
  272. if fatal:
  273. subprocess.check_call(cmd)
  274. else:
  275. try:
  276. subprocess.check_call(cmd)
  277. except subprocess.CalledProcessError:
  278. log(error_msg)
  279. def add_lcm_key():
  280. '''
  281. Adds public key of PLUMgrid-lcm to authorized keys of PLUMgrid Gateway.
  282. '''
  283. key = config('lcm-ssh-key')
  284. if key == 'null':
  285. log('lcm key not specified')
  286. return 0
  287. file_write_type = 'w+'
  288. if os.path.isfile(AUTH_KEY_PATH):
  289. file_write_type = 'a'
  290. try:
  291. fr = open(AUTH_KEY_PATH, 'r')
  292. except IOError:
  293. log('plumgrid-lxc not installed yet')
  294. return 0
  295. for line in fr:
  296. if key in line:
  297. log('key already added')
  298. return 0
  299. try:
  300. fa = open(AUTH_KEY_PATH, file_write_type)
  301. except IOError:
  302. log('Error opening file to append')
  303. return 0
  304. fa.write(key)
  305. fa.write('\n')
  306. fa.close()
  307. return 1
  308. def load_iptables():
  309. '''
  310. Loads iptables rules to allow all PLUMgrid communication.
  311. '''
  312. network = get_cidr_from_iface(get_mgmt_interface())
  313. if network:
  314. _exec_cmd(['sudo', 'iptables', '-A', 'INPUT', '-p', 'tcp',
  315. '-j', 'ACCEPT', '-s', network, '-d',
  316. network, '-m', 'state', '--state', 'NEW'])
  317. _exec_cmd(['sudo', 'iptables', '-A', 'INPUT', '-p', 'udp', '-j',
  318. 'ACCEPT', '-s', network, '-d', network,
  319. '-m', 'state', '--state', 'NEW'])
  320. apt_install('iptables-persistent')
  321. def get_cidr_from_iface(interface):
  322. '''
  323. Determines Network CIDR from interface.
  324. '''
  325. if not interface:
  326. return None
  327. apt_install('ohai')
  328. try:
  329. os_info = subprocess.check_output(['ohai', '-l', 'fatal'])
  330. except OSError:
  331. log('Unable to get operating system information')
  332. return None
  333. try:
  334. os_info_json = json.loads(os_info)
  335. except ValueError:
  336. log('Unable to determine network')
  337. return None
  338. device = os_info_json['network']['interfaces'].get(interface)
  339. if device is not None:
  340. if device.get('routes'):
  341. routes = device['routes']
  342. for net in routes:
  343. if 'scope' in net:
  344. return net.get('destination')
  345. else:
  346. return None
  347. else:
  348. return None
  349. def director_cluster_ready():
  350. dirs_count = len(pg_gw_context._pg_dir_context()['director_ips'])
  351. return True if dirs_count == 1 or dirs_count == 3 else False
  352. def restart_on_stop():
  353. """
  354. Starts plumgrid service if it is stopped
  355. """
  356. def wrap(f):
  357. def wrapped_f(*args, **kwargs):
  358. f(*args, **kwargs)
  359. if not service_running('plumgrid'):
  360. restart_pg()
  361. return wrapped_f
  362. return wrap
  363. def restart_on_change(restart_map):
  364. """
  365. Restart services based on configuration files changing
  366. """
  367. def wrap(f):
  368. def wrapped_f(*args, **kwargs):
  369. checksums = {path: path_hash(path) for path in restart_map}
  370. f(*args, **kwargs)
  371. for path in restart_map:
  372. if path_hash(path) != checksums[path]:
  373. if path == PG_IFCS_CONF:
  374. ensure_files()
  375. restart_pg()
  376. break
  377. return wrapped_f
  378. return wrap