gnt-*: Print better error message for uninitialized cluster
[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
28 import simplejson
29 import re
30 import hmac
31
32 from ganeti import errors
33
34 try:
35   from hashlib import sha1
36 except ImportError:
37   import sha as sha1
38
39
40 _JSON_INDENT = 2
41
42 _RE_EOLSP = re.compile('[ \t]+$', re.MULTILINE)
43
44
45 def _GetJsonDumpers(_encoder_class=simplejson.JSONEncoder):
46   """Returns two JSON functions to serialize data.
47
48   @rtype: (callable, callable)
49   @return: The function to generate a compact form of JSON and another one to
50            generate a more readable, indented form of JSON (if supported)
51
52   """
53   plain_encoder = _encoder_class(sort_keys=True)
54
55   # Check whether the simplejson module supports indentation
56   try:
57     indent_encoder = _encoder_class(indent=_JSON_INDENT, sort_keys=True)
58   except TypeError:
59     # Indentation not supported
60     indent_encoder = plain_encoder
61
62   return (plain_encoder.encode, indent_encoder.encode)
63
64
65 (_DumpJson, _DumpJsonIndent) = _GetJsonDumpers()
66
67
68 def DumpJson(data, indent=True):
69   """Serialize a given object.
70
71   @param data: the data to serialize
72   @param indent: whether to indent output (depends on simplejson version)
73
74   @return: the string representation of data
75
76   """
77   if indent:
78     fn = _DumpJsonIndent
79   else:
80     fn = _DumpJson
81
82   txt = _RE_EOLSP.sub("", fn(data))
83   if not txt.endswith('\n'):
84     txt += '\n'
85
86   return txt
87
88
89 def LoadJson(txt):
90   """Unserialize data from a string.
91
92   @param txt: the json-encoded form
93
94   @return: the original data
95
96   """
97   return simplejson.loads(txt)
98
99
100 def DumpSignedJson(data, key, salt=None):
101   """Serialize a given object and authenticate it.
102
103   @param data: the data to serialize
104   @param key: shared hmac key
105   @return: the string representation of data signed by the hmac key
106
107   """
108   txt = DumpJson(data, indent=False)
109   if salt is None:
110     salt = ''
111   signed_dict = {
112     'msg': txt,
113     'salt': salt,
114     'hmac': hmac.new(key, salt + txt, sha1).hexdigest(),
115   }
116   return DumpJson(signed_dict, indent=False)
117
118
119 def LoadSignedJson(txt, key):
120   """Verify that a given message was signed with the given key, and load it.
121
122   @param txt: json-encoded hmac-signed message
123   @param key: shared hmac key
124   @rtype: tuple of original data, string
125   @return: original data, salt
126   @raises errors.SignatureError: if the message signature doesn't verify
127
128   """
129   signed_dict = LoadJson(txt)
130   if not isinstance(signed_dict, dict):
131     raise errors.SignatureError('Invalid external message')
132   try:
133     msg = signed_dict['msg']
134     salt = signed_dict['salt']
135     hmac_sign = signed_dict['hmac']
136   except KeyError:
137     raise errors.SignatureError('Invalid external message')
138
139   if hmac.new(key, salt + msg, sha1).hexdigest() != hmac_sign:
140     raise errors.SignatureError('Invalid Signature')
141
142   return LoadJson(msg), salt
143
144
145 Dump = DumpJson
146 Load = LoadJson
147 DumpSigned = DumpSignedJson
148 LoadSigned = LoadSignedJson