Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-node @ ab3e6da8

History | View | Annotate | Download (21.6 kB)

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