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 14KB

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