First pass at STONITH support.
This commit is contained in:
13
config.yaml
13
config.yaml
@@ -23,3 +23,16 @@ options:
|
||||
.
|
||||
This configuration element is mandatory and the service will fail on
|
||||
install if it is not provided.
|
||||
stonith_enabled:
|
||||
type: string
|
||||
default: 'False'
|
||||
description: |
|
||||
Enable resource fencing (aka STONITH) for every node in the cluster.
|
||||
This requires MAAS credentials be provided and each node's power
|
||||
parameters are properly configured in its invenvory.
|
||||
maas_url:
|
||||
type: string
|
||||
description: MAAS API endpoint.
|
||||
maas_credentials:
|
||||
type: string
|
||||
description: MAAS credentials.
|
||||
|
||||
@@ -293,6 +293,37 @@ def configure_cluster():
|
||||
cmd = 'crm resource cleanup %s' % res_name
|
||||
pcmk.commit(cmd)
|
||||
|
||||
if utils.config_get('stonith_enabled') in ['true', 'True']:
|
||||
utils.juju_log('INFO', 'Configuring STONITH for all nodes in cluster.')
|
||||
# configure stontih resources for all nodes in cluster.
|
||||
# note: this is totally provider dependent and requires
|
||||
# access to the MAAS API endpoint, using endpoint and credentials
|
||||
# set in config.
|
||||
url = utils.config_get('maas_url')
|
||||
creds = utils.config_get('maas_credentials')
|
||||
if None in [url, creds]:
|
||||
utils.juju_log('ERROR', 'maas_url and maas_credentials must be set'\
|
||||
' in config to enable STONITH.')
|
||||
maas = maas.MAASHelper(url, creds)
|
||||
nodes = maas.list_nodes()
|
||||
if not nodes:
|
||||
utils.juju_log('ERROR', 'Could not obtain node inventory from '\
|
||||
'MAAS @ %s.' % url)
|
||||
sys.exit(1)
|
||||
|
||||
hosts = get_cluster_nodes()
|
||||
for host in hosts:
|
||||
rsc, constraint = pcmk.maas_stonith_primitive(nodes, host)
|
||||
if not rsc:
|
||||
utils.juju_log('ERROR',
|
||||
'Failed to determine STONITH primitive for node'\
|
||||
' %s' % host)
|
||||
cmd = 'crm -F configure %s' % rsc
|
||||
pcmk.commit(cmd)
|
||||
if constraint:
|
||||
cmd = 'crm -F configure %s' % constraint
|
||||
pcmk.commit(cmd)
|
||||
|
||||
for rel_id in utils.relation_ids('ha'):
|
||||
utils.relation_set(rid=rel_id,
|
||||
clustered="yes")
|
||||
|
||||
64
hooks/maas.py
Normal file
64
hooks/maas.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import apt_pkg as apt
|
||||
|
||||
import json
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
import utils
|
||||
|
||||
MAAS_STABLE_PPA = 'ppa:maas-maintainers/stable '
|
||||
MAAS_PROFILE_NAME = 'maas-juju-hacluster'
|
||||
|
||||
class MAASHelper(object):
|
||||
def __init__(url, creds):
|
||||
self.url = url
|
||||
self.creds = creds
|
||||
self.install_maas_cli()
|
||||
|
||||
def install_maas_cli(self):
|
||||
'''
|
||||
Ensure maas-cli is installed. Fallback to MAAS stable PPA when
|
||||
needed.
|
||||
'''
|
||||
apt.init()
|
||||
cache = apt.Cache()
|
||||
|
||||
try:
|
||||
pkg = cache['maas-cli']
|
||||
except KeyError:
|
||||
cmd = ['add-apt-repository', '-y', MAAS_STABLE_PPA]
|
||||
subprocess.check_call(cmd)
|
||||
cmd = ['apt-get', 'update']
|
||||
subprocess.check_call(cmd)
|
||||
install_maas_cli()
|
||||
return
|
||||
|
||||
if not pkg.current_ver:
|
||||
utils.install('maas-cli')
|
||||
|
||||
|
||||
def login(self):
|
||||
cmd = ['maas-cli', 'login', MAAS_PROFILE_NAME, self.url, self.creds]
|
||||
try:
|
||||
subprocess.check_call(cmd)
|
||||
except subprocess.CalledProcessError:
|
||||
utils.juju_log('ERROR', 'Could not login to MAAS @ %s.' % url)
|
||||
return False
|
||||
|
||||
|
||||
def logout(self):
|
||||
cmd = ['maas-cli', 'logout', MAAS_PROFILE_NAME]
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
|
||||
def list_nodes():
|
||||
self.login()
|
||||
|
||||
try:
|
||||
cmd = ['maas-cli', MAAS_PROFILE_NAME, 'nodes', 'list']
|
||||
out = subprocess.check_output(cmd)
|
||||
except subprocess.CalledProcessError:
|
||||
utils.juju_log('ERROR', 'Could not get node inventory from MAAS.')
|
||||
return False
|
||||
self.logout()
|
||||
return json.loads(out)
|
||||
@@ -52,3 +52,44 @@ def crm_opt_exists(opt_name):
|
||||
if opt:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _maas_ipmi_stonith_resource(host, power_params):
|
||||
crm_node = crm_node_name(host)
|
||||
rsc_name = '%s_res_stonith' % crm_node
|
||||
rsc = 'primitive %s stonith:external/ipmi' % rsc_name
|
||||
rsc += ' params hostname=%s ipaddr=%s user=%s passwd=%s interface=lan' %\
|
||||
(crm_node, power_params['power_address'],
|
||||
power_params['power_user'], power_params['power_pass'])
|
||||
|
||||
# ensure ipmi stonith agents are not running on the hosts that
|
||||
# they manage.
|
||||
constraint = 'location const_loc_stonith_avoid_%s %s -inf: %s' %\
|
||||
(crm_node, rsc_name, crm_node)
|
||||
|
||||
return rsc, constraint
|
||||
|
||||
|
||||
def maas_stonith_primitive(maas_nodes, host):
|
||||
power_type = power_params = None
|
||||
for node in maas_nodes:
|
||||
if node['hostname'] == host:
|
||||
power_type = node['power_type']
|
||||
power_params = node['power_parameters']
|
||||
|
||||
if not power_type or not power_params:
|
||||
return False, False
|
||||
|
||||
rsc = constraint = None
|
||||
# we can extend to support other power flavors in the future?
|
||||
if power_type == 'ipmi':
|
||||
rsc, constraint = _maas_ipmi_stonith_resource(host, power_params)
|
||||
else:
|
||||
utils.juju_log('ERROR',
|
||||
'Unsupported STONITH power_type: %s' % power_type)
|
||||
return False, False
|
||||
|
||||
if not rsc or not constraint:
|
||||
return False, False
|
||||
|
||||
return rsc, constraint
|
||||
|
||||
Reference in New Issue
Block a user