build-rpc 4.95 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
43
44
45
46
47
48
49
50
51
52
53
54
#!/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"


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("")
55
56
  sw.Write("from ganeti import rpc_defs")
  sw.Write("")
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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


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]
  if timeout:
    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

117
118
119
    sw.Write("_CALLS = rpc_defs.CALLS[%r]", clsname)
    sw.Write("")

120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
    for (name, kind, timeout, args, postproc, desc) in calls:
      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))

      assert "read_timeout" not in funcargs
      funcargs.append("read_timeout=%s" % timeout)

135
136
      funcargs.append("_def=_CALLS[%r]" % name)

137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
      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
        buf.write("( ")

        if postproc:
          buf.write("%s(" % postproc)
153
        buf.write("self._Call(_def, ")
154
155
156
157
        if kind == _SINGLE:
          buf.write("[node]")
        else:
          buf.write("node_list")
158

159
160
161
        buf.write(", read_timeout, [%s])" %
                  # Function arguments
                  utils.CommaJoin(map(compat.fst, args)))
162

163
164
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]")
        if postproc:
          buf.write(")")
        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

    for (clsname, calls) in module.CALLS.items():
198
      _WriteBaseClass(sw, clsname, calls.values())
199
200
201
202
203
204

  print buf.getvalue()


if __name__ == "__main__":
  main()