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

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