Add option to ignore offline node on instance start/stop
[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", "variants"], names=[])
48   result = SubmitOpCode(op, opts=opts)
49
50   if not result:
51     ToStderr("Can't get the OS list")
52     return 1
53
54   if not opts.no_headers:
55     headers = {"name": "Name"}
56   else:
57     headers = None
58
59   os_names = []
60   for (name, variants) in result:
61     os_names.extend([[n] for n in CalculateOSNames(name, variants)])
62
63   data = GenerateTable(separator=None, headers=headers, fields=["name"],
64                        data=os_names, units=None)
65
66   for line in data:
67     ToStdout(line)
68
69   return 0
70
71
72 def ShowOSInfo(opts, args):
73   """List detailed information about OSes in the cluster.
74
75   @param opts: the command line options selected by the user
76   @type args: list
77   @param args: should be an empty list
78   @rtype: int
79   @return: the desired exit code
80
81   """
82   op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants",
83                                            "parameters", "api_versions",
84                                            "blacklisted", "hidden"],
85                             names=[])
86   result = SubmitOpCode(op, opts=opts)
87
88   if not result:
89     ToStderr("Can't get the OS list")
90     return 1
91
92   do_filter = bool(args)
93
94   for (name, valid, variants, parameters, api_versions, blk, hid) in result:
95     if do_filter:
96       if name not in args:
97         continue
98       else:
99         args.remove(name)
100     ToStdout("%s:", name)
101     ToStdout("  - valid: %s", valid)
102     ToStdout("  - hidden: %s", hid)
103     ToStdout("  - blacklisted: %s", blk)
104     if valid:
105       ToStdout("  - API versions:")
106       for version in sorted(api_versions):
107         ToStdout("    - %s", version)
108       ToStdout("  - variants:")
109       for vname in variants:
110         ToStdout("    - %s", vname)
111       ToStdout("  - parameters:")
112       for pname, pdesc in parameters:
113         ToStdout("    - %s: %s", pname, pdesc)
114     ToStdout("")
115
116   if args:
117     for name in args:
118       ToStdout("%s: ", name)
119       ToStdout("")
120
121   return 0
122
123
124 def _OsStatus(status, diagnose):
125   """Beautifier function for OS status.
126
127   @type status: boolean
128   @param status: is the OS valid
129   @type diagnose: string
130   @param diagnose: the error message for invalid OSes
131   @rtype: string
132   @return: a formatted status
133
134   """
135   if status:
136     return "valid"
137   else:
138     return "invalid - %s" % diagnose
139
140
141 def DiagnoseOS(opts, args):
142   """Analyse all OSes on this cluster.
143
144   @param opts: the command line options selected by the user
145   @type args: list
146   @param args: should be an empty list
147   @rtype: int
148   @return: the desired exit code
149
150   """
151   op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants",
152                                            "node_status", "hidden",
153                                            "blacklisted"], names=[])
154   result = SubmitOpCode(op, opts=opts)
155
156   if not result:
157     ToStderr("Can't get the OS list")
158     return 1
159
160   has_bad = False
161
162   for os_name, _, os_variants, node_data, hid, blk in result:
163     nodes_valid = {}
164     nodes_bad = {}
165     nodes_hidden = {}
166     for node_name, node_info in node_data.iteritems():
167       nodes_hidden[node_name] = []
168       if node_info: # at least one entry in the per-node list
169         (fo_path, fo_status, fo_msg, fo_variants,
170          fo_params, fo_api) = node_info.pop(0)
171         fo_msg = "%s (path: %s)" % (_OsStatus(fo_status, fo_msg), fo_path)
172         if fo_api:
173           max_os_api = max(fo_api)
174           fo_msg += " [API versions: %s]" % utils.CommaJoin(fo_api)
175         else:
176           max_os_api = 0
177           fo_msg += " [no API versions declared]"
178
179         if max_os_api >= constants.OS_API_V15:
180           if fo_variants:
181             fo_msg += " [variants: %s]" % utils.CommaJoin(fo_variants)
182           else:
183             fo_msg += " [no variants]"
184         if max_os_api >= constants.OS_API_V20:
185           if fo_params:
186             fo_msg += (" [parameters: %s]" %
187                        utils.CommaJoin([v[0] for v in fo_params]))
188           else:
189             fo_msg += " [no parameters]"
190         if fo_status:
191           nodes_valid[node_name] = fo_msg
192         else:
193           nodes_bad[node_name] = fo_msg
194         for hpath, hstatus, hmsg, _, _, _ in node_info:
195           nodes_hidden[node_name].append("    [hidden] path: %s, status: %s" %
196                                          (hpath, _OsStatus(hstatus, hmsg)))
197       else:
198         nodes_bad[node_name] = "OS not found"
199
200     if nodes_valid and not nodes_bad:
201       status = "valid"
202     elif not nodes_valid and nodes_bad:
203       status = "invalid"
204       has_bad = True
205     else:
206       status = "partial valid"
207       has_bad = True
208
209     def _OutputPerNodeOSStatus(msg_map):
210       map_k = utils.NiceSort(msg_map.keys())
211       for node_name in map_k:
212         ToStdout("  Node: %s, status: %s", node_name, msg_map[node_name])
213         for msg in nodes_hidden[node_name]:
214           ToStdout(msg)
215
216     st_msg = "OS: %s [global status: %s]" % (os_name, status)
217     if hid:
218       st_msg += " [hidden]"
219     if blk:
220       st_msg += " [blacklisted]"
221     ToStdout(st_msg)
222     if os_variants:
223       ToStdout("  Variants: [%s]" % utils.CommaJoin(os_variants))
224     _OutputPerNodeOSStatus(nodes_valid)
225     _OutputPerNodeOSStatus(nodes_bad)
226     ToStdout("")
227
228   return int(has_bad)
229
230
231 def ModifyOS(opts, args):
232   """Modify OS parameters for one OS.
233
234   @param opts: the command line options selected by the user
235   @type args: list
236   @param args: should be a list with one entry
237   @rtype: int
238   @return: the desired exit code
239
240   """
241   os = args[0]
242
243   if opts.hvparams:
244     os_hvp = {os: dict(opts.hvparams)}
245   else:
246     os_hvp = None
247
248   if opts.osparams:
249     osp = {os: opts.osparams}
250   else:
251     osp = None
252
253   if opts.hidden is not None:
254     if opts.hidden:
255       ohid = [(constants.DDM_ADD, os)]
256     else:
257       ohid = [(constants.DDM_REMOVE, os)]
258   else:
259     ohid = None
260
261   if opts.blacklisted is not None:
262     if opts.blacklisted:
263       oblk = [(constants.DDM_ADD, os)]
264     else:
265       oblk = [(constants.DDM_REMOVE, os)]
266   else:
267     oblk = None
268
269   if not (os_hvp or osp or ohid or oblk):
270     ToStderr("At least one of OS parameters or hypervisor parameters"
271              " must be passed")
272     return 1
273
274   op = opcodes.OpSetClusterParams(os_hvp=os_hvp,
275                                   osparams=osp,
276                                   hidden_os=ohid,
277                                   blacklisted_os=oblk)
278   SubmitOpCode(op, opts=opts)
279
280   return 0
281
282
283 commands = {
284   'list': (
285     ListOS, ARGS_NONE, [NOHDR_OPT, PRIORITY_OPT],
286     "", "Lists all valid operating systems on the cluster"),
287   'diagnose': (
288     DiagnoseOS, ARGS_NONE, [PRIORITY_OPT],
289     "", "Diagnose all operating systems"),
290   'info': (
291     ShowOSInfo, [ArgOs()], [PRIORITY_OPT],
292     "", "Show detailed information about "
293     "operating systems"),
294   'modify': (
295     ModifyOS, ARGS_ONE_OS,
296     [HVLIST_OPT, OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT,
297      HID_OS_OPT, BLK_OS_OPT],
298     "", "Modify the OS parameters"),
299   }
300
301 if __name__ == '__main__':
302   sys.exit(GenericMain(commands))