summaryrefslogtreecommitdiffstats
path: root/agents/stonith/fence_watchdog.in
blob: f43ab879d4fccec326dfd43303d20cf5c58af80c (plain)
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
55
56
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
#!@PYTHON@
"""Dummy watchdog fence agent for providing meta-data for the pacemaker internal agent
"""

__copyright__ = "Copyright 2012-2022 the Pacemaker project contributors"
__license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY"

import io
import os
import re
import sys
import atexit
import getopt

AGENT_VERSION = "1.0.0"
SHORT_DESC = "Dummy watchdog fence agent"
LONG_DESC = """fence_watchdog just provides
meta-data - actual fencing is done by the pacemaker internal watchdog agent."""

ALL_OPT = {
    "version" : {
        "getopt" : "V",
        "longopt" : "version",
        "help" : "-V, --version                  Display version information and exit",
        "required" : "0",
        "shortdesc" : "Display version information and exit",
        "order" : 53
        },
    "help"    : {
        "getopt" : "h",
        "longopt" : "help",
        "help" : "-h, --help                     Display this help and exit",
        "required" : "0",
        "shortdesc" : "Display help and exit",
        "order" : 54
        },
    "action" : {
        "getopt" : "o:",
        "longopt" : "action",
        "help" : "-o, --action=[action]          Action: metadata",
        "required" : "1",
        "shortdesc" : "Fencing Action",
        "default" : "metadata",
        "order" : 1
        },
    "nodename" : {
        "getopt" : "N:",
        "longopt" : "nodename",
        "help" : "-N, --nodename                 Node name of fence target (ignored)",
        "required" : "0",
        "shortdesc" : "Ignored",
        "order" : 2
        },
    "plug" : {
        "getopt" : "n:",
        "longopt" : "plug",
        "help" : "-n, --plug=[id]                Physical plug number on device (ignored)",
        "required" : "1",
        "shortdesc" : "Ignored",
        "order" : 4
        }
}


def agent():
    """ Return name this file was run as. """

    return os.path.basename(sys.argv[0])


def fail_usage(message):
    """ Print a usage message and exit. """

    sys.exit("%s\nPlease use '-h' for usage" % message)


def show_docs(options):
    """ Handle informational options (display info and exit). """

    device_opt = options["device_opt"]

    if "-h" in options:
        usage(device_opt)
        sys.exit(0)

    if "-o" in options and options["-o"].lower() == "metadata":
        metadata(device_opt, options)
        sys.exit(0)

    if "-V" in options:
        print(AGENT_VERSION)
        sys.exit(0)


def sorted_options(avail_opt):
    """ Return a list of all options, in their internally specified order. """

    sorted_list = [(key, ALL_OPT[key]) for key in avail_opt]
    sorted_list.sort(key=lambda x: x[1]["order"])
    return sorted_list


def usage(avail_opt):
    """ Print a usage message. """
    print(LONG_DESC)
    print()
    print("Usage:")
    print("\t" + agent() + " [options]")
    print("Options:")

    for dummy, value in sorted_options(avail_opt):
        if len(value["help"]) != 0:
            print("   " + value["help"])


def metadata(avail_opt, options):
    """ Print agent metadata. """

    print("""<?xml version="1.0" ?>
<resource-agent name="%s" shortdesc="%s">
<longdesc>%s</longdesc>
<parameters>""" % (agent(), SHORT_DESC, LONG_DESC))

    for option, dummy in sorted_options(avail_opt):
        if "shortdesc" in ALL_OPT[option]:
            print('    <parameter name="' + option +
                  '" required="' + ALL_OPT[option]["required"] + '">')

            default = ""
            default_name_arg = "-" + ALL_OPT[option]["getopt"][:-1]
            default_name_no_arg = "-" + ALL_OPT[option]["getopt"]

            if "default" in ALL_OPT[option]:
                default = 'default="%s"' % str(ALL_OPT[option]["default"])
            elif default_name_arg in options:
                if options[default_name_arg]:
                    try:
                        default = 'default="%s"' % options[default_name_arg]
                    except TypeError:
                        ## @todo/@note: Currently there is no clean way how to handle lists
                        ## we can create a string from it but we can't set it on command line
                        default = 'default="%s"' % str(options[default_name_arg])
            elif default_name_no_arg in options:
                default = 'default="true"'

            mixed = ALL_OPT[option]["help"]
            ## split it between option and help text
            res = re.compile(r"^(.*--\S+)\s+", re.IGNORECASE | re.S).search(mixed)
            if None != res:
                mixed = res.group(1)
            mixed = mixed.replace("<", "&lt;").replace(">", "&gt;")
            print('      <getopt mixed="' + mixed + '" />')

            if ALL_OPT[option]["getopt"].count(":") > 0:
                print('      <content type="string" ' + default + ' />')
            else:
                print('      <content type="boolean" ' + default + ' />')

            print('      <shortdesc lang="en">' + ALL_OPT[option]["shortdesc"] + '</shortdesc>')
            print('    </parameter>')

    print('  </parameters>\n <actions>')
    print('    <action name="on" />')
    print('    <action name="off" />')
    print('    <action name="reboot" />')
    print('    <action name="monitor" />')
    print('    <action name="list" />')
    print('    <action name="metadata" />')
    print('  </actions>')
    print('</resource-agent>')


def option_longopt(option):
    """ Return the getopt-compatible long-option name of the given option. """

    if ALL_OPT[option]["getopt"].endswith(":"):
        return ALL_OPT[option]["longopt"] + "="
    else:
        return ALL_OPT[option]["longopt"]


def opts_from_command_line(argv, avail_opt):
    """ Read options from command-line arguments. """

    # Prepare list of options for getopt
    getopt_string = ""
    longopt_list = []
    for k in avail_opt:
        if k in ALL_OPT:
            getopt_string += ALL_OPT[k]["getopt"]
        else:
            fail_usage("Parse error: unknown option '" + k + "'")

        if k in ALL_OPT and "longopt" in ALL_OPT[k]:
            longopt_list.append(option_longopt(k))

    try:
        opt, dummy = getopt.gnu_getopt(argv, getopt_string, longopt_list)
    except getopt.GetoptError as error:
        fail_usage("Parse error: " + error.msg)

    # Transform longopt to short one which are used in fencing agents
    old_opt = opt
    opt = {}
    for old_option in dict(old_opt).keys():
        if old_option.startswith("--"):
            for option in ALL_OPT.keys():
                if "longopt" in ALL_OPT[option] and "--" + ALL_OPT[option]["longopt"] == old_option:
                    opt["-" + ALL_OPT[option]["getopt"].rstrip(":")] = dict(old_opt)[old_option]
        else:
            opt[old_option] = dict(old_opt)[old_option]

    return opt


def opts_from_stdin(avail_opt):
    """ Read options from standard input. """

    opt = {}
    name = ""
    for line in sys.stdin.readlines():
        line = line.strip()
        if line.startswith("#") or (len(line) == 0):
            continue

        (name, value) = (line + "=").split("=", 1)
        value = value[:-1]

        if name not in avail_opt:
            print("Parse error: Ignoring unknown option '%s'" % line,
                  file=sys.stderr)
            continue

        if ALL_OPT[name]["getopt"].endswith(":"):
            opt["-"+ALL_OPT[name]["getopt"].rstrip(":")] = value
        elif value.lower() in ["1", "yes", "on", "true"]:
            opt["-"+ALL_OPT[name]["getopt"]] = "1"

    return opt


def process_input(avail_opt):
    """ Set standard environment variables, and parse all options. """

    # Set standard environment
    os.putenv("LANG", "C")
    os.putenv("LC_ALL", "C")

    # Read options from command line or standard input
    if len(sys.argv) > 1:
        return opts_from_command_line(sys.argv[1:], avail_opt)
    else:
        return opts_from_stdin(avail_opt)


def atexit_handler():
    """ Close stdout on exit. """

    try:
        sys.stdout.close()
        os.close(1)
    except IOError:
        sys.exit("%s failed to close standard output" % agent())


def main():
    """ Make it so! """

    device_opt = ALL_OPT.keys()

    ## Defaults for fence agent
    atexit.register(atexit_handler)
    options = process_input(device_opt)
    options["device_opt"] = device_opt
    show_docs(options)

    print("Watchdog fencing may be initiated only by the cluster, not this agent.",
          file=sys.stderr)

    sys.exit(1)


if __name__ == "__main__":
    main()