Add script to generate RPC wrappers
[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
44 def _WritePreamble(sw):
45   """Writes a preamble for the RPC wrapper output.
46
47   """
48   sw.Write("# This code is automatically generated at build time.")
49   sw.Write("# Do not modify manually.")
50   sw.Write("")
51   sw.Write("\"\"\"Automatically generated RPC client wrappers.")
52   sw.Write("")
53   sw.Write("\"\"\"")
54   sw.Write("")
55
56
57 def _WrapCode(line):
58   """Wraps Python code.
59
60   """
61   return textwrap.wrap(line, width=70, expand_tabs=False,
62                        fix_sentence_endings=False, break_long_words=False,
63                        replace_whitespace=True,
64                        subsequent_indent=utils.ShellWriter.INDENT_STR)
65
66
67 def _WriteDocstring(sw, name, timeout, kind, args, desc):
68   """Writes a docstring for an RPC wrapper.
69
70   """
71   sw.Write("\"\"\"Wrapper for RPC call '%s'", name)
72   sw.Write("")
73   if desc:
74     sw.Write(desc)
75     sw.Write("")
76
77   note = ["This is a %s call" % kind]
78   if timeout:
79     note.append(" with a timeout of %s" % utils.FormatSeconds(timeout))
80   sw.Write("@note: %s", "".join(note))
81
82   if kind == _SINGLE:
83     sw.Write("@type node: string")
84     sw.Write("@param node: Node name")
85   else:
86     sw.Write("@type node_list: list of string")
87     sw.Write("@param node_list: List of node names")
88
89   if args:
90     for (argname, _, argtext) in args:
91       if argtext:
92         docline = "@param %s: %s" % (argname, argtext)
93         for line in _WrapCode(docline):
94           sw.Write(line)
95   sw.Write("")
96   sw.Write("\"\"\"")
97
98
99 def _MakeArgument((argname, wrapper, _)):
100   """Format argument for function call.
101
102   """
103   if wrapper:
104     return wrapper % argname
105   else:
106     return argname
107
108
109 def _WriteBaseClass(sw, clsname, calls):
110   """Write RPC wrapper class.
111
112   """
113   sw.Write("")
114   sw.Write("class %s(object):", clsname)
115   sw.IncIndent()
116   try:
117     sw.Write("# E1101: Non-existent members")
118     sw.Write("# R0904: Too many public methods")
119     sw.Write("# pylint: disable=E1101,R0904")
120
121     if not calls:
122       sw.Write("pass")
123       return
124
125     for (name, kind, timeout, args, postproc, desc) in calls:
126       funcargs = ["self"]
127
128       if kind == _SINGLE:
129         funcargs.append("node")
130       elif kind == _MULTI:
131         funcargs.append("node_list")
132       else:
133         raise Exception("Unknown kind '%s'" % kind)
134
135       funcargs.extend(map(compat.fst, args))
136
137       assert "read_timeout" not in funcargs
138       funcargs.append("read_timeout=%s" % timeout)
139
140       funcdef = "def call_%s(%s):" % (name, utils.CommaJoin(funcargs))
141       for line in _WrapCode(funcdef):
142         sw.Write(line)
143
144       sw.IncIndent()
145       try:
146         _WriteDocstring(sw, name, timeout, kind, args, desc)
147
148         buf = StringIO()
149         buf.write("return ")
150
151         # In case line gets too long and is wrapped in a bad spot
152         buf.write("( ")
153
154         if postproc:
155           buf.write("%s(" % postproc)
156         buf.write("self._Call(")
157         if kind == _SINGLE:
158           buf.write("[node]")
159         else:
160           buf.write("node_list")
161         buf.write(", \"%s\", read_timeout, [%s])" %
162                   (name, utils.CommaJoin(map(_MakeArgument, args))))
163         if kind == _SINGLE:
164           buf.write("[node]")
165         if postproc:
166           buf.write(")")
167         buf.write(")")
168
169         for line in _WrapCode(buf.getvalue()):
170           sw.Write(line)
171       finally:
172         sw.DecIndent()
173       sw.Write("")
174   finally:
175     sw.DecIndent()
176
177
178 def main():
179   """Main function.
180
181   """
182   buf = StringIO()
183   sw = utils.ShellWriter(buf)
184
185   _WritePreamble(sw)
186
187   for filename in sys.argv[1:]:
188     sw.Write("# Definitions from '%s'", filename)
189
190     module = build.LoadModule(filename)
191
192     # Call types are re-defined in definitions file to avoid imports. Verify
193     # here to ensure they're equal to local constants.
194     assert module.SINGLE == _SINGLE
195     assert module.MULTI == _MULTI
196
197     for (clsname, calls) in module.CALLS.items():
198       _WriteBaseClass(sw, clsname, calls)
199
200   print buf.getvalue()
201
202
203 if __name__ == "__main__":
204   main()