Skip to content
This repository was archived by the owner on Jun 19, 2023. It is now read-only.

Commit

Permalink
Implement rankfile option, add tests for it
Browse files Browse the repository at this point in the history
  • Loading branch information
spookey committed Dec 1, 2015
1 parent cd87a96 commit 172fe1b
Show file tree
Hide file tree
Showing 12 changed files with 515 additions and 14 deletions.
8 changes: 3 additions & 5 deletions ffflash/inc/nodelist.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,13 @@ def _nodelist_fetch(ff):
)
if not nodelist or not isinstance(nodelist, dict):
return ff.log(
'Could not fetch nodelist {}'.format(ff.args.nodelist),
'could not fetch nodelist {}'.format(ff.args.nodelist),
level=False
)

if not all([
nodelist.get(a) for a in ['version', 'nodes', 'updated_at']
]):
if not all([(a in nodelist) for a in ['version', 'nodes', 'updated_at']]):
return ff.log(
'This is no nodelist {}'.format(ff.args.nodelist),
'this is no nodelist {}'.format(ff.args.nodelist),
level=False
)

Expand Down
120 changes: 120 additions & 0 deletions ffflash/inc/rankfile.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,113 @@
from operator import itemgetter
from os import path

from ffflash.lib.api import api_timestamp
from ffflash.lib.files import check_file_location, dump_file, load_file


def _rankfile_load(ff):
if not ff.access_for('rankfile'):
return (False, None, None)
ff.log('handling rankfile {}'.format(ff.args.rankfile))

rankfile = check_file_location(ff.args.rankfile, must_exist=False)
if not rankfile:
return ff.log(
'wrong path for rankfile {}'.format(ff.args.rankfile),
level=False
), None, None
_, ext = path.splitext(rankfile)
if not ext or ext.lower() not in ['.yaml', '.json']:
return ff.log(
'rankfile {} {} is neither json nor yaml'.format(rankfile, ext),
level=False
), None, None
as_yaml = True if ext == '.yaml' else False

ranks = load_file(rankfile, fallback={
'updated_at': 'never', 'nodes': []
}, as_yaml=as_yaml)

if not ranks or not isinstance(ranks, dict):
return ff.log(
'could not load rankfile {}'.format(rankfile),
level=False
), None, None

if not all([(a in ranks) for a in ['nodes', 'updated_at']]):
return ff.log(
'this is no rankfile {}'.format(rankfile),
level=False
), None, None

lranks = len(ranks.get('nodes', 0))
ff.log((
'creating new rankfile {}'.format(rankfile)
if lranks == 0 else
'loaded {} nodes'.format(lranks)
))

return rankfile, ranks, as_yaml


def _rankfile_score(ff, ranks, nodelist):
if not ff.access_for('rankfile'):
return False
if not all([
ranks, isinstance(ranks, dict), ranks and 'nodes' in ranks,
nodelist, isinstance(nodelist, dict), nodelist and 'nodes' in nodelist,
]):
return ff.log('wrong input data passed', level=False)

res = []
exist = dict(
(node.get('id'), node.get('score')) for node in ranks.get('nodes', [])
)
for node in nodelist.get('nodes', []):
nid = node.get('id')
if not nid:
ff.log('node without id {}'.format(node))
continue
nr = {
'id': nid,
'score': exist.get(nid, ff.args.rankwelcome),
'name': node.get('name')
}

if node.get('position', False):
nr['score'] += ff.args.rankposition
if node.get('status', {}).get('online', False):
nr['score'] += ff.args.rankonline
nr['online'] = True
cl = node.get('status', {}).get('clients', 0)
nr['score'] += (ff.args.rankclients * cl)
nr['clients'] = cl
else:
nr['score'] -= ff.args.rankoffline
nr['online'] = False
nr['clients'] = 0
if nr['score'] > 0:
res.append(nr)

ranks['nodes'] = list(sorted(res, key=itemgetter('score'), reverse=True))
return ranks


def _rankfile_dump(ff, rankfile, ranks, as_yaml):
if not ff.access_for('rankfile'):
return False
if not all([
rankfile, ranks, isinstance(ranks, dict), all([
(a in ranks) for a in ['nodes', 'updated_at'] if ranks
]), (as_yaml is not None)
]):
return ff.log('wrong input data passed', level=False)

ranks['updated_at'] = api_timestamp()
dump_file(rankfile, ranks, as_yaml=as_yaml)

return True


def handle_rankfile(ff, nodelist):
'''
Expand All @@ -11,3 +121,13 @@ def handle_rankfile(ff, nodelist):
return False
if not nodelist or not isinstance(nodelist, dict):
return False

rankfile, ranks, as_yaml = _rankfile_load(ff)
if not all([rankfile, ranks, (as_yaml is not None)]):
return False

ranks = _rankfile_score(ff, ranks, nodelist)
if not ranks:
return False

return _rankfile_dump(ff, rankfile, ranks, as_yaml)
2 changes: 1 addition & 1 deletion ffflash/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def __init__(self):
self.version = '0.9'

self.name = self.cname.lower()
self.release = '{}{}'.format(self.version, 'a5')
self.release = '{}{}'.format(self.version, 'a6')
self.ident = '{} {}'.format(self.name, self.release)
self.download_url = '{}/archive/{}.tar.gz'.format(
self.url, self.release
Expand Down
24 changes: 22 additions & 2 deletions ffflash/lib/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ def parsed_args(argv=None):
'APIfile', action='store',
help='Freifunk API File to modify'
)
parser.add_argument(
'-s', '--sidecars', nargs='+',
help='sync updates from/with sidecar files'
)
parser.add_argument(
'-n', '--nodelist', action='store',
help='URL or location to map\'s nodelist.json, updates nodes count'
Expand All @@ -30,8 +34,24 @@ def parsed_args(argv=None):
help='location to rankfile.json, for node statistics and credits'
)
parser.add_argument(
'-s', '--sidecars', nargs='+',
help='sync updates from/with sidecar files'
'-rc', '--rankclients', action='store', type=float, default=0.01,
help='factor to increase score per client'
)
parser.add_argument(
'-rf', '--rankoffline', action='store', type=float, default=1.0,
help='score to decrease on offline'
)
parser.add_argument(
'-rn', '--rankonline', action='store', type=float, default=1.0,
help='score to increase on online'
)
parser.add_argument(
'-rp', '--rankposition', action='store', type=float, default=0.1,
help='score to increase on position set'
)
parser.add_argument(
'-rw', '--rankwelcome', action='store', type=float, default=10.0,
help='score to start with for new nodes'
)
parser.add_argument(
'-d', '--dry', action='store_true',
Expand Down
15 changes: 11 additions & 4 deletions tests/inc/nodelist/test_handle_nodelist.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from json import dumps
from json import dumps, loads
from random import choice

from ffflash.inc.nodelist import handle_nodelist
Expand Down Expand Up @@ -40,7 +40,7 @@ def test_handle_nodelist_empty_nodelist(tmpdir, fffake):
assert handle_nodelist(ff) is False

nl.write_text(dumps({
'version': 1, 'nodes': [{}], 'updated_at': 'never'
'version': 1, 'nodes': [], 'updated_at': 'never'
}), 'utf-8')
ff = fffake(apifile, nodelist=nl, dry=True)
assert handle_nodelist(ff) is False
Expand Down Expand Up @@ -79,12 +79,19 @@ def test_handle_nodelist_launch_rankfile(tmpdir, fffake):
apifile.write_text(dumps({'a': 'b'}), 'utf-8')
nl = tmpdir.join('nodelist.json')
nl.write_text(dumps({
'version': 1, 'nodes': [{}], 'updated_at': 'never'
'version': 0, 'nodes': [], 'updated_at': 'never'
}), 'utf-8')
rf = tmpdir.join('rankfile.json')

assert tmpdir.listdir() == [apifile, nl]
ff = fffake(apifile, nodelist=nl, rankfile=rf, dry=True)

assert handle_nodelist(ff) is False
assert handle_nodelist(ff) is True
assert tmpdir.listdir() == [apifile, nl, rf]

res = loads(rf.read_text('utf-8'))
assert res
assert res.get('nodes') == []
assert res.get('updated_at', False) != 'never'

assert tmpdir.remove() is None
2 changes: 1 addition & 1 deletion tests/inc/nodelist/test_nodelist_fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def test_nodelist_fetch_wrong_format_or_empty(tmpdir, fffake, capsys):
def test_nodelist_fetch(tmpdir, fffake):
apifile = tmpdir.join('api_file.json')
apifile.write_text(dumps({'a': 'b'}), 'utf-8')
nodelist = {'version': 1, 'nodes': [None], 'updated_at': 'never'}
nodelist = {'version': 0, 'nodes': [], 'updated_at': 'never'}
nl = tmpdir.join('nodelist.json')
nl.write_text(dumps(nodelist), 'utf-8')
assert tmpdir.listdir() == [apifile, nl]
Expand Down
36 changes: 36 additions & 0 deletions tests/inc/rankfile/test_handle_rankfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,39 @@ def test_handle_rankfile_empty_nodelist(tmpdir, fffake):
assert handle_rankfile(ff, {}) is False

assert tmpdir.remove() is None


def test_handle_rankfile_trashy_rankfile(tmpdir, fffake):
apifile = tmpdir.join('api_file.json')
apifile.write_text(dumps({'a': 'b'}), 'utf-8')
nodelist = tmpdir.join('nodelist.json')
nodelist.write_text(dumps({'a': 'b'}), 'utf-8')
rankfile = tmpdir.join('rankfile.json')
rankfile.write_text(dumps({'a': 'b'}), 'utf-8')

ff = fffake(
apifile, nodelist=nodelist,
rankfile=rankfile, dry=True
)

assert handle_rankfile(ff, {'a': 'b'}) is False

assert tmpdir.remove() is None


def test_handle_rankfile_trashy_nodelist(tmpdir, fffake):
apifile = tmpdir.join('api_file.json')
apifile.write_text(dumps({'a': 'b'}), 'utf-8')
nodelist = tmpdir.join('nodelist.json')
nodelist.write_text(dumps({'a': 'b'}), 'utf-8')
rankfile = tmpdir.join('rankfile.json')
rankfile.write_text(dumps({'nodes': [], 'updated_at': 'now'}), 'utf-8')

ff = fffake(
apifile, nodelist=nodelist,
rankfile=rankfile, dry=True
)

assert handle_rankfile(ff, {'a': 'b'}) is False

assert tmpdir.remove() is None
53 changes: 53 additions & 0 deletions tests/inc/rankfile/test_rankfile_dump.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from json import dumps, loads

from ffflash.inc.rankfile import _rankfile_dump


def test_rankfile_dump_no_access(tmpdir, fffake):
ff = fffake(tmpdir.join('api_file.json'), dry=True)

assert _rankfile_dump(ff, None, {}, {}) is False

assert tmpdir.remove() is None


def test_rankfile_dump_wrong_input(tmpdir, fffake):
apifile = tmpdir.join('api_file.json')
apifile.write_text(dumps({'a': 'b'}), 'utf-8')
rf = tmpdir.join('rankfile.yaml')

ff = fffake(
apifile, nodelist=tmpdir.join('nodelist.json'),
rankfile=tmpdir.join('rankfile.yaml'), dry=True
)

assert _rankfile_dump(ff, None, None, None) is False
assert _rankfile_dump(ff, 'test', {}, None) is False
assert _rankfile_dump(ff, str(rf), {}, True) is False
assert _rankfile_dump(ff, str(rf), {'nodes': []}, True) is False
assert _rankfile_dump(ff, str(rf), {'updated_at': 'now'}, True) is False

assert tmpdir.remove() is None


def test_rankfile_dump_data(tmpdir, fffake):
apifile = tmpdir.join('api_file.json')
apifile.write_text(dumps({'a': 'b'}), 'utf-8')
rf = tmpdir.join('rankfile.yaml')
rk = {'updated_at': 'never', 'nodes': [{'a': 'b'}, {'c': 'd'}]}

assert tmpdir.listdir() == [apifile]
ff = fffake(
apifile, nodelist=tmpdir.join('nodelist.json'),
rankfile=tmpdir.join('rankfile.json'), dry=True
)

assert _rankfile_dump(ff, str(rf), rk, False) is True

assert tmpdir.listdir() == [apifile, rf]
r = loads(rf.read_text('utf-8'))
assert r
assert r.get('nodes') == rk['nodes']
assert r.get('updated_at') != 'never'

assert tmpdir.remove() is None
Loading

0 comments on commit 172fe1b

Please sign in to comment.