Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-node @ 19b9ba9a

History | View | Annotate | Download (22 kB)

1 a8083063 Iustin Pop
#!/usr/bin/python
2 a8083063 Iustin Pop
#
3 a8083063 Iustin Pop
4 783a6c0b Iustin Pop
# Copyright (C) 2006, 2007, 2008, 2009, 2010 Google Inc.
5 a8083063 Iustin Pop
#
6 a8083063 Iustin Pop
# This program is free software; you can redistribute it and/or modify
7 a8083063 Iustin Pop
# it under the terms of the GNU General Public License as published by
8 a8083063 Iustin Pop
# the Free Software Foundation; either version 2 of the License, or
9 a8083063 Iustin Pop
# (at your option) any later version.
10 a8083063 Iustin Pop
#
11 a8083063 Iustin Pop
# This program is distributed in the hope that it will be useful, but
12 a8083063 Iustin Pop
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 a8083063 Iustin Pop
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 a8083063 Iustin Pop
# General Public License for more details.
15 a8083063 Iustin Pop
#
16 a8083063 Iustin Pop
# You should have received a copy of the GNU General Public License
17 a8083063 Iustin Pop
# along with this program; if not, write to the Free Software
18 a8083063 Iustin Pop
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 a8083063 Iustin Pop
# 02110-1301, USA.
20 a8083063 Iustin Pop
21 7260cfbe Iustin Pop
"""Node related commands"""
22 a8083063 Iustin Pop
23 2d54e29c Iustin Pop
# pylint: disable-msg=W0401,W0613,W0614,C0103
24 2f79bd34 Iustin Pop
# W0401: Wildcard import ganeti.cli
25 2d54e29c Iustin Pop
# W0613: Unused argument, since all functions follow the same API
26 2f79bd34 Iustin Pop
# W0614: Unused import %s from wildcard import (since we need cli)
27 7260cfbe Iustin Pop
# C0103: Invalid name gnt-node
28 2f79bd34 Iustin Pop
29 a8083063 Iustin Pop
import sys
30 a8083063 Iustin Pop
31 a8083063 Iustin Pop
from ganeti.cli import *
32 a8083063 Iustin Pop
from ganeti import opcodes
33 a8083063 Iustin Pop
from ganeti import utils
34 846baef9 Iustin Pop
from ganeti import constants
35 6396164f Guido Trotter
from ganeti import compat
36 c450e9b0 Iustin Pop
from ganeti import errors
37 827f753e Guido Trotter
from ganeti import bootstrap
38 a744b676 Manuel Franceschini
from ganeti import netutils
39 a8083063 Iustin Pop
40 a8083063 Iustin Pop
41 ebf366ee Iustin Pop
#: default list of field for L{ListNodes}
42 48c4dfa8 Iustin Pop
_LIST_DEF_FIELDS = [
43 48c4dfa8 Iustin Pop
  "name", "dtotal", "dfree",
44 48c4dfa8 Iustin Pop
  "mtotal", "mnode", "mfree",
45 48c4dfa8 Iustin Pop
  "pinst_cnt", "sinst_cnt",
46 48c4dfa8 Iustin Pop
  ]
47 48c4dfa8 Iustin Pop
48 620a85fd Iustin Pop
49 a4ebd726 Michael Hanselmann
#: Default field list for L{ListVolumes}
50 a4ebd726 Michael Hanselmann
_LIST_VOL_DEF_FIELDS = ["node", "phys", "vg", "name", "size", "instance"]
51 a4ebd726 Michael Hanselmann
52 a4ebd726 Michael Hanselmann
53 620a85fd Iustin Pop
#: default list of field for L{ListStorage}
54 620a85fd Iustin Pop
_LIST_STOR_DEF_FIELDS = [
55 620a85fd Iustin Pop
  constants.SF_NODE,
56 620a85fd Iustin Pop
  constants.SF_TYPE,
57 620a85fd Iustin Pop
  constants.SF_NAME,
58 620a85fd Iustin Pop
  constants.SF_SIZE,
59 620a85fd Iustin Pop
  constants.SF_USED,
60 620a85fd Iustin Pop
  constants.SF_FREE,
61 620a85fd Iustin Pop
  constants.SF_ALLOCATABLE,
62 620a85fd Iustin Pop
  ]
63 620a85fd Iustin Pop
64 620a85fd Iustin Pop
65 cc3bcec8 Guido Trotter
#: headers (and full field list for L{ListNodes}
66 cc3bcec8 Guido Trotter
_LIST_HEADERS = {
67 cc3bcec8 Guido Trotter
  "name": "Node", "pinst_cnt": "Pinst", "sinst_cnt": "Sinst",
68 cc3bcec8 Guido Trotter
  "pinst_list": "PriInstances", "sinst_list": "SecInstances",
69 cc3bcec8 Guido Trotter
  "pip": "PrimaryIP", "sip": "SecondaryIP",
70 cc3bcec8 Guido Trotter
  "dtotal": "DTotal", "dfree": "DFree",
71 cc3bcec8 Guido Trotter
  "mtotal": "MTotal", "mnode": "MNode", "mfree": "MFree",
72 cc3bcec8 Guido Trotter
  "bootid": "BootID",
73 0105bad3 Iustin Pop
  "ctotal": "CTotal", "cnodes": "CNodes", "csockets": "CSockets",
74 cc3bcec8 Guido Trotter
  "tags": "Tags",
75 cc3bcec8 Guido Trotter
  "serial_no": "SerialNo",
76 cc3bcec8 Guido Trotter
  "master_candidate": "MasterC",
77 cc3bcec8 Guido Trotter
  "master": "IsMaster",
78 0b2454b9 Iustin Pop
  "offline": "Offline", "drained": "Drained",
79 c120ff34 Iustin Pop
  "role": "Role",
80 033d58b0 Iustin Pop
  "ctime": "CTime", "mtime": "MTime", "uuid": "UUID"
81 cc3bcec8 Guido Trotter
  }
82 cc3bcec8 Guido Trotter
83 620a85fd Iustin Pop
84 620a85fd Iustin Pop
#: headers (and full field list for L{ListStorage}
85 620a85fd Iustin Pop
_LIST_STOR_HEADERS = {
86 620a85fd Iustin Pop
  constants.SF_NODE: "Node",
87 620a85fd Iustin Pop
  constants.SF_TYPE: "Type",
88 620a85fd Iustin Pop
  constants.SF_NAME: "Name",
89 620a85fd Iustin Pop
  constants.SF_SIZE: "Size",
90 620a85fd Iustin Pop
  constants.SF_USED: "Used",
91 620a85fd Iustin Pop
  constants.SF_FREE: "Free",
92 620a85fd Iustin Pop
  constants.SF_ALLOCATABLE: "Allocatable",
93 620a85fd Iustin Pop
  }
94 620a85fd Iustin Pop
95 620a85fd Iustin Pop
96 0e89fc2d Michael Hanselmann
#: User-facing storage unit types
97 0e89fc2d Michael Hanselmann
_USER_STORAGE_TYPE = {
98 0e89fc2d Michael Hanselmann
  constants.ST_FILE: "file",
99 0e89fc2d Michael Hanselmann
  constants.ST_LVM_PV: "lvm-pv",
100 0e89fc2d Michael Hanselmann
  constants.ST_LVM_VG: "lvm-vg",
101 0e89fc2d Michael Hanselmann
  }
102 0e89fc2d Michael Hanselmann
103 a8005e17 Michael Hanselmann
_STORAGE_TYPE_OPT = \
104 aeaefce8 Iustin Pop
  cli_option("-t", "--storage-type",
105 a8005e17 Michael Hanselmann
             dest="user_storage_type",
106 a8005e17 Michael Hanselmann
             choices=_USER_STORAGE_TYPE.keys(),
107 a8005e17 Michael Hanselmann
             default=None,
108 a8005e17 Michael Hanselmann
             metavar="STORAGE_TYPE",
109 ab3e6da8 Iustin Pop
             help=("Storage type (%s)" %
110 ab3e6da8 Iustin Pop
                   utils.CommaJoin(_USER_STORAGE_TYPE.keys())))
111 a8005e17 Michael Hanselmann
112 a8005e17 Michael Hanselmann
_REPAIRABLE_STORAGE_TYPES = \
113 a8005e17 Michael Hanselmann
  [st for st, so in constants.VALID_STORAGE_OPERATIONS.iteritems()
114 a8005e17 Michael Hanselmann
   if constants.SO_FIX_CONSISTENCY in so]
115 a8005e17 Michael Hanselmann
116 a8005e17 Michael Hanselmann
_MODIFIABLE_STORAGE_TYPES = constants.MODIFIABLE_STORAGE_FIELDS.keys()
117 a8005e17 Michael Hanselmann
118 51144e33 Michael Hanselmann
119 86f5eae3 Michael Hanselmann
def ConvertStorageType(user_storage_type):
120 86f5eae3 Michael Hanselmann
  """Converts a user storage type to its internal name.
121 86f5eae3 Michael Hanselmann
122 86f5eae3 Michael Hanselmann
  """
123 86f5eae3 Michael Hanselmann
  try:
124 86f5eae3 Michael Hanselmann
    return _USER_STORAGE_TYPE[user_storage_type]
125 86f5eae3 Michael Hanselmann
  except KeyError:
126 debac808 Iustin Pop
    raise errors.OpPrereqError("Unknown storage type: %s" % user_storage_type,
127 debac808 Iustin Pop
                               errors.ECODE_INVAL)
128 86f5eae3 Michael Hanselmann
129 86f5eae3 Michael Hanselmann
130 4331f6cd Michael Hanselmann
@UsesRPC
131 a8083063 Iustin Pop
def AddNode(opts, args):
132 ebf366ee Iustin Pop
  """Add a node to the cluster.
133 ebf366ee Iustin Pop
134 ebf366ee Iustin Pop
  @param opts: the command line options selected by the user
135 ebf366ee Iustin Pop
  @type args: list
136 ebf366ee Iustin Pop
  @param args: should contain only one element, the new node name
137 ebf366ee Iustin Pop
  @rtype: int
138 ebf366ee Iustin Pop
  @return: the desired exit code
139 05ccd983 Guido Trotter
140 05ccd983 Guido Trotter
  """
141 87622829 Iustin Pop
  cl = GetClient()
142 a744b676 Manuel Franceschini
  dns_data = netutils.GetHostInfo(netutils.HostInfo.NormalizeName(args[0]))
143 05ccd983 Guido Trotter
  node = dns_data.name
144 82e12743 Iustin Pop
  readd = opts.readd
145 82e12743 Iustin Pop
146 82e12743 Iustin Pop
  try:
147 82e12743 Iustin Pop
    output = cl.QueryNodes(names=[node], fields=['name', 'sip'],
148 77921a95 Iustin Pop
                           use_locking=False)
149 82e12743 Iustin Pop
    node_exists, sip = output[0]
150 82e12743 Iustin Pop
  except (errors.OpPrereqError, errors.OpExecError):
151 82e12743 Iustin Pop
    node_exists = ""
152 82e12743 Iustin Pop
    sip = None
153 82e12743 Iustin Pop
154 82e12743 Iustin Pop
  if readd:
155 82e12743 Iustin Pop
    if not node_exists:
156 82e12743 Iustin Pop
      ToStderr("Node %s not in the cluster"
157 82e12743 Iustin Pop
               " - please retry without '--readd'", node)
158 82e12743 Iustin Pop
      return 1
159 82e12743 Iustin Pop
  else:
160 82e12743 Iustin Pop
    if node_exists:
161 3a24c527 Iustin Pop
      ToStderr("Node %s already in the cluster (as %s)"
162 82e12743 Iustin Pop
               " - please retry with '--readd'", node, node_exists)
163 05ccd983 Guido Trotter
      return 1
164 82e12743 Iustin Pop
    sip = opts.secondary_ip
165 05ccd983 Guido Trotter
166 87622829 Iustin Pop
  # read the cluster name from the master
167 87622829 Iustin Pop
  output = cl.QueryConfigValues(['cluster_name'])
168 87622829 Iustin Pop
  cluster_name = output[0]
169 87622829 Iustin Pop
170 a8ae3eb5 Iustin Pop
  if not readd:
171 82e12743 Iustin Pop
    ToStderr("-- WARNING -- \n"
172 82e12743 Iustin Pop
             "Performing this operation is going to replace the ssh daemon"
173 82e12743 Iustin Pop
             " keypair\n"
174 82e12743 Iustin Pop
             "on the target machine (%s) with the ones of the"
175 82e12743 Iustin Pop
             " current one\n"
176 82e12743 Iustin Pop
             "and grant full intra-cluster ssh root access to/from it\n", node)
177 05ccd983 Guido Trotter
178 87622829 Iustin Pop
  bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)
179 827f753e Guido Trotter
180 82e12743 Iustin Pop
  op = opcodes.OpAddNode(node_name=args[0], secondary_ip=sip,
181 e7c6e02b Michael Hanselmann
                         readd=opts.readd)
182 400ca2f7 Iustin Pop
  SubmitOpCode(op, opts=opts)
183 a8083063 Iustin Pop
184 a8083063 Iustin Pop
185 a8083063 Iustin Pop
def ListNodes(opts, args):
186 a8083063 Iustin Pop
  """List nodes and their properties.
187 a8083063 Iustin Pop
188 ebf366ee Iustin Pop
  @param opts: the command line options selected by the user
189 ebf366ee Iustin Pop
  @type args: list
190 ebf366ee Iustin Pop
  @param args: should be an empty list
191 ebf366ee Iustin Pop
  @rtype: int
192 ebf366ee Iustin Pop
  @return: the desired exit code
193 ebf366ee Iustin Pop
194 a8083063 Iustin Pop
  """
195 a4ebd726 Michael Hanselmann
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
196 a8083063 Iustin Pop
197 f1de3563 Iustin Pop
  output = GetClient().QueryNodes(args, selected_fields, opts.do_locking)
198 a8083063 Iustin Pop
199 a8083063 Iustin Pop
  if not opts.no_headers:
200 cc3bcec8 Guido Trotter
    headers = _LIST_HEADERS
201 137161c9 Michael Hanselmann
  else:
202 137161c9 Michael Hanselmann
    headers = None
203 137161c9 Michael Hanselmann
204 9fbfbb7b Iustin Pop
  unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"]
205 137161c9 Michael Hanselmann
206 ec223efb Iustin Pop
  numfields = ["dtotal", "dfree",
207 ec223efb Iustin Pop
               "mtotal", "mnode", "mfree",
208 e8a4c138 Iustin Pop
               "pinst_cnt", "sinst_cnt",
209 38d7239a Iustin Pop
               "ctotal", "serial_no"]
210 ec223efb Iustin Pop
211 130a6a6f Iustin Pop
  list_type_fields = ("pinst_list", "sinst_list", "tags")
212 ec223efb Iustin Pop
  # change raw values to nicer strings
213 ec223efb Iustin Pop
  for row in output:
214 ec223efb Iustin Pop
    for idx, field in enumerate(selected_fields):
215 ec223efb Iustin Pop
      val = row[idx]
216 130a6a6f Iustin Pop
      if field in list_type_fields:
217 ec223efb Iustin Pop
        val = ",".join(val)
218 0b2454b9 Iustin Pop
      elif field in ('master', 'master_candidate', 'offline', 'drained'):
219 0e67cdbe Iustin Pop
        if val:
220 0e67cdbe Iustin Pop
          val = 'Y'
221 0e67cdbe Iustin Pop
        else:
222 0e67cdbe Iustin Pop
          val = 'N'
223 90f72445 Iustin Pop
      elif field == "ctime" or field == "mtime":
224 90f72445 Iustin Pop
        val = utils.FormatTime(val)
225 ec223efb Iustin Pop
      elif val is None:
226 ec223efb Iustin Pop
        val = "?"
227 cc5b94db Guido Trotter
      elif opts.roman_integers and isinstance(val, int):
228 6396164f Guido Trotter
        val = compat.TryToRoman(val)
229 ec223efb Iustin Pop
      row[idx] = str(val)
230 137161c9 Michael Hanselmann
231 16be8703 Iustin Pop
  data = GenerateTable(separator=opts.separator, headers=headers,
232 16be8703 Iustin Pop
                       fields=selected_fields, unitfields=unitfields,
233 9fbfbb7b Iustin Pop
                       numfields=numfields, data=output, units=opts.units)
234 16be8703 Iustin Pop
  for line in data:
235 3a24c527 Iustin Pop
    ToStdout(line)
236 a8083063 Iustin Pop
237 a8083063 Iustin Pop
  return 0
238 a8083063 Iustin Pop
239 a8083063 Iustin Pop
240 a5bc662a Iustin Pop
def EvacuateNode(opts, args):
241 a5bc662a Iustin Pop
  """Relocate all secondary instance from a node.
242 a5bc662a Iustin Pop
243 ebf366ee Iustin Pop
  @param opts: the command line options selected by the user
244 ebf366ee Iustin Pop
  @type args: list
245 ebf366ee Iustin Pop
  @param args: should be an empty list
246 ebf366ee Iustin Pop
  @rtype: int
247 ebf366ee Iustin Pop
  @return: the desired exit code
248 ebf366ee Iustin Pop
249 a5bc662a Iustin Pop
  """
250 479636a3 Iustin Pop
  cl = GetClient()
251 a5bc662a Iustin Pop
  force = opts.force
252 c4ed32cb Iustin Pop
253 c4ed32cb Iustin Pop
  dst_node = opts.dst_node
254 c4ed32cb Iustin Pop
  iallocator = opts.iallocator
255 c4ed32cb Iustin Pop
256 f8c9fa5c Iustin Pop
  op = opcodes.OpNodeEvacuationStrategy(nodes=args,
257 f8c9fa5c Iustin Pop
                                        iallocator=iallocator,
258 f8c9fa5c Iustin Pop
                                        remote_node=dst_node)
259 f8c9fa5c Iustin Pop
260 f8c9fa5c Iustin Pop
  result = SubmitOpCode(op, cl=cl, opts=opts)
261 f8c9fa5c Iustin Pop
  if not result:
262 f8c9fa5c Iustin Pop
    # no instances to migrate
263 f8c9fa5c Iustin Pop
    ToStderr("No secondary instances on node(s) %s, exiting.",
264 f8c9fa5c Iustin Pop
             utils.CommaJoin(args))
265 a5bc662a Iustin Pop
    return constants.EXIT_SUCCESS
266 a5bc662a Iustin Pop
267 f8c9fa5c Iustin Pop
  if not force and not AskUser("Relocate instance(s) %s from node(s) %s?" %
268 f8c9fa5c Iustin Pop
                               (",".join("'%s'" % name[0] for name in result),
269 f8c9fa5c Iustin Pop
                               utils.CommaJoin(args))):
270 a5bc662a Iustin Pop
    return constants.EXIT_CONFIRMATION
271 a5bc662a Iustin Pop
272 f8c9fa5c Iustin Pop
  jex = JobExecutor(cl=cl, opts=opts)
273 f8c9fa5c Iustin Pop
  for row in result:
274 f8c9fa5c Iustin Pop
    iname = row[0]
275 f8c9fa5c Iustin Pop
    node = row[1]
276 f8c9fa5c Iustin Pop
    ToStdout("Will relocate instance %s to node %s", iname, node)
277 f8c9fa5c Iustin Pop
    op = opcodes.OpReplaceDisks(instance_name=iname,
278 f8c9fa5c Iustin Pop
                                remote_node=node, disks=[],
279 f8c9fa5c Iustin Pop
                                mode=constants.REPLACE_DISK_CHG,
280 f8c9fa5c Iustin Pop
                                early_release=opts.early_release)
281 f8c9fa5c Iustin Pop
    jex.QueueJob(iname, op)
282 f8c9fa5c Iustin Pop
  results = jex.GetResults()
283 f8c9fa5c Iustin Pop
  bad_cnt = len([row for row in results if not row[0]])
284 f8c9fa5c Iustin Pop
  if bad_cnt == 0:
285 f8c9fa5c Iustin Pop
    ToStdout("All %d instance(s) failed over successfully.", len(results))
286 f8c9fa5c Iustin Pop
    rcode = constants.EXIT_SUCCESS
287 f8c9fa5c Iustin Pop
  else:
288 f8c9fa5c Iustin Pop
    ToStdout("There were errors during the failover:\n"
289 f8c9fa5c Iustin Pop
             "%d error(s) out of %d instance(s).", bad_cnt, len(results))
290 f8c9fa5c Iustin Pop
    rcode = constants.EXIT_FAILURE
291 f8c9fa5c Iustin Pop
  return rcode
292 a5bc662a Iustin Pop
293 a5bc662a Iustin Pop
294 c450e9b0 Iustin Pop
def FailoverNode(opts, args):
295 c450e9b0 Iustin Pop
  """Failover all primary instance on a node.
296 c450e9b0 Iustin Pop
297 ebf366ee Iustin Pop
  @param opts: the command line options selected by the user
298 ebf366ee Iustin Pop
  @type args: list
299 ebf366ee Iustin Pop
  @param args: should be an empty list
300 ebf366ee Iustin Pop
  @rtype: int
301 ebf366ee Iustin Pop
  @return: the desired exit code
302 ebf366ee Iustin Pop
303 c450e9b0 Iustin Pop
  """
304 479636a3 Iustin Pop
  cl = GetClient()
305 c450e9b0 Iustin Pop
  force = opts.force
306 c450e9b0 Iustin Pop
  selected_fields = ["name", "pinst_list"]
307 c450e9b0 Iustin Pop
308 2e7b8369 Iustin Pop
  # these fields are static data anyway, so it doesn't matter, but
309 2e7b8369 Iustin Pop
  # locking=True should be safer
310 2e7b8369 Iustin Pop
  result = cl.QueryNodes(names=args, fields=selected_fields,
311 77921a95 Iustin Pop
                         use_locking=False)
312 c450e9b0 Iustin Pop
  node, pinst = result[0]
313 c450e9b0 Iustin Pop
314 c450e9b0 Iustin Pop
  if not pinst:
315 3a24c527 Iustin Pop
    ToStderr("No primary instances on node %s, exiting.", node)
316 c450e9b0 Iustin Pop
    return 0
317 c450e9b0 Iustin Pop
318 c450e9b0 Iustin Pop
  pinst = utils.NiceSort(pinst)
319 c450e9b0 Iustin Pop
320 c450e9b0 Iustin Pop
  retcode = 0
321 c450e9b0 Iustin Pop
322 c450e9b0 Iustin Pop
  if not force and not AskUser("Fail over instance(s) %s?" %
323 c450e9b0 Iustin Pop
                               (",".join("'%s'" % name for name in pinst))):
324 c450e9b0 Iustin Pop
    return 2
325 c450e9b0 Iustin Pop
326 cb573a31 Iustin Pop
  jex = JobExecutor(cl=cl, opts=opts)
327 c450e9b0 Iustin Pop
  for iname in pinst:
328 c450e9b0 Iustin Pop
    op = opcodes.OpFailoverInstance(instance_name=iname,
329 c450e9b0 Iustin Pop
                                    ignore_consistency=opts.ignore_consistency)
330 479636a3 Iustin Pop
    jex.QueueJob(iname, op)
331 479636a3 Iustin Pop
  results = jex.GetResults()
332 479636a3 Iustin Pop
  bad_cnt = len([row for row in results if not row[0]])
333 479636a3 Iustin Pop
  if bad_cnt == 0:
334 479636a3 Iustin Pop
    ToStdout("All %d instance(s) failed over successfully.", len(results))
335 c450e9b0 Iustin Pop
  else:
336 3a24c527 Iustin Pop
    ToStdout("There were errors during the failover:\n"
337 479636a3 Iustin Pop
             "%d error(s) out of %d instance(s).", bad_cnt, len(results))
338 c450e9b0 Iustin Pop
  return retcode
339 c450e9b0 Iustin Pop
340 c450e9b0 Iustin Pop
341 40ef0ed6 Iustin Pop
def MigrateNode(opts, args):
342 40ef0ed6 Iustin Pop
  """Migrate all primary instance on a node.
343 40ef0ed6 Iustin Pop
344 40ef0ed6 Iustin Pop
  """
345 40ef0ed6 Iustin Pop
  cl = GetClient()
346 40ef0ed6 Iustin Pop
  force = opts.force
347 40ef0ed6 Iustin Pop
  selected_fields = ["name", "pinst_list"]
348 40ef0ed6 Iustin Pop
349 77921a95 Iustin Pop
  result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
350 40ef0ed6 Iustin Pop
  node, pinst = result[0]
351 40ef0ed6 Iustin Pop
352 40ef0ed6 Iustin Pop
  if not pinst:
353 40ef0ed6 Iustin Pop
    ToStdout("No primary instances on node %s, exiting." % node)
354 40ef0ed6 Iustin Pop
    return 0
355 40ef0ed6 Iustin Pop
356 40ef0ed6 Iustin Pop
  pinst = utils.NiceSort(pinst)
357 40ef0ed6 Iustin Pop
358 40ef0ed6 Iustin Pop
  if not force and not AskUser("Migrate instance(s) %s?" %
359 40ef0ed6 Iustin Pop
                               (",".join("'%s'" % name for name in pinst))):
360 40ef0ed6 Iustin Pop
    return 2
361 40ef0ed6 Iustin Pop
362 e71b9ef4 Iustin Pop
  # this should be removed once --non-live is deprecated
363 783a6c0b Iustin Pop
  if not opts.live and opts.migration_mode is not None:
364 e71b9ef4 Iustin Pop
    raise errors.OpPrereqError("Only one of the --non-live and "
365 783a6c0b Iustin Pop
                               "--migration-mode options can be passed",
366 e71b9ef4 Iustin Pop
                               errors.ECODE_INVAL)
367 e71b9ef4 Iustin Pop
  if not opts.live: # --non-live passed
368 8c35561f Iustin Pop
    mode = constants.HT_MIGRATION_NONLIVE
369 e71b9ef4 Iustin Pop
  else:
370 8c35561f Iustin Pop
    mode = opts.migration_mode
371 8c35561f Iustin Pop
  op = opcodes.OpMigrateNode(node_name=args[0], mode=mode)
372 400ca2f7 Iustin Pop
  SubmitOpCode(op, cl=cl, opts=opts)
373 40ef0ed6 Iustin Pop
374 40ef0ed6 Iustin Pop
375 a8083063 Iustin Pop
def ShowNodeConfig(opts, args):
376 a8083063 Iustin Pop
  """Show node information.
377 a8083063 Iustin Pop
378 ebf366ee Iustin Pop
  @param opts: the command line options selected by the user
379 ebf366ee Iustin Pop
  @type args: list
380 ebf366ee Iustin Pop
  @param args: should either be an empty list, in which case
381 ebf366ee Iustin Pop
      we show information about all nodes, or should contain
382 ebf366ee Iustin Pop
      a list of nodes to be queried for information
383 ebf366ee Iustin Pop
  @rtype: int
384 ebf366ee Iustin Pop
  @return: the desired exit code
385 ebf366ee Iustin Pop
386 a8083063 Iustin Pop
  """
387 2e7b8369 Iustin Pop
  cl = GetClient()
388 2e7b8369 Iustin Pop
  result = cl.QueryNodes(fields=["name", "pip", "sip",
389 0b2454b9 Iustin Pop
                                 "pinst_list", "sinst_list",
390 0b2454b9 Iustin Pop
                                 "master_candidate", "drained", "offline"],
391 77921a95 Iustin Pop
                         names=args, use_locking=False)
392 a8083063 Iustin Pop
393 0b2454b9 Iustin Pop
  for (name, primary_ip, secondary_ip, pinst, sinst,
394 0b2454b9 Iustin Pop
       is_mc, drained, offline) in result:
395 3a24c527 Iustin Pop
    ToStdout("Node name: %s", name)
396 3a24c527 Iustin Pop
    ToStdout("  primary ip: %s", primary_ip)
397 3a24c527 Iustin Pop
    ToStdout("  secondary ip: %s", secondary_ip)
398 0b2454b9 Iustin Pop
    ToStdout("  master candidate: %s", is_mc)
399 0b2454b9 Iustin Pop
    ToStdout("  drained: %s", drained)
400 0b2454b9 Iustin Pop
    ToStdout("  offline: %s", offline)
401 a8083063 Iustin Pop
    if pinst:
402 3a24c527 Iustin Pop
      ToStdout("  primary for instances:")
403 ae07a1d3 Iustin Pop
      for iname in utils.NiceSort(pinst):
404 3a24c527 Iustin Pop
        ToStdout("    - %s", iname)
405 a8083063 Iustin Pop
    else:
406 3a24c527 Iustin Pop
      ToStdout("  primary for no instances")
407 a8083063 Iustin Pop
    if sinst:
408 3a24c527 Iustin Pop
      ToStdout("  secondary for instances:")
409 ae07a1d3 Iustin Pop
      for iname in utils.NiceSort(sinst):
410 3a24c527 Iustin Pop
        ToStdout("    - %s", iname)
411 a8083063 Iustin Pop
    else:
412 3a24c527 Iustin Pop
      ToStdout("  secondary for no instances")
413 a8083063 Iustin Pop
414 a8083063 Iustin Pop
  return 0
415 a8083063 Iustin Pop
416 a8083063 Iustin Pop
417 a8083063 Iustin Pop
def RemoveNode(opts, args):
418 ebf366ee Iustin Pop
  """Remove a node from the cluster.
419 ebf366ee Iustin Pop
420 ebf366ee Iustin Pop
  @param opts: the command line options selected by the user
421 ebf366ee Iustin Pop
  @type args: list
422 ebf366ee Iustin Pop
  @param args: should contain only one element, the name of
423 ebf366ee Iustin Pop
      the node to be removed
424 ebf366ee Iustin Pop
  @rtype: int
425 ebf366ee Iustin Pop
  @return: the desired exit code
426 ebf366ee Iustin Pop
427 ebf366ee Iustin Pop
  """
428 a8083063 Iustin Pop
  op = opcodes.OpRemoveNode(node_name=args[0])
429 400ca2f7 Iustin Pop
  SubmitOpCode(op, opts=opts)
430 ebf366ee Iustin Pop
  return 0
431 a8083063 Iustin Pop
432 a8083063 Iustin Pop
433 f5118ade Iustin Pop
def PowercycleNode(opts, args):
434 f5118ade Iustin Pop
  """Remove a node from the cluster.
435 f5118ade Iustin Pop
436 f5118ade Iustin Pop
  @param opts: the command line options selected by the user
437 f5118ade Iustin Pop
  @type args: list
438 f5118ade Iustin Pop
  @param args: should contain only one element, the name of
439 f5118ade Iustin Pop
      the node to be removed
440 f5118ade Iustin Pop
  @rtype: int
441 f5118ade Iustin Pop
  @return: the desired exit code
442 f5118ade Iustin Pop
443 f5118ade Iustin Pop
  """
444 f5118ade Iustin Pop
  node = args[0]
445 f5118ade Iustin Pop
  if (not opts.confirm and
446 f5118ade Iustin Pop
      not AskUser("Are you sure you want to hard powercycle node %s?" % node)):
447 f5118ade Iustin Pop
    return 2
448 f5118ade Iustin Pop
449 f5118ade Iustin Pop
  op = opcodes.OpPowercycleNode(node_name=node, force=opts.force)
450 400ca2f7 Iustin Pop
  result = SubmitOpCode(op, opts=opts)
451 48418fea Iustin Pop
  if result:
452 48418fea Iustin Pop
    ToStderr(result)
453 f5118ade Iustin Pop
  return 0
454 f5118ade Iustin Pop
455 f5118ade Iustin Pop
456 dcb93971 Michael Hanselmann
def ListVolumes(opts, args):
457 dcb93971 Michael Hanselmann
  """List logical volumes on node(s).
458 dcb93971 Michael Hanselmann
459 ebf366ee Iustin Pop
  @param opts: the command line options selected by the user
460 ebf366ee Iustin Pop
  @type args: list
461 ebf366ee Iustin Pop
  @param args: should either be an empty list, in which case
462 ebf366ee Iustin Pop
      we list data for all nodes, or contain a list of nodes
463 ebf366ee Iustin Pop
      to display data only for those
464 ebf366ee Iustin Pop
  @rtype: int
465 ebf366ee Iustin Pop
  @return: the desired exit code
466 ebf366ee Iustin Pop
467 dcb93971 Michael Hanselmann
  """
468 a4ebd726 Michael Hanselmann
  selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
469 dcb93971 Michael Hanselmann
470 dcb93971 Michael Hanselmann
  op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
471 400ca2f7 Iustin Pop
  output = SubmitOpCode(op, opts=opts)
472 dcb93971 Michael Hanselmann
473 dcb93971 Michael Hanselmann
  if not opts.no_headers:
474 137161c9 Michael Hanselmann
    headers = {"node": "Node", "phys": "PhysDev",
475 137161c9 Michael Hanselmann
               "vg": "VG", "name": "Name",
476 137161c9 Michael Hanselmann
               "size": "Size", "instance": "Instance"}
477 137161c9 Michael Hanselmann
  else:
478 137161c9 Michael Hanselmann
    headers = None
479 137161c9 Michael Hanselmann
480 9fbfbb7b Iustin Pop
  unitfields = ["size"]
481 137161c9 Michael Hanselmann
482 137161c9 Michael Hanselmann
  numfields = ["size"]
483 137161c9 Michael Hanselmann
484 16be8703 Iustin Pop
  data = GenerateTable(separator=opts.separator, headers=headers,
485 16be8703 Iustin Pop
                       fields=selected_fields, unitfields=unitfields,
486 9fbfbb7b Iustin Pop
                       numfields=numfields, data=output, units=opts.units)
487 16be8703 Iustin Pop
488 16be8703 Iustin Pop
  for line in data:
489 3a24c527 Iustin Pop
    ToStdout(line)
490 dcb93971 Michael Hanselmann
491 dcb93971 Michael Hanselmann
  return 0
492 dcb93971 Michael Hanselmann
493 dcb93971 Michael Hanselmann
494 9b94905f Iustin Pop
def ListStorage(opts, args):
495 4007f57d Michael Hanselmann
  """List physical volumes on node(s).
496 4007f57d Michael Hanselmann
497 4007f57d Michael Hanselmann
  @param opts: the command line options selected by the user
498 4007f57d Michael Hanselmann
  @type args: list
499 4007f57d Michael Hanselmann
  @param args: should either be an empty list, in which case
500 4007f57d Michael Hanselmann
      we list data for all nodes, or contain a list of nodes
501 4007f57d Michael Hanselmann
      to display data only for those
502 4007f57d Michael Hanselmann
  @rtype: int
503 4007f57d Michael Hanselmann
  @return: the desired exit code
504 4007f57d Michael Hanselmann
505 4007f57d Michael Hanselmann
  """
506 53548798 Michael Hanselmann
  # TODO: Default to ST_FILE if LVM is disabled on the cluster
507 53548798 Michael Hanselmann
  if opts.user_storage_type is None:
508 53548798 Michael Hanselmann
    opts.user_storage_type = constants.ST_LVM_PV
509 53548798 Michael Hanselmann
510 86f5eae3 Michael Hanselmann
  storage_type = ConvertStorageType(opts.user_storage_type)
511 53548798 Michael Hanselmann
512 a4ebd726 Michael Hanselmann
  selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
513 4007f57d Michael Hanselmann
514 4007f57d Michael Hanselmann
  op = opcodes.OpQueryNodeStorage(nodes=args,
515 53548798 Michael Hanselmann
                                  storage_type=storage_type,
516 4007f57d Michael Hanselmann
                                  output_fields=selected_fields)
517 400ca2f7 Iustin Pop
  output = SubmitOpCode(op, opts=opts)
518 4007f57d Michael Hanselmann
519 4007f57d Michael Hanselmann
  if not opts.no_headers:
520 4007f57d Michael Hanselmann
    headers = {
521 620a85fd Iustin Pop
      constants.SF_NODE: "Node",
522 620a85fd Iustin Pop
      constants.SF_TYPE: "Type",
523 4007f57d Michael Hanselmann
      constants.SF_NAME: "Name",
524 4007f57d Michael Hanselmann
      constants.SF_SIZE: "Size",
525 4007f57d Michael Hanselmann
      constants.SF_USED: "Used",
526 4007f57d Michael Hanselmann
      constants.SF_FREE: "Free",
527 4007f57d Michael Hanselmann
      constants.SF_ALLOCATABLE: "Allocatable",
528 4007f57d Michael Hanselmann
      }
529 4007f57d Michael Hanselmann
  else:
530 4007f57d Michael Hanselmann
    headers = None
531 4007f57d Michael Hanselmann
532 4007f57d Michael Hanselmann
  unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
533 4007f57d Michael Hanselmann
  numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
534 4007f57d Michael Hanselmann
535 dc09c3cf Iustin Pop
  # change raw values to nicer strings
536 dc09c3cf Iustin Pop
  for row in output:
537 dc09c3cf Iustin Pop
    for idx, field in enumerate(selected_fields):
538 dc09c3cf Iustin Pop
      val = row[idx]
539 dc09c3cf Iustin Pop
      if field == constants.SF_ALLOCATABLE:
540 dc09c3cf Iustin Pop
        if val:
541 dc09c3cf Iustin Pop
          val = "Y"
542 dc09c3cf Iustin Pop
        else:
543 dc09c3cf Iustin Pop
          val = "N"
544 dc09c3cf Iustin Pop
      row[idx] = str(val)
545 dc09c3cf Iustin Pop
546 4007f57d Michael Hanselmann
  data = GenerateTable(separator=opts.separator, headers=headers,
547 4007f57d Michael Hanselmann
                       fields=selected_fields, unitfields=unitfields,
548 4007f57d Michael Hanselmann
                       numfields=numfields, data=output, units=opts.units)
549 4007f57d Michael Hanselmann
550 4007f57d Michael Hanselmann
  for line in data:
551 4007f57d Michael Hanselmann
    ToStdout(line)
552 4007f57d Michael Hanselmann
553 4007f57d Michael Hanselmann
  return 0
554 4007f57d Michael Hanselmann
555 4007f57d Michael Hanselmann
556 9b94905f Iustin Pop
def ModifyStorage(opts, args):
557 0e89fc2d Michael Hanselmann
  """Modify storage volume on a node.
558 0e89fc2d Michael Hanselmann
559 0e89fc2d Michael Hanselmann
  @param opts: the command line options selected by the user
560 0e89fc2d Michael Hanselmann
  @type args: list
561 0e89fc2d Michael Hanselmann
  @param args: should contain 3 items: node name, storage type and volume name
562 0e89fc2d Michael Hanselmann
  @rtype: int
563 0e89fc2d Michael Hanselmann
  @return: the desired exit code
564 0e89fc2d Michael Hanselmann
565 0e89fc2d Michael Hanselmann
  """
566 0e89fc2d Michael Hanselmann
  (node_name, user_storage_type, volume_name) = args
567 0e89fc2d Michael Hanselmann
568 86f5eae3 Michael Hanselmann
  storage_type = ConvertStorageType(user_storage_type)
569 0e89fc2d Michael Hanselmann
570 0e89fc2d Michael Hanselmann
  changes = {}
571 0e89fc2d Michael Hanselmann
572 0e89fc2d Michael Hanselmann
  if opts.allocatable is not None:
573 e7b61bb0 Iustin Pop
    changes[constants.SF_ALLOCATABLE] = opts.allocatable
574 0e89fc2d Michael Hanselmann
575 0e89fc2d Michael Hanselmann
  if changes:
576 0e89fc2d Michael Hanselmann
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
577 0e89fc2d Michael Hanselmann
                                     storage_type=storage_type,
578 0e89fc2d Michael Hanselmann
                                     name=volume_name,
579 0e89fc2d Michael Hanselmann
                                     changes=changes)
580 400ca2f7 Iustin Pop
    SubmitOpCode(op, opts=opts)
581 620a85fd Iustin Pop
  else:
582 620a85fd Iustin Pop
    ToStderr("No changes to perform, exiting.")
583 0e89fc2d Michael Hanselmann
584 0e89fc2d Michael Hanselmann
585 9b94905f Iustin Pop
def RepairStorage(opts, args):
586 1e3463f1 Michael Hanselmann
  """Repairs a storage volume on a node.
587 1e3463f1 Michael Hanselmann
588 1e3463f1 Michael Hanselmann
  @param opts: the command line options selected by the user
589 1e3463f1 Michael Hanselmann
  @type args: list
590 1e3463f1 Michael Hanselmann
  @param args: should contain 3 items: node name, storage type and volume name
591 1e3463f1 Michael Hanselmann
  @rtype: int
592 1e3463f1 Michael Hanselmann
  @return: the desired exit code
593 1e3463f1 Michael Hanselmann
594 1e3463f1 Michael Hanselmann
  """
595 1e3463f1 Michael Hanselmann
  (node_name, user_storage_type, volume_name) = args
596 1e3463f1 Michael Hanselmann
597 1e3463f1 Michael Hanselmann
  storage_type = ConvertStorageType(user_storage_type)
598 1e3463f1 Michael Hanselmann
599 1e3463f1 Michael Hanselmann
  op = opcodes.OpRepairNodeStorage(node_name=node_name,
600 1e3463f1 Michael Hanselmann
                                   storage_type=storage_type,
601 7e9c6a78 Iustin Pop
                                   name=volume_name,
602 7e9c6a78 Iustin Pop
                                   ignore_consistency=opts.ignore_consistency)
603 400ca2f7 Iustin Pop
  SubmitOpCode(op, opts=opts)
604 1e3463f1 Michael Hanselmann
605 1e3463f1 Michael Hanselmann
606 b31c8676 Iustin Pop
def SetNodeParams(opts, args):
607 b31c8676 Iustin Pop
  """Modifies a node.
608 b31c8676 Iustin Pop
609 b31c8676 Iustin Pop
  @param opts: the command line options selected by the user
610 b31c8676 Iustin Pop
  @type args: list
611 b31c8676 Iustin Pop
  @param args: should contain only one element, the node name
612 b31c8676 Iustin Pop
  @rtype: int
613 b31c8676 Iustin Pop
  @return: the desired exit code
614 b31c8676 Iustin Pop
615 b31c8676 Iustin Pop
  """
616 c9d443ea Iustin Pop
  if [opts.master_candidate, opts.drained, opts.offline].count(None) == 3:
617 b31c8676 Iustin Pop
    ToStderr("Please give at least one of the parameters.")
618 b31c8676 Iustin Pop
    return 1
619 b31c8676 Iustin Pop
620 b31c8676 Iustin Pop
  op = opcodes.OpSetNodeParams(node_name=args[0],
621 e7b61bb0 Iustin Pop
                               master_candidate=opts.master_candidate,
622 e7b61bb0 Iustin Pop
                               offline=opts.offline,
623 e7b61bb0 Iustin Pop
                               drained=opts.drained,
624 4c61d894 Iustin Pop
                               force=opts.force,
625 4c61d894 Iustin Pop
                               auto_promote=opts.auto_promote)
626 b31c8676 Iustin Pop
627 b31c8676 Iustin Pop
  # even if here we process the result, we allow submit only
628 b31c8676 Iustin Pop
  result = SubmitOrSend(op, opts)
629 b31c8676 Iustin Pop
630 b31c8676 Iustin Pop
  if result:
631 b31c8676 Iustin Pop
    ToStdout("Modified node %s", args[0])
632 b31c8676 Iustin Pop
    for param, data in result:
633 b31c8676 Iustin Pop
      ToStdout(" - %-5s -> %s", param, data)
634 b31c8676 Iustin Pop
  return 0
635 b31c8676 Iustin Pop
636 b31c8676 Iustin Pop
637 a8083063 Iustin Pop
commands = {
638 6ea815cf Iustin Pop
  'add': (
639 6ea815cf Iustin Pop
    AddNode, [ArgHost(min=1, max=1)],
640 064c21f8 Iustin Pop
    [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT],
641 6ea815cf Iustin Pop
    "[-s ip] [--readd] [--no-ssh-key-check] <node_name>",
642 6ea815cf Iustin Pop
    "Add a node to the cluster"),
643 6ea815cf Iustin Pop
  'evacuate': (
644 f8c9fa5c Iustin Pop
    EvacuateNode, [ArgNode(min=1)],
645 f8c9fa5c Iustin Pop
    [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT],
646 6ea815cf Iustin Pop
    "[-f] {-I <iallocator> | -n <dst>} <node>",
647 6ea815cf Iustin Pop
    "Relocate the secondary instances from a node"
648 6ea815cf Iustin Pop
    " to other nodes (only for instances with drbd disk template)"),
649 6ea815cf Iustin Pop
  'failover': (
650 064c21f8 Iustin Pop
    FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT],
651 6ea815cf Iustin Pop
    "[-f] <node>",
652 6ea815cf Iustin Pop
    "Stops the primary instances on a node and start them on their"
653 6ea815cf Iustin Pop
    " secondary node (only for instances with drbd disk template)"),
654 6ea815cf Iustin Pop
  'migrate': (
655 783a6c0b Iustin Pop
    MigrateNode, ARGS_ONE_NODE, [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT],
656 6ea815cf Iustin Pop
    "[-f] <node>",
657 6ea815cf Iustin Pop
    "Migrate all the primary instance on a node away from it"
658 6ea815cf Iustin Pop
    " (only for instances of type drbd)"),
659 6ea815cf Iustin Pop
  'info': (
660 064c21f8 Iustin Pop
    ShowNodeConfig, ARGS_MANY_NODES, [],
661 6ea815cf Iustin Pop
    "[<node_name>...]", "Show information about the node(s)"),
662 6ea815cf Iustin Pop
  'list': (
663 6ea815cf Iustin Pop
    ListNodes, ARGS_MANY_NODES,
664 31d97b2a Guido Trotter
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT, ROMAN_OPT],
665 6ea815cf Iustin Pop
    "[nodes...]",
666 6ea815cf Iustin Pop
    "Lists the nodes in the cluster. The available fields are (see the man"
667 6ea815cf Iustin Pop
    " page for details): %s. The default field list is (in order): %s." %
668 1f864b60 Iustin Pop
    (utils.CommaJoin(_LIST_HEADERS), utils.CommaJoin(_LIST_DEF_FIELDS))),
669 6ea815cf Iustin Pop
  'modify': (
670 6ea815cf Iustin Pop
    SetNodeParams, ARGS_ONE_NODE,
671 4c61d894 Iustin Pop
    [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
672 db5a8a2d Iustin Pop
     AUTO_PROMOTE_OPT, DRY_RUN_OPT],
673 6ea815cf Iustin Pop
    "<node_name>", "Alters the parameters of a node"),
674 6ea815cf Iustin Pop
  'powercycle': (
675 6ea815cf Iustin Pop
    PowercycleNode, ARGS_ONE_NODE,
676 db5a8a2d Iustin Pop
    [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT],
677 6ea815cf Iustin Pop
    "<node_name>", "Tries to forcefully powercycle a node"),
678 6ea815cf Iustin Pop
  'remove': (
679 db5a8a2d Iustin Pop
    RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT],
680 6ea815cf Iustin Pop
    "<node_name>", "Removes a node from the cluster"),
681 6ea815cf Iustin Pop
  'volumes': (
682 6ea815cf Iustin Pop
    ListVolumes, [ArgNode()],
683 064c21f8 Iustin Pop
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
684 6ea815cf Iustin Pop
    "[<node_name>...]", "List logical volumes on node(s)"),
685 9b94905f Iustin Pop
  'list-storage': (
686 9b94905f Iustin Pop
    ListStorage, ARGS_MANY_NODES,
687 064c21f8 Iustin Pop
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT],
688 620a85fd Iustin Pop
    "[<node_name>...]", "List physical volumes on node(s). The available"
689 620a85fd Iustin Pop
    " fields are (see the man page for details): %s." %
690 1f864b60 Iustin Pop
    (utils.CommaJoin(_LIST_STOR_HEADERS))),
691 9b94905f Iustin Pop
  'modify-storage': (
692 9b94905f Iustin Pop
    ModifyStorage,
693 6ea815cf Iustin Pop
    [ArgNode(min=1, max=1),
694 6ea815cf Iustin Pop
     ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
695 6ea815cf Iustin Pop
     ArgFile(min=1, max=1)],
696 db5a8a2d Iustin Pop
    [ALLOCATABLE_OPT, DRY_RUN_OPT],
697 064c21f8 Iustin Pop
    "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
698 9b94905f Iustin Pop
  'repair-storage': (
699 9b94905f Iustin Pop
    RepairStorage,
700 6ea815cf Iustin Pop
    [ArgNode(min=1, max=1),
701 6ea815cf Iustin Pop
     ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
702 6ea815cf Iustin Pop
     ArgFile(min=1, max=1)],
703 db5a8a2d Iustin Pop
    [IGNORE_CONSIST_OPT, DRY_RUN_OPT],
704 6ea815cf Iustin Pop
    "<node_name> <storage_type> <name>",
705 6ea815cf Iustin Pop
    "Repairs a storage volume on a node"),
706 6ea815cf Iustin Pop
  'list-tags': (
707 064c21f8 Iustin Pop
    ListTags, ARGS_ONE_NODE, [],
708 6ea815cf Iustin Pop
    "<node_name>", "List the tags of the given node"),
709 6ea815cf Iustin Pop
  'add-tags': (
710 064c21f8 Iustin Pop
    AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT],
711 6ea815cf Iustin Pop
    "<node_name> tag...", "Add tags to the given node"),
712 6ea815cf Iustin Pop
  'remove-tags': (
713 064c21f8 Iustin Pop
    RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT],
714 6ea815cf Iustin Pop
    "<node_name> tag...", "Remove tags from the given node"),
715 a8083063 Iustin Pop
  }
716 a8083063 Iustin Pop
717 a8083063 Iustin Pop
718 a8083063 Iustin Pop
if __name__ == '__main__':
719 846baef9 Iustin Pop
  sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_NODE}))