blob: 2de71d5402137f33d62646a23ed4ceb076ab6c2d [file] [log] [blame]
#!/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"
#: Expected length of a rpc definition
_RPC_DEF_LEN = 8
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("")
sw.Write("from ganeti import rpc_defs")
sw.Write("")
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 and not callable(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
sw.Write("_CALLS = rpc_defs.CALLS[%r]", clsname)
sw.Write("")
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))
for (name, kind, _, timeout, args, _, _, desc) in sorted(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))
funcargs.append("_def=_CALLS[%r]" % name)
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("(")
buf.write("self._Call(_def, ")
if kind == _SINGLE:
buf.write("[node]")
else:
buf.write("node_list")
buf.write(", [%s])" %
# Function arguments
utils.CommaJoin(map(compat.fst, args)))
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
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)))
for (clsname, calls) in sorted(module.CALLS.items()):
_WriteBaseClass(sw, clsname, calls.values())
print buf.getvalue()
if __name__ == "__main__":
main()