5 minutes is too long of a timeout to keep an apache proc open on PLC. 90 seconds...
[nodemanager.git] / plcapi.py
1 import safexmlrpc
2 import hmac, sha
3
4 class PLCAPI:
5     """
6     Wrapper around safexmlrpc.ServerProxy to automagically add an Auth
7     struct as the first argument to every XML-RPC call. Initialize
8     auth with either:
9
10     (node_id, key) => BootAuth
11     or
12     session => SessionAuth
13
14     To authenticate using the Boot Manager authentication method, or
15     the new session-based method.
16     """
17
18     def __init__(self, uri, cacert, auth, timeout = 90, **kwds):
19         if isinstance(auth, (tuple, list)):
20             (self.node_id, self.key) = auth
21             self.session = None
22         else:
23             self.node_id = self.key = None
24             self.session = auth
25
26         self.server = safexmlrpc.ServerProxy(uri, cacert, timeout, allow_none = 1, **kwds)
27
28     def add_auth(self, function):
29         """
30         Returns a wrapper which adds an Auth struct as the first
31         argument when the function is called.
32         """
33
34         def canonicalize(args):
35             """
36             BootAuth canonicalization method. Parameter values are
37             collected, sorted, converted to strings, then hashed with
38             the node key.
39             """
40
41             values = []
42
43             for arg in args:
44                 if isinstance(arg, list) or isinstance(arg, tuple):
45                     # The old implementation did not recursively handle
46                     # lists of lists. But neither did the old API itself.
47                     values += canonicalize(arg)
48                 elif isinstance(arg, dict):
49                     # Yes, the comments in the old implementation are
50                     # misleading. Keys of dicts are not included in the
51                     # hash.
52                     values += canonicalize(arg.values())
53                 else:
54                     # We use unicode() instead of str().
55                     values.append(unicode(arg))
56
57             return values
58
59         def wrapper(*params):
60             """
61             Adds an Auth struct as the first argument when the
62             function is called.
63             """
64
65             if self.session is not None:
66                 # Use session authentication
67                 auth = {'AuthMethod': "session",
68                         'session': self.session}
69             else:
70                 # Yes, this is the "canonicalization" method used.
71                 args = canonicalize(params)
72                 args.sort()
73                 msg = "[" + "".join(args) + "]"
74
75                 # We encode in UTF-8 before calculating the HMAC, which is
76                 # an 8-bit algorithm.
77                 digest = hmac.new(self.key, msg.encode('utf-8'), sha).hexdigest()
78
79                 auth = {'AuthMethod': "hmac",
80                         'node_id': self.node_id,
81                         'value': digest}
82
83             # Automagically add auth struct to every call
84             params = (auth,) + params
85
86             return function(*params)
87
88         return wrapper
89
90     def __getattr__(self, methodname):
91         function = getattr(self.server, methodname)
92         return self.add_auth(function)