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

151
        buf.write("self._Call(_def, ")
152
153
154
155
        if kind == _SINGLE:
          buf.write("[node]")
        else:
          buf.write("node_list")
156

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

161
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
        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

    for (clsname, calls) in module.CALLS.items():
194
      _WriteBaseClass(sw, clsname, calls.values())
195
196
197
198
199
200

  print buf.getvalue()


if __name__ == "__main__":
  main()