build-rpc 5.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/python
#

# Copyright (C) 2011 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


"""Script to generate RPC code.

"""

# pylint: disable=C0103
# [C0103] Invalid name

import sys
import re
import itertools
import textwrap
from cStringIO import StringIO

from ganeti import utils
from ganeti import compat
from ganeti import build


_SINGLE = "single-node"
_MULTI = "multi-node"

43
#: Expected length of a rpc definition
44
_RPC_DEF_LEN = 8
45

46
47
48
49
50
51
52
53
54
55
56
57

def _WritePreamble(sw):
  """Writes a preamble for the RPC wrapper output.

  """
  sw.Write("# This code is automatically generated at build time.")
  sw.Write("# Do not modify manually.")
  sw.Write("")
  sw.Write("\"\"\"Automatically generated RPC client wrappers.")
  sw.Write("")
  sw.Write("\"\"\"")
  sw.Write("")
58
59
  sw.Write("from ganeti import rpc_defs")
  sw.Write("")
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82


def _WrapCode(line):
  """Wraps Python code.

  """
  return textwrap.wrap(line, width=70, expand_tabs=False,
                       fix_sentence_endings=False, break_long_words=False,
                       replace_whitespace=True,
                       subsequent_indent=utils.ShellWriter.INDENT_STR)


def _WriteDocstring(sw, name, timeout, kind, args, desc):
  """Writes a docstring for an RPC wrapper.

  """
  sw.Write("\"\"\"Wrapper for RPC call '%s'", name)
  sw.Write("")
  if desc:
    sw.Write(desc)
    sw.Write("")

  note = ["This is a %s call" % kind]
83
  if timeout and not callable(timeout):
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
    note.append(" with a timeout of %s" % utils.FormatSeconds(timeout))
  sw.Write("@note: %s", "".join(note))

  if kind == _SINGLE:
    sw.Write("@type node: string")
    sw.Write("@param node: Node name")
  else:
    sw.Write("@type node_list: list of string")
    sw.Write("@param node_list: List of node names")

  if args:
    for (argname, _, argtext) in args:
      if argtext:
        docline = "@param %s: %s" % (argname, argtext)
        for line in _WrapCode(docline):
          sw.Write(line)
  sw.Write("")
  sw.Write("\"\"\"")


def _WriteBaseClass(sw, clsname, calls):
  """Write RPC wrapper class.

  """
  sw.Write("")
  sw.Write("class %s(object):", clsname)
  sw.IncIndent()
  try:
    sw.Write("# E1101: Non-existent members")
    sw.Write("# R0904: Too many public methods")
    sw.Write("# pylint: disable=E1101,R0904")

    if not calls:
      sw.Write("pass")
      return

120
121
122
    sw.Write("_CALLS = rpc_defs.CALLS[%r]", clsname)
    sw.Write("")

123
124
125
126
127
    for v in calls:
      if len(v) != _RPC_DEF_LEN:
        raise ValueError("Procedure %s has only %d elements, expected %d" %
                         (v[0], len(v), _RPC_DEF_LEN))

128
    for (name, kind, _, timeout, args, _, _, desc) in sorted(calls):
129
130
131
132
133
134
135
136
137
138
139
      funcargs = ["self"]

      if kind == _SINGLE:
        funcargs.append("node")
      elif kind == _MULTI:
        funcargs.append("node_list")
      else:
        raise Exception("Unknown kind '%s'" % kind)

      funcargs.extend(map(compat.fst, args))

140
141
      funcargs.append("_def=_CALLS[%r]" % name)

142
143
144
145
146
147
148
149
150
151
152
153
      funcdef = "def call_%s(%s):" % (name, utils.CommaJoin(funcargs))
      for line in _WrapCode(funcdef):
        sw.Write(line)

      sw.IncIndent()
      try:
        _WriteDocstring(sw, name, timeout, kind, args, desc)

        buf = StringIO()
        buf.write("return ")

        # In case line gets too long and is wrapped in a bad spot
154
        buf.write("(")
155

156
        buf.write("self._Call(_def, ")
157
158
159
160
        if kind == _SINGLE:
          buf.write("[node]")
        else:
          buf.write("node_list")
161

162
        buf.write(", [%s])" %
163
164
                  # Function arguments
                  utils.CommaJoin(map(compat.fst, args)))
165

166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
        if kind == _SINGLE:
          buf.write("[node]")
        buf.write(")")

        for line in _WrapCode(buf.getvalue()):
          sw.Write(line)
      finally:
        sw.DecIndent()
      sw.Write("")
  finally:
    sw.DecIndent()


def main():
  """Main function.

  """
  buf = StringIO()
  sw = utils.ShellWriter(buf)

  _WritePreamble(sw)

  for filename in sys.argv[1:]:
    sw.Write("# Definitions from '%s'", filename)

    module = build.LoadModule(filename)

    # Call types are re-defined in definitions file to avoid imports. Verify
    # here to ensure they're equal to local constants.
    assert module.SINGLE == _SINGLE
    assert module.MULTI == _MULTI

198
199
200
201
202
203
    dups = utils.FindDuplicates(itertools.chain(*map(lambda value: value.keys(),
                                                     module.CALLS.values())))
    if dups:
      raise Exception("Found duplicate RPC definitions for '%s'" %
                      utils.CommaJoin(sorted(dups)))

204
    for (clsname, calls) in sorted(module.CALLS.items()):
205
      _WriteBaseClass(sw, clsname, calls.values())
206
207
208
209
210
211

  print buf.getvalue()


if __name__ == "__main__":
  main()