from ffflash.inc.rankfile import handle_rankfile
from ffflash.lib.files import check_file_location, load_file
from ffflash.lib.remote import fetch_www_struct
from ffflash.lib.text import replace_text
[docs]def _nodelist_fetch(ff):
'''
Determines if ``--nodelist`` was a file or a url, and tries to fetch it.
Validates nodelist to be json and to have the *version*, *nodes* and
*updated_at* keys.
:param ff: running :class:`ffflash.main.FFFlash` instance
:return: the unpickled nodelist or ``False``/``None`` on error
'''
if not ff.access_for('nodelist'):
return False
ff.log('fetching nodelist {}'.format(ff.args.nodelist))
nodelist = (
load_file(ff.args.nodelist, fallback=None, as_yaml=False)
if check_file_location(ff.args.nodelist, must_exist=True) else
fetch_www_struct(ff.args.nodelist, fallback=None, as_yaml=False)
)
if not nodelist or not isinstance(nodelist, dict):
return ff.log(
'could not fetch nodelist {}'.format(ff.args.nodelist),
level=False
)
if not all([(a in nodelist) for a in ['version', 'nodes', 'updated_at']]):
return ff.log(
'this is no nodelist {}'.format(ff.args.nodelist),
level=False
)
ff.log('successfully fetched nodelist from {}'.format(ff.args.nodelist))
return nodelist
[docs]def _nodelist_count(ff, nodelist):
'''
Count online nodes and sum up their clients from a nodelist.
:param ff: running :class:`ffflash.main.FFFlash` instance
:param nodelist: nodelist from :meth:`_nodelist_fetch`, should contain a
list of dictionaries at the key *nodes*
:return: Tuple of counted nodes and clients
'''
nodes, clients = 0, 0
for node in nodelist.get('nodes', []):
if node.get('status', {}).get('online', False):
nodes += 1
clients += node.get('status', {}).get('clients', 0)
if not all([nodes, clients]):
ff.log('your nodelist seems to be empty', level=False)
ff.log('found {} nodes, {} clients'.format(nodes, clients))
return nodes, clients
[docs]def _nodelist_dump(ff, nodes, clients):
'''
Store the counted numbers in the api-file.
Sets the key ``state`` . ``nodes`` with the node number.
Leaves ``state`` . ``description`` untouched, if any already present.
If empty, or the pattern ``\[[\d]+ Nodes, [\d]+ Clients\]`` is matched,
the numbers in the pattern will be replaced.
:param ff: running :class:`ffflash.main.FFFlash` instance
:param nodes: Number of online nodes
:param clients: Number of their clients
:return: ``True`` if :attr:`api` was modified else ``False``
'''
if not ff.access_for('nodelist'):
return False
modified = []
if ff.api.pull('state', 'nodes') is not None:
ff.api.push(nodes, 'state', 'nodes')
ff.log('stored {} in state.nodes'.format(nodes))
modified.append(True)
descr = ff.api.pull('state', 'description')
if descr is not None:
new = '[{} Nodes, {} Clients]'.format(nodes, clients)
new_descr = (replace_text(
r'(\[[\d]+ Nodes, [\d]+ Clients\])', new, descr
) if descr else new)
ff.api.push(new_descr, 'state', 'description')
ff.log('stored {} nodes and {} clients in state.description'.format(
nodes, clients
))
modified.append(True)
return any(modified)
[docs]def handle_nodelist(ff):
'''
Entry function to receive a ``--nodelist`` and store determined results
into both :attr:`api` and ``--rankfile`` (if specified).
:param ff: running :class:`ffflash.main.FFFlash` instance
:return: ``True`` if :attr:`api` was modified else ``False``
'''
if not ff.access_for('nodelist'):
return False
nodelist = _nodelist_fetch(ff)
if not nodelist:
return False
modified = []
nodes, clients = _nodelist_count(ff, nodelist)
if all([nodes, clients]):
modified.append(
_nodelist_dump(ff, nodes, clients)
)
if ff.access_for('rankfile'):
modified.append(
handle_rankfile(ff, nodelist)
)
return any(modified)