Make build_chroot compatible with Squeeze
[ganeti-local] / autotools / build-rpc
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2011 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
22 """Script to generate RPC code.
23
24 """
25
26 # pylint: disable=C0103
27 # [C0103] Invalid name
28
29 import sys
30 import re
31 import itertools
32 import textwrap
33 from cStringIO import StringIO
34
35 from ganeti import utils
36 from ganeti import compat
37 from ganeti import build
38
39
40 _SINGLE = "single-node"
41 _MULTI = "multi-node"
42
43 #: Expected length of a rpc definition
44 _RPC_DEF_LEN = 8
45
46
47 def _WritePreamble(sw):
48   """Writes a preamble for the RPC wrapper output.
49
50   """
51   sw.Write("# This code is automatically generated at build time.")
52   sw.Write("# Do not modify manually.")
53   sw.Write("")
54   sw.Write("\"\"\"Automatically generated RPC client wrappers.")
55   sw.Write("")
56   sw.Write("\"\"\"")
57   sw.Write("")
58   sw.Write("from ganeti import rpc_defs")
59   sw.Write("")
60
61
62 def _WrapCode(line):
63   """Wraps Python code.
64
65   """
66   return textwrap.wrap(line, width=70, expand_tabs=False,
67                        fix_sentence_endings=False, break_long_words=False,
68                        replace_whitespace=True,
69                        subsequent_indent=utils.ShellWriter.INDENT_STR)
70
71
72 def _WriteDocstring(sw, name, timeout, kind, args, desc):
73   """Writes a docstring for an RPC wrapper.
74
75   """
76   sw.Write("\"\"\"Wrapper for RPC call '%s'", name)
77   sw.Write("")
78   if desc:
79     sw.Write(desc)
80     sw.Write("")
81
82   note = ["This is a %s call" % kind]
83   if timeout and not callable(timeout):
84     note.append(" with a timeout of %s" % utils.FormatSeconds(timeout))
85   sw.Write("@note: %s", "".join(note))
86
87   if kind == _SINGLE:
88     sw.Write("@type node: string")
89     sw.Write("@param node: Node name")
90   else:
91     sw.Write("@type node_list: list of string")
92     sw.Write("@param node_list: List of node names")
93
94   if args:
95     for (argname, _, argtext) in args:
96       if argtext:
97         docline = "@param %s: %s" % (argname, argtext)
98         for line in _WrapCode(docline):
99           sw.Write(line)
100   sw.Write("")
101   sw.Write("\"\"\"")
102
103
104 def _WriteBaseClass(sw, clsname, calls):
105   """Write RPC wrapper class.
106
107   """
108   sw.Write("")
109   sw.Write("class %s(object):", clsname)
110   sw.IncIndent()
111   try:
112     sw.Write("# E1101: Non-existent members")
113     sw.Write("# R0904: Too many public methods")
114     sw.Write("# pylint: disable=E1101,R0904")
115
116     if not calls:
117       sw.Write("pass")
118       return
119
120     sw.Write("_CALLS = rpc_defs.CALLS[%r]", clsname)
121     sw.Write("")
122
123     for v in calls:
124       if len(v) != _RPC_DEF_LEN:
125         raise ValueError("Procedure %s has only %d elements, expected %d" %
126                          (v[0], len(v), _RPC_DEF_LEN))
127
128     for (name, kind, _, timeout, args, _, _, desc) in sorted(calls):
129       funcargs = ["self"]
130
131       if kind == _SINGLE:
132         funcargs.append("node")
133       elif kind == _MULTI:
134         funcargs.append("node_list")
135       else:
136         raise Exception("Unknown kind '%s'" % kind)
137
138       funcargs.extend(map(compat.fst, args))
139
140       funcargs.append("_def=_CALLS[%r]" % name)
141
142       funcdef = "def call_%s(%s):" % (name, utils.CommaJoin(funcargs))
143       for line in _WrapCode(funcdef):
144         sw.Write(line)
145
146       sw.IncIndent()
147       try:
148         _WriteDocstring(sw, name, timeout, kind, args, desc)
149
150         buf = StringIO()
151         buf.write("return ")
152
153         # In case line gets too long and is wrapped in a bad spot
154         buf.write("(")
155
156         buf.write("self._Call(_def, ")
157         if kind == _SINGLE:
158           buf.write("[node]")
159         else:
160           buf.write("node_list")
161
162         buf.write(", [%s])" %
163                   # Function arguments
164                   utils.CommaJoin(map(compat.fst, args)))
165
166         if kind == _SINGLE:
167           buf.write("[node]")
168         buf.write(")")
169
170         for line in _WrapCode(buf.getvalue()):
171           sw.Write(line)
172       finally:
173         sw.DecIndent()
174       sw.Write("")
175   finally:
176     sw.DecIndent()
177
178
179 def main():
180   """Main function.
181
182   """
183   buf = StringIO()
184   sw = utils.ShellWriter(buf)
185
186   _WritePreamble(sw)
187
188   for filename in sys.argv[1:]:
189     sw.Write("# Definitions from '%s'", filename)
190
191     module = build.LoadModule(filename)
192
193     # Call types are re-defined in definitions file to avoid imports. Verify
194     # here to ensure they're equal to local constants.
195     assert module.SINGLE == _SINGLE
196     assert module.MULTI == _MULTI
197
198     dups = utils.FindDuplicates(itertools.chain(*map(lambda value: value.keys(),
199                                                      module.CALLS.values())))
200     if dups:
201       raise Exception("Found duplicate RPC definitions for '%s'" %
202                       utils.CommaJoin(sorted(dups)))
203
204     for (clsname, calls) in sorted(module.CALLS.items()):
205       _WriteBaseClass(sw, clsname, calls.values())
206
207   print buf.getvalue()
208
209
210 if __name__ == "__main__":
211   main()