Implement dependencies, cleanup code
This commit is contained in:
parent
935cf24833
commit
1b0578adf4
1 changed files with 91 additions and 57 deletions
148
server.py
148
server.py
|
@ -25,6 +25,32 @@ def require_args(*args):
|
||||||
parser.add_argument(arg, required=True)
|
parser.add_argument(arg, required=True)
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
class etcdWrapper(object):
|
||||||
|
""" Generalises some etcd actions """
|
||||||
|
|
||||||
|
def __init__(self, client, base):
|
||||||
|
self.client = client
|
||||||
|
self.base = base
|
||||||
|
|
||||||
|
def read_key_or_none(self, path, recursive=False):
|
||||||
|
try:
|
||||||
|
data = self.client.read(path, recursive=recursive)
|
||||||
|
except etcd.EtcdKeyNotFound:
|
||||||
|
return None
|
||||||
|
except Exception:
|
||||||
|
abort(Response(status=400, response="Error connecting to etcd"))
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_user_key_or_none(self, user, key):
|
||||||
|
path = "{}/user/{}/{}".format(self.base, user, key)
|
||||||
|
return self.read_key_or_none(path)
|
||||||
|
|
||||||
|
def set_user_key(self, user, key, value):
|
||||||
|
path = "{}/user/{}/{}".format(self.base, user, key)
|
||||||
|
self.client.write(path, value)
|
||||||
|
|
||||||
|
|
||||||
class Challenge(object):
|
class Challenge(object):
|
||||||
""" A sample challenge -- inherit this and overwrite accordingly """
|
""" A sample challenge -- inherit this and overwrite accordingly """
|
||||||
|
|
||||||
|
@ -32,21 +58,12 @@ class Challenge(object):
|
||||||
provides = []
|
provides = []
|
||||||
requires = []
|
requires = []
|
||||||
description = None
|
description = None
|
||||||
|
dependencies_provided_by = {}
|
||||||
|
|
||||||
def __init__(self, etcdclient):
|
def __init__(self, wrapper):
|
||||||
self.client = etcdclient
|
self.db = wrapper
|
||||||
|
|
||||||
def game(self):
|
def game(self):
|
||||||
# Check if preconditions are met - otherwise error out
|
|
||||||
# This might need to me moved to the Game class to
|
|
||||||
# retrieve other challenges that we can recommend
|
|
||||||
# for req in self.requires:
|
|
||||||
# path = "/ungleichgame/v1/user/{}/{}".format(user, req)
|
|
||||||
# try:
|
|
||||||
# data = self.client.read(path)
|
|
||||||
# except etcd.EtcdKeyNotFound:
|
|
||||||
# return None
|
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
return self.describe()
|
return self.describe()
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
|
@ -59,11 +76,7 @@ class Challenge(object):
|
||||||
""" should be called when the challenge was solved successfully"""
|
""" should be called when the challenge was solved successfully"""
|
||||||
|
|
||||||
key = "points/{}".format(user, type(self).__name__)
|
key = "points/{}".format(user, type(self).__name__)
|
||||||
self.set_user_key(user, key, self.points)
|
self.db.set_user_key(user, key, self.points)
|
||||||
|
|
||||||
def set_user_key(self, user, key, value):
|
|
||||||
path = "/ungleichgame/v1/user/{}/{}".format(user, key)
|
|
||||||
self.client.write(path, value)
|
|
||||||
|
|
||||||
def solve(self):
|
def solve(self):
|
||||||
""" Needs to be implemented per challenge """
|
""" Needs to be implemented per challenge """
|
||||||
|
@ -95,7 +108,7 @@ Submit your network with the "network" parameter.
|
||||||
return Response(status=400, response="{} mask is not /64 - please use a /64 network".format(net))
|
return Response(status=400, response="{} mask is not /64 - please use a /64 network".format(net))
|
||||||
|
|
||||||
# Save network
|
# Save network
|
||||||
self.set_user_key(user, "network", network)
|
self.db.set_user_key(user, "network", network)
|
||||||
self.save_points(user)
|
self.save_points(user)
|
||||||
|
|
||||||
return "Network {} registered, have fun with the next challenge!".format(network)
|
return "Network {} registered, have fun with the next challenge!".format(network)
|
||||||
|
@ -108,34 +121,53 @@ class IPv6Address(Challenge):
|
||||||
You have setup your network, great!
|
You have setup your network, great!
|
||||||
Now it is time to show that you are really controlling your network!
|
Now it is time to show that you are really controlling your network!
|
||||||
|
|
||||||
|
Setup the IPv6 address
|
||||||
|
|
||||||
|
{}
|
||||||
|
|
||||||
|
and POST to this address, when it should be reachable by ping6.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def describe(self):
|
def describe(self):
|
||||||
pass
|
args = require_args("user")
|
||||||
|
user = args['user']
|
||||||
|
key = "network"
|
||||||
|
|
||||||
|
network = self.db.get_user_key_or_none(user, key)
|
||||||
|
if not network:
|
||||||
|
return Response(status=400, response="""
|
||||||
|
Register a network before trying to be reachable. Possible challenges that
|
||||||
|
provide the network are:
|
||||||
|
|
||||||
|
{}
|
||||||
|
""".format("\n".join(self.dependencies_provided_by['network'])))
|
||||||
|
|
||||||
|
|
||||||
|
key = "address"
|
||||||
|
address = self.db.get_user_key_or_none(user, key)
|
||||||
|
if not address:
|
||||||
|
address = get_random_ip(network.value)
|
||||||
|
self.db.set_user_key(user, key, address)
|
||||||
|
else:
|
||||||
|
address = address.value
|
||||||
|
|
||||||
|
return self.description.format(address)
|
||||||
|
|
||||||
def solve(self):
|
def solve(self):
|
||||||
args = require_args("user", "network")
|
args = require_args("user")
|
||||||
network = args['network']
|
|
||||||
user = args['user']
|
user = args['user']
|
||||||
|
|
||||||
try:
|
return Response(status=400, response="""
|
||||||
net = ipaddress.IPv6Network(network)
|
Not yet implemented""")
|
||||||
except Exception as e:
|
|
||||||
return Response(status=400, response="Cannot register network {}: {}".format(network, e))
|
|
||||||
|
|
||||||
if not net.prefixlen == 64:
|
|
||||||
return Response(status=400, response="{} mask is not /64 - please use a /64 network".format(net))
|
|
||||||
|
|
||||||
# Save network
|
|
||||||
self.set_user_key(user, "network", network)
|
|
||||||
self.save_points(user)
|
|
||||||
|
|
||||||
return "Network {} registered, have fun with the next challenge!".format(network)
|
|
||||||
|
|
||||||
|
|
||||||
class Game(object):
|
class Game(object):
|
||||||
def __init__(self, name, etcdclient, etcbase="/ungleichgame/v1"):
|
def __init__(self, name, etcdclient, etcbase="/ungleichgame/v1"):
|
||||||
self.client = etcdclient
|
self.client = etcdclient
|
||||||
|
self.etcbase = etcbase
|
||||||
|
|
||||||
|
self.wrapper = etcdWrapper(etcdclient, self.etcbase)
|
||||||
|
|
||||||
self.app = Flask(name)
|
self.app = Flask(name)
|
||||||
|
|
||||||
self.app.add_url_rule('/', 'index', self.index)
|
self.app.add_url_rule('/', 'index', self.index)
|
||||||
|
@ -143,24 +175,43 @@ class Game(object):
|
||||||
self.app.add_url_rule('/register', 'register', self.register, methods=['POST'])
|
self.app.add_url_rule('/register', 'register', self.register, methods=['POST'])
|
||||||
|
|
||||||
# etcd paths are below here
|
# etcd paths are below here
|
||||||
self.etcbase = etcbase
|
|
||||||
self.userbase = "{}/user".format(self.etcbase)
|
self.userbase = "{}/user".format(self.etcbase)
|
||||||
|
|
||||||
# Automate this
|
# Automate this
|
||||||
challenges = [ RegisterNet ]
|
challenges = [ RegisterNet, IPv6Address ]
|
||||||
|
challenge_instances = []
|
||||||
|
|
||||||
self.app.add_url_rule('/challenge', 'list_of_challenges', self.list_of_challenges)
|
self.app.add_url_rule('/challenge', 'list_of_challenges', self.list_of_challenges)
|
||||||
self.app.add_url_rule('/challenge/', 'list_of_challenges', self.list_of_challenges)
|
self.app.add_url_rule('/challenge/', 'list_of_challenges', self.list_of_challenges)
|
||||||
|
|
||||||
|
self.providers = {}
|
||||||
self.challenge_names = []
|
self.challenge_names = []
|
||||||
for challenge in challenges:
|
for challenge in challenges:
|
||||||
c = challenge(self.client)
|
c = challenge(self.wrapper)
|
||||||
|
challenge_instances.append(c)
|
||||||
|
|
||||||
name = type(c).__name__
|
name = type(c).__name__
|
||||||
self.challenge_names.append(name)
|
self.challenge_names.append(name)
|
||||||
path = "/challenge/{}".format(name)
|
path = "/challenge/{}".format(name)
|
||||||
|
|
||||||
self.app.add_url_rule(path, name, c.game, methods=['GET', 'POST'])
|
self.app.add_url_rule(path, name, c.game, methods=['GET', 'POST'])
|
||||||
|
|
||||||
|
for provider in challenge.provides:
|
||||||
|
if not provider in self.providers:
|
||||||
|
self.providers[provider] = []
|
||||||
|
|
||||||
|
self.providers[provider].append(name)
|
||||||
|
|
||||||
|
# Update challenges with provider information
|
||||||
|
for challenge in challenge_instances:
|
||||||
|
for requirement in challenge.requires:
|
||||||
|
if not requirement in self.providers:
|
||||||
|
raise Exception("Unplayable server/game: {}".format(type(challenge).__name__))
|
||||||
|
|
||||||
|
challenge.dependencies_provided_by[requirement] = self.providers[requirement]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def list_of_challenges(self):
|
def list_of_challenges(self):
|
||||||
base = request.base_url
|
base = request.base_url
|
||||||
|
@ -173,15 +224,6 @@ class Game(object):
|
||||||
""".format("\n".join(challenges))
|
""".format("\n".join(challenges))
|
||||||
|
|
||||||
|
|
||||||
def read_etcd(self, path, recursive=False):
|
|
||||||
try:
|
|
||||||
data = self.client.read(path, recursive=recursive)
|
|
||||||
except etcd.EtcdKeyNotFound:
|
|
||||||
return None
|
|
||||||
except Exception:
|
|
||||||
abort(Response(status=400, response="Error connecting to etcd"))
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def get_points(self):
|
def get_points(self):
|
||||||
""" Returns a dict['username'] = points """
|
""" Returns a dict['username'] = points """
|
||||||
|
@ -189,16 +231,15 @@ class Game(object):
|
||||||
user_points = {}
|
user_points = {}
|
||||||
|
|
||||||
path = "{}/".format(self.userbase)
|
path = "{}/".format(self.userbase)
|
||||||
users = self.client.get(path)
|
users = self.wrapper.read_key_or_none(path)
|
||||||
|
|
||||||
if users:
|
if users:
|
||||||
print(users)
|
|
||||||
for user in users.children:
|
for user in users.children:
|
||||||
username= user.key # needs to be FIXED
|
username = user.key.replace(path,"")
|
||||||
user_points[username] = 0
|
user_points[username] = 0
|
||||||
|
|
||||||
point_path = "{}/points".format(user.key)
|
point_path = "{}/points".format(user.key)
|
||||||
points = self.read_etcd(point_path, recursive=True)
|
points = self.wrapper.read_key_or_none(point_path, recursive=True)
|
||||||
|
|
||||||
if not points:
|
if not points:
|
||||||
continue
|
continue
|
||||||
|
@ -308,12 +349,5 @@ Point list (aka high score)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# net_base = "2a0a:e5c1:{:x}::/64"
|
|
||||||
# net_offset = random.randrange(0xffff)
|
|
||||||
# net = ipaddress.IPv6Network(net_base.format(net_offset))
|
|
||||||
# username = 'nico{}'.format(net_offset)
|
|
||||||
|
|
||||||
# print("{} has {}".format(username, net))
|
|
||||||
|
|
||||||
g = Game(__name__, etcd.Client(port=2379))
|
g = Game(__name__, etcd.Client(port=2379))
|
||||||
g.app.run(host="::", port='5002')
|
g.app.run(host="::", port='5002')
|
||||||
|
|
Loading…
Add table
Reference in a new issue