Add blacklisted/hidden OS support in LUDiagnoseOS
[ganeti-local] / scripts / gnt-os
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2006, 2007, 2010 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21 """OS scripts related commands"""
22
23 # pylint: disable-msg=W0401,W0613,W0614,C0103
24 # W0401: Wildcard import ganeti.cli
25 # W0613: Unused argument, since all functions follow the same API
26 # W0614: Unused import %s from wildcard import (since we need cli)
27 # C0103: Invalid name gnt-os
28
29 import sys
30
31 from ganeti.cli import *
32 from ganeti import constants
33 from ganeti import opcodes
34 from ganeti import utils
35
36
37 def ListOS(opts, args):
38   """List the valid OSes in the cluster.
39
40   @param opts: the command line options selected by the user
41   @type args: list
42   @param args: should be an empty list
43   @rtype: int
44   @return: the desired exit code
45
46   """
47   op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants"],
48                             names=[])
49   result = SubmitOpCode(op, opts=opts)
50
51   if not result:
52     ToStderr("Can't get the OS list")
53     return 1
54
55   if not opts.no_headers:
56     headers = {"name": "Name"}
57   else:
58     headers = None
59
60   os_names = []
61   for (name, valid, variants) in result:
62     if valid:
63       os_names.extend([[n] for n in CalculateOSNames(name, variants)])
64
65   data = GenerateTable(separator=None, headers=headers, fields=["name"],
66                        data=os_names, units=None)
67
68   for line in data:
69     ToStdout(line)
70
71   return 0
72
73
74 def ShowOSInfo(opts, args):
75   """List detailed information about OSes in the cluster.
76
77   @param opts: the command line options selected by the user
78   @type args: list
79   @param args: should be an empty list
80   @rtype: int
81   @return: the desired exit code
82
83   """
84   op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants",
85                                            "parameters", "api_versions",
86                                            "blacklisted", "hidden"],
87                             names=[])
88   result = SubmitOpCode(op, opts=opts)
89
90   if not result:
91     ToStderr("Can't get the OS list")
92     return 1
93
94   do_filter = bool(args)
95
96   for (name, valid, variants, parameters, api_versions, blk, hid) in result:
97     if do_filter:
98       if name not in args:
99         continue
100       else:
101         args.remove(name)
102     ToStdout("%s:", name)
103     ToStdout("  - valid: %s", valid)
104     ToStdout("  - hidden: %s", hid)
105     ToStdout("  - blacklisted: %s", blk)
106     if valid:
107       ToStdout("  - API versions:")
108       for version in sorted(api_versions):
109         ToStdout("    - %s", version)
110       ToStdout("  - variants:")
111       for vname in variants:
112         ToStdout("    - %s", vname)
113       ToStdout("  - parameters:")
114       for pname, pdesc in parameters:
115         ToStdout("    - %s: %s", pname, pdesc)
116     ToStdout("")
117
118   if args:
119     for name in args:
120       ToStdout("%s: ", name)
121       ToStdout("")
122
123   return 0
124
125
126 def _OsStatus(status, diagnose):
127   """Beautifier function for OS status.
128
129   @type status: boolean
130   @param status: is the OS valid
131   @type diagnose: string
132   @param diagnose: the error message for invalid OSes
133   @rtype: string
134   @return: a formatted status
135
136   """
137   if status:
138     return "valid"
139   else:
140     return "invalid - %s" % diagnose
141
142
143 def DiagnoseOS(opts, args):
144   """Analyse all OSes on this cluster.
145
146   @param opts: the command line options selected by the user
147   @type args: list
148   @param args: should be an empty list
149   @rtype: int
150   @return: the desired exit code
151
152   """
153   op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants",
154                                            "node_status", "hidden",
155                                            "blacklisted"], names=[])
156   result = SubmitOpCode(op, opts=opts)
157
158   if not result:
159     ToStderr("Can't get the OS list")
160     return 1
161
162   has_bad = False
163
164   for os_name, _, os_variants, node_data, hid, blk in result:
165     nodes_valid = {}
166     nodes_bad = {}
167     nodes_hidden = {}
168     for node_name, node_info in node_data.iteritems():
169       nodes_hidden[node_name] = []
170       if node_info: # at least one entry in the per-node list
171         (fo_path, fo_status, fo_msg, fo_variants,
172          fo_params, fo_api) = node_info.pop(0)
173         fo_msg = "%s (path: %s)" % (_OsStatus(fo_status, fo_msg), fo_path)
174         if fo_api:
175           max_os_api = max(fo_api)
176           fo_msg += " [API versions: %s]" % utils.CommaJoin(fo_api)
177         else:
178           max_os_api = 0
179           fo_msg += " [no API versions declared]"
180
181         if max_os_api >= constants.OS_API_V15:
182           if fo_variants:
183             fo_msg += " [variants: %s]" % utils.CommaJoin(fo_variants)
184           else:
185             fo_msg += " [no variants]"
186         if max_os_api >= constants.OS_API_V20:
187           if fo_params:
188             fo_msg += (" [parameters: %s]" %
189                        utils.CommaJoin([v[0] for v in fo_params]))
190           else:
191             fo_msg += " [no parameters]"
192         if fo_status:
193           nodes_valid[node_name] = fo_msg
194         else:
195           nodes_bad[node_name] = fo_msg
196         for hpath, hstatus, hmsg, _, _, _ in node_info:
197           nodes_hidden[node_name].append("    [hidden] path: %s, status: %s" %
198                                          (hpath, _OsStatus(hstatus, hmsg)))
199       else:
200         nodes_bad[node_name] = "OS not found"
201
202     if nodes_valid and not nodes_bad:
203       status = "valid"
204     elif not nodes_valid and nodes_bad:
205       status = "invalid"
206       has_bad = True
207     else:
208       status = "partial valid"
209       has_bad = True
210
211     def _OutputPerNodeOSStatus(msg_map):
212       map_k = utils.NiceSort(msg_map.keys())
213       for node_name in map_k:
214         ToStdout("  Node: %s, status: %s", node_name, msg_map[node_name])
215         for msg in nodes_hidden[node_name]:
216           ToStdout(msg)
217
218     st_msg = "OS: %s [global status: %s]" % (os_name, status)
219     if hid:
220       st_msg += " [hidden]"
221     if blk:
222       st_msg += " [blacklisted]"
223     ToStdout(st_msg)
224     if os_variants:
225       ToStdout("  Variants: [%s]" % utils.CommaJoin(os_variants))
226     _OutputPerNodeOSStatus(nodes_valid)
227     _OutputPerNodeOSStatus(nodes_bad)
228     ToStdout("")
229
230   return int(has_bad)
231
232
233 def ModifyOS(opts, args):
234   """Modify OS parameters for one OS.
235
236   @param opts: the command line options selected by the user
237   @type args: list
238   @param args: should be a list with one entry
239   @rtype: int
240   @return: the desired exit code
241
242   """
243   os = args[0]
244
245   if opts.hvparams:
246     os_hvp = {os: dict(opts.hvparams)}
247   else:
248     os_hvp = None
249
250   if opts.osparams:
251     osp = {os: opts.osparams}
252   else:
253     osp = None
254
255   if not (os_hvp or osp):
256     ToStderr("At least one of OS parameters or hypervisor parameters"
257              " must be passed")
258     return 1
259
260   op = opcodes.OpSetClusterParams(vg_name=None,
261                                   enabled_hypervisors=None,
262                                   hvparams=None,
263                                   beparams=None,
264                                   nicparams=None,
265                                   candidate_pool_size=None,
266                                   os_hvp=os_hvp,
267                                   osparams=osp)
268   SubmitOpCode(op, opts=opts)
269
270   return 0
271
272
273 commands = {
274   'list': (
275     ListOS, ARGS_NONE, [NOHDR_OPT], "", "Lists all valid operating systems"
276     " on the cluster"),
277   'diagnose': (
278     DiagnoseOS, ARGS_NONE, [], "", "Diagnose all operating systems"),
279   'info': (
280     ShowOSInfo, [ArgOs()], [], "", "Show detailed information about "
281     "operating systems"),
282   'modify': (
283     ModifyOS, ARGS_ONE_OS, [HVLIST_OPT, OSPARAMS_OPT, DRY_RUN_OPT], "",
284     "Modify the OS parameters"),
285   }
286
287 if __name__ == '__main__':
288   sys.exit(GenericMain(commands))