Use node UUID for locking in LUInstanceMove
[ganeti-local] / lib / serializer.py
1 #
2 #
3
4 # Copyright (C) 2007, 2008 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21 """Serializer abstraction module
22
23 This module introduces a simple abstraction over the serialization
24 backend (currently json).
25
26 """
27 # pylint: disable=C0103
28
29 # C0103: Invalid name, since pylint doesn't see that Dump points to a
30 # function and not a constant
31
32 import re
33
34 # Python 2.6 and above contain a JSON module based on simplejson. Unfortunately
35 # the standard library version is significantly slower than the external
36 # module. While it should be better from at least Python 3.2 on (see Python
37 # issue 7451), for now Ganeti needs to work well with older Python versions
38 # too.
39 import simplejson
40
41 from ganeti import errors
42 from ganeti import utils
43
44
45 _RE_EOLSP = re.compile("[ \t]+$", re.MULTILINE)
46
47
48 def DumpJson(data):
49   """Serialize a given object.
50
51   @param data: the data to serialize
52   @return: the string representation of data
53
54   """
55   encoded = simplejson.dumps(data)
56
57   txt = _RE_EOLSP.sub("", encoded)
58   if not txt.endswith("\n"):
59     txt += "\n"
60
61   return txt
62
63
64 def LoadJson(txt):
65   """Unserialize data from a string.
66
67   @param txt: the json-encoded form
68
69   @return: the original data
70
71   """
72   return simplejson.loads(txt)
73
74
75 def DumpSignedJson(data, key, salt=None, key_selector=None):
76   """Serialize a given object and authenticate it.
77
78   @param data: the data to serialize
79   @param key: shared hmac key
80   @param key_selector: name/id that identifies the key (in case there are
81     multiple keys in use, e.g. in a multi-cluster environment)
82   @return: the string representation of data signed by the hmac key
83
84   """
85   txt = DumpJson(data)
86   if salt is None:
87     salt = ""
88   signed_dict = {
89     "msg": txt,
90     "salt": salt,
91     }
92
93   if key_selector:
94     signed_dict["key_selector"] = key_selector
95   else:
96     key_selector = ""
97
98   signed_dict["hmac"] = utils.Sha1Hmac(key, txt, salt=salt + key_selector)
99
100   return DumpJson(signed_dict)
101
102
103 def LoadSignedJson(txt, key):
104   """Verify that a given message was signed with the given key, and load it.
105
106   @param txt: json-encoded hmac-signed message
107   @param key: the shared hmac key or a callable taking one argument (the key
108     selector), which returns the hmac key belonging to the key selector.
109     Typical usage is to pass a reference to the get method of a dict.
110   @rtype: tuple of original data, string
111   @return: original data, salt
112   @raises errors.SignatureError: if the message signature doesn't verify
113
114   """
115   signed_dict = LoadJson(txt)
116   if not isinstance(signed_dict, dict):
117     raise errors.SignatureError("Invalid external message")
118   try:
119     msg = signed_dict["msg"]
120     salt = signed_dict["salt"]
121     hmac_sign = signed_dict["hmac"]
122   except KeyError:
123     raise errors.SignatureError("Invalid external message")
124
125   if callable(key):
126     # pylint: disable=E1103
127     key_selector = signed_dict.get("key_selector", None)
128     hmac_key = key(key_selector)
129     if not hmac_key:
130       raise errors.SignatureError("No key with key selector '%s' found" %
131                                   key_selector)
132   else:
133     key_selector = ""
134     hmac_key = key
135
136   if not utils.VerifySha1Hmac(hmac_key, msg, hmac_sign,
137                               salt=salt + key_selector):
138     raise errors.SignatureError("Invalid Signature")
139
140   return LoadJson(msg), salt
141
142
143 def LoadAndVerifyJson(raw, verify_fn):
144   """Parses and verifies JSON data.
145
146   @type raw: string
147   @param raw: Input data in JSON format
148   @type verify_fn: callable
149   @param verify_fn: Verification function, usually from L{ht}
150   @return: De-serialized data
151
152   """
153   try:
154     data = LoadJson(raw)
155   except Exception, err:
156     raise errors.ParseError("Can't parse input data: %s" % err)
157
158   if not verify_fn(data):
159     raise errors.ParseError("Data does not match expected format: %s" %
160                             verify_fn)
161
162   return data
163
164
165 Dump = DumpJson
166 Load = LoadJson
167 DumpSigned = DumpSignedJson
168 LoadSigned = LoadSignedJson