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

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