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

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