Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-node @ debac808

History | View | Annotate | Download (21.4 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 6915bc28 Guido Trotter
             help=("Storage type (%s)" % " ,".join(_USER_STORAGE_TYPE.keys())))
102 a8005e17 Michael Hanselmann
103 a8005e17 Michael Hanselmann
_REPAIRABLE_STORAGE_TYPES = \
104 a8005e17 Michael Hanselmann
  [st for st, so in constants.VALID_STORAGE_OPERATIONS.iteritems()
105 a8005e17 Michael Hanselmann
   if constants.SO_FIX_CONSISTENCY in so]
106 a8005e17 Michael Hanselmann
107 a8005e17 Michael Hanselmann
_MODIFIABLE_STORAGE_TYPES = constants.MODIFIABLE_STORAGE_FIELDS.keys()
108 a8005e17 Michael Hanselmann
109 51144e33 Michael Hanselmann
110 86f5eae3 Michael Hanselmann
def ConvertStorageType(user_storage_type):
111 86f5eae3 Michael Hanselmann
  """Converts a user storage type to its internal name.
112 86f5eae3 Michael Hanselmann
113 86f5eae3 Michael Hanselmann
  """
114 86f5eae3 Michael Hanselmann
  try:
115 86f5eae3 Michael Hanselmann
    return _USER_STORAGE_TYPE[user_storage_type]
116 86f5eae3 Michael Hanselmann
  except KeyError:
117 debac808 Iustin Pop
    raise errors.OpPrereqError("Unknown storage type: %s" % user_storage_type,
118 debac808 Iustin Pop
                               errors.ECODE_INVAL)
119 86f5eae3 Michael Hanselmann
120 86f5eae3 Michael Hanselmann
121 4331f6cd Michael Hanselmann
@UsesRPC
122 a8083063 Iustin Pop
def AddNode(opts, args):
123 ebf366ee Iustin Pop
  """Add a node to the cluster.
124 ebf366ee Iustin Pop
125 ebf366ee Iustin Pop
  @param opts: the command line options selected by the user
126 ebf366ee Iustin Pop
  @type args: list
127 ebf366ee Iustin Pop
  @param args: should contain only one element, the new node name
128 ebf366ee Iustin Pop
  @rtype: int
129 ebf366ee Iustin Pop
  @return: the desired exit code
130 05ccd983 Guido Trotter
131 05ccd983 Guido Trotter
  """
132 87622829 Iustin Pop
  cl = GetClient()
133 05ccd983 Guido Trotter
  dns_data = utils.HostInfo(args[0])
134 05ccd983 Guido Trotter
  node = dns_data.name
135 82e12743 Iustin Pop
  readd = opts.readd
136 82e12743 Iustin Pop
137 82e12743 Iustin Pop
  try:
138 82e12743 Iustin Pop
    output = cl.QueryNodes(names=[node], fields=['name', 'sip'],
139 77921a95 Iustin Pop
                           use_locking=False)
140 82e12743 Iustin Pop
    node_exists, sip = output[0]
141 82e12743 Iustin Pop
  except (errors.OpPrereqError, errors.OpExecError):
142 82e12743 Iustin Pop
    node_exists = ""
143 82e12743 Iustin Pop
    sip = None
144 82e12743 Iustin Pop
145 82e12743 Iustin Pop
  if readd:
146 82e12743 Iustin Pop
    if not node_exists:
147 82e12743 Iustin Pop
      ToStderr("Node %s not in the cluster"
148 82e12743 Iustin Pop
               " - please retry without '--readd'", node)
149 82e12743 Iustin Pop
      return 1
150 82e12743 Iustin Pop
  else:
151 82e12743 Iustin Pop
    if node_exists:
152 3a24c527 Iustin Pop
      ToStderr("Node %s already in the cluster (as %s)"
153 82e12743 Iustin Pop
               " - please retry with '--readd'", node, node_exists)
154 05ccd983 Guido Trotter
      return 1
155 82e12743 Iustin Pop
    sip = opts.secondary_ip
156 05ccd983 Guido Trotter
157 87622829 Iustin Pop
  # read the cluster name from the master
158 87622829 Iustin Pop
  output = cl.QueryConfigValues(['cluster_name'])
159 87622829 Iustin Pop
  cluster_name = output[0]
160 87622829 Iustin Pop
161 a8ae3eb5 Iustin Pop
  if not readd:
162 82e12743 Iustin Pop
    ToStderr("-- WARNING -- \n"
163 82e12743 Iustin Pop
             "Performing this operation is going to replace the ssh daemon"
164 82e12743 Iustin Pop
             " keypair\n"
165 82e12743 Iustin Pop
             "on the target machine (%s) with the ones of the"
166 82e12743 Iustin Pop
             " current one\n"
167 82e12743 Iustin Pop
             "and grant full intra-cluster ssh root access to/from it\n", node)
168 05ccd983 Guido Trotter
169 87622829 Iustin Pop
  bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)
170 827f753e Guido Trotter
171 82e12743 Iustin Pop
  op = opcodes.OpAddNode(node_name=args[0], secondary_ip=sip,
172 e7c6e02b Michael Hanselmann
                         readd=opts.readd)
173 a8083063 Iustin Pop
  SubmitOpCode(op)
174 a8083063 Iustin Pop
175 a8083063 Iustin Pop
176 a8083063 Iustin Pop
def ListNodes(opts, args):
177 a8083063 Iustin Pop
  """List nodes and their properties.
178 a8083063 Iustin Pop
179 ebf366ee Iustin Pop
  @param opts: the command line options selected by the user
180 ebf366ee Iustin Pop
  @type args: list
181 ebf366ee Iustin Pop
  @param args: should be an empty list
182 ebf366ee Iustin Pop
  @rtype: int
183 ebf366ee Iustin Pop
  @return: the desired exit code
184 ebf366ee Iustin Pop
185 a8083063 Iustin Pop
  """
186 a8083063 Iustin Pop
  if opts.output is None:
187 48c4dfa8 Iustin Pop
    selected_fields = _LIST_DEF_FIELDS
188 48c4dfa8 Iustin Pop
  elif opts.output.startswith("+"):
189 48c4dfa8 Iustin Pop
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
190 a8083063 Iustin Pop
  else:
191 a8083063 Iustin Pop
    selected_fields = opts.output.split(",")
192 a8083063 Iustin Pop
193 f1de3563 Iustin Pop
  output = GetClient().QueryNodes(args, selected_fields, opts.do_locking)
194 a8083063 Iustin Pop
195 a8083063 Iustin Pop
  if not opts.no_headers:
196 cc3bcec8 Guido Trotter
    headers = _LIST_HEADERS
197 137161c9 Michael Hanselmann
  else:
198 137161c9 Michael Hanselmann
    headers = None
199 137161c9 Michael Hanselmann
200 9fbfbb7b Iustin Pop
  unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"]
201 137161c9 Michael Hanselmann
202 ec223efb Iustin Pop
  numfields = ["dtotal", "dfree",
203 ec223efb Iustin Pop
               "mtotal", "mnode", "mfree",
204 e8a4c138 Iustin Pop
               "pinst_cnt", "sinst_cnt",
205 38d7239a Iustin Pop
               "ctotal", "serial_no"]
206 ec223efb Iustin Pop
207 130a6a6f Iustin Pop
  list_type_fields = ("pinst_list", "sinst_list", "tags")
208 ec223efb Iustin Pop
  # change raw values to nicer strings
209 ec223efb Iustin Pop
  for row in output:
210 ec223efb Iustin Pop
    for idx, field in enumerate(selected_fields):
211 ec223efb Iustin Pop
      val = row[idx]
212 130a6a6f Iustin Pop
      if field in list_type_fields:
213 ec223efb Iustin Pop
        val = ",".join(val)
214 0b2454b9 Iustin Pop
      elif field in ('master', 'master_candidate', 'offline', 'drained'):
215 0e67cdbe Iustin Pop
        if val:
216 0e67cdbe Iustin Pop
          val = 'Y'
217 0e67cdbe Iustin Pop
        else:
218 0e67cdbe Iustin Pop
          val = 'N'
219 90f72445 Iustin Pop
      elif field == "ctime" or field == "mtime":
220 90f72445 Iustin Pop
        val = utils.FormatTime(val)
221 ec223efb Iustin Pop
      elif val is None:
222 ec223efb Iustin Pop
        val = "?"
223 ec223efb Iustin Pop
      row[idx] = str(val)
224 137161c9 Michael Hanselmann
225 16be8703 Iustin Pop
  data = GenerateTable(separator=opts.separator, headers=headers,
226 16be8703 Iustin Pop
                       fields=selected_fields, unitfields=unitfields,
227 9fbfbb7b Iustin Pop
                       numfields=numfields, data=output, units=opts.units)
228 16be8703 Iustin Pop
  for line in data:
229 3a24c527 Iustin Pop
    ToStdout(line)
230 a8083063 Iustin Pop
231 a8083063 Iustin Pop
  return 0
232 a8083063 Iustin Pop
233 a8083063 Iustin Pop
234 a5bc662a Iustin Pop
def EvacuateNode(opts, args):
235 a5bc662a Iustin Pop
  """Relocate all secondary instance from a node.
236 a5bc662a Iustin Pop
237 ebf366ee Iustin Pop
  @param opts: the command line options selected by the user
238 ebf366ee Iustin Pop
  @type args: list
239 ebf366ee Iustin Pop
  @param args: should be an empty list
240 ebf366ee Iustin Pop
  @rtype: int
241 ebf366ee Iustin Pop
  @return: the desired exit code
242 ebf366ee Iustin Pop
243 a5bc662a Iustin Pop
  """
244 479636a3 Iustin Pop
  cl = GetClient()
245 a5bc662a Iustin Pop
  force = opts.force
246 c4ed32cb Iustin Pop
247 c4ed32cb Iustin Pop
  dst_node = opts.dst_node
248 c4ed32cb Iustin Pop
  iallocator = opts.iallocator
249 c4ed32cb Iustin Pop
250 c4ed32cb Iustin Pop
  cnt = [dst_node, iallocator].count(None)
251 c4ed32cb Iustin Pop
  if cnt != 1:
252 f6a32708 Iustin Pop
    raise errors.OpPrereqError("One and only one of the -n and -I"
253 debac808 Iustin Pop
                               " options must be passed", errors.ECODE_INVAL)
254 c4ed32cb Iustin Pop
255 a5bc662a Iustin Pop
  selected_fields = ["name", "sinst_list"]
256 c4ed32cb Iustin Pop
  src_node = args[0]
257 a5bc662a Iustin Pop
258 ec79568d Iustin Pop
  result = cl.QueryNodes(names=[src_node], fields=selected_fields,
259 77921a95 Iustin Pop
                         use_locking=False)
260 a5bc662a Iustin Pop
  src_node, sinst = result[0]
261 a5bc662a Iustin Pop
262 a5bc662a Iustin Pop
  if not sinst:
263 3a24c527 Iustin Pop
    ToStderr("No secondary instances on node %s, exiting.", src_node)
264 a5bc662a Iustin Pop
    return constants.EXIT_SUCCESS
265 a5bc662a Iustin Pop
266 c4ed32cb Iustin Pop
  if dst_node is not None:
267 77921a95 Iustin Pop
    result = cl.QueryNodes(names=[dst_node], fields=["name"],
268 77921a95 Iustin Pop
                           use_locking=False)
269 c4ed32cb Iustin Pop
    dst_node = result[0][0]
270 c4ed32cb Iustin Pop
271 c4ed32cb Iustin Pop
    if src_node == dst_node:
272 c4ed32cb Iustin Pop
      raise errors.OpPrereqError("Evacuate node needs different source and"
273 c4ed32cb Iustin Pop
                                 " target nodes (node %s given twice)" %
274 debac808 Iustin Pop
                                 src_node, errors.ECODE_INVAL)
275 c4ed32cb Iustin Pop
    txt_msg = "to node %s" % dst_node
276 c4ed32cb Iustin Pop
  else:
277 c4ed32cb Iustin Pop
    txt_msg = "using iallocator %s" % iallocator
278 c4ed32cb Iustin Pop
279 a5bc662a Iustin Pop
  sinst = utils.NiceSort(sinst)
280 a5bc662a Iustin Pop
281 a5bc662a Iustin Pop
  if not force and not AskUser("Relocate instance(s) %s from node\n"
282 c4ed32cb Iustin Pop
                               " %s %s?" %
283 a5bc662a Iustin Pop
                               (",".join("'%s'" % name for name in sinst),
284 c4ed32cb Iustin Pop
                               src_node, txt_msg)):
285 a5bc662a Iustin Pop
    return constants.EXIT_CONFIRMATION
286 a5bc662a Iustin Pop
287 80dd50bf Michael Hanselmann
  op = opcodes.OpEvacuateNode(node_name=args[0], remote_node=dst_node,
288 80dd50bf Michael Hanselmann
                              iallocator=iallocator)
289 80dd50bf Michael Hanselmann
  SubmitOpCode(op, cl=cl)
290 a5bc662a Iustin Pop
291 a5bc662a Iustin Pop
292 c450e9b0 Iustin Pop
def FailoverNode(opts, args):
293 c450e9b0 Iustin Pop
  """Failover all primary instance on a node.
294 c450e9b0 Iustin Pop
295 ebf366ee Iustin Pop
  @param opts: the command line options selected by the user
296 ebf366ee Iustin Pop
  @type args: list
297 ebf366ee Iustin Pop
  @param args: should be an empty list
298 ebf366ee Iustin Pop
  @rtype: int
299 ebf366ee Iustin Pop
  @return: the desired exit code
300 ebf366ee Iustin Pop
301 c450e9b0 Iustin Pop
  """
302 479636a3 Iustin Pop
  cl = GetClient()
303 c450e9b0 Iustin Pop
  force = opts.force
304 c450e9b0 Iustin Pop
  selected_fields = ["name", "pinst_list"]
305 c450e9b0 Iustin Pop
306 2e7b8369 Iustin Pop
  # these fields are static data anyway, so it doesn't matter, but
307 2e7b8369 Iustin Pop
  # locking=True should be safer
308 2e7b8369 Iustin Pop
  result = cl.QueryNodes(names=args, fields=selected_fields,
309 77921a95 Iustin Pop
                         use_locking=False)
310 c450e9b0 Iustin Pop
  node, pinst = result[0]
311 c450e9b0 Iustin Pop
312 c450e9b0 Iustin Pop
  if not pinst:
313 3a24c527 Iustin Pop
    ToStderr("No primary instances on node %s, exiting.", node)
314 c450e9b0 Iustin Pop
    return 0
315 c450e9b0 Iustin Pop
316 c450e9b0 Iustin Pop
  pinst = utils.NiceSort(pinst)
317 c450e9b0 Iustin Pop
318 c450e9b0 Iustin Pop
  retcode = 0
319 c450e9b0 Iustin Pop
320 c450e9b0 Iustin Pop
  if not force and not AskUser("Fail over instance(s) %s?" %
321 c450e9b0 Iustin Pop
                               (",".join("'%s'" % name for name in pinst))):
322 c450e9b0 Iustin Pop
    return 2
323 c450e9b0 Iustin Pop
324 479636a3 Iustin Pop
  jex = JobExecutor(cl=cl)
325 c450e9b0 Iustin Pop
  for iname in pinst:
326 c450e9b0 Iustin Pop
    op = opcodes.OpFailoverInstance(instance_name=iname,
327 c450e9b0 Iustin Pop
                                    ignore_consistency=opts.ignore_consistency)
328 479636a3 Iustin Pop
    jex.QueueJob(iname, op)
329 479636a3 Iustin Pop
  results = jex.GetResults()
330 479636a3 Iustin Pop
  bad_cnt = len([row for row in results if not row[0]])
331 479636a3 Iustin Pop
  if bad_cnt == 0:
332 479636a3 Iustin Pop
    ToStdout("All %d instance(s) failed over successfully.", len(results))
333 c450e9b0 Iustin Pop
  else:
334 3a24c527 Iustin Pop
    ToStdout("There were errors during the failover:\n"
335 479636a3 Iustin Pop
             "%d error(s) out of %d instance(s).", bad_cnt, len(results))
336 c450e9b0 Iustin Pop
  return retcode
337 c450e9b0 Iustin Pop
338 c450e9b0 Iustin Pop
339 40ef0ed6 Iustin Pop
def MigrateNode(opts, args):
340 40ef0ed6 Iustin Pop
  """Migrate all primary instance on a node.
341 40ef0ed6 Iustin Pop
342 40ef0ed6 Iustin Pop
  """
343 40ef0ed6 Iustin Pop
  cl = GetClient()
344 40ef0ed6 Iustin Pop
  force = opts.force
345 40ef0ed6 Iustin Pop
  selected_fields = ["name", "pinst_list"]
346 40ef0ed6 Iustin Pop
347 77921a95 Iustin Pop
  result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
348 40ef0ed6 Iustin Pop
  node, pinst = result[0]
349 40ef0ed6 Iustin Pop
350 40ef0ed6 Iustin Pop
  if not pinst:
351 40ef0ed6 Iustin Pop
    ToStdout("No primary instances on node %s, exiting." % node)
352 40ef0ed6 Iustin Pop
    return 0
353 40ef0ed6 Iustin Pop
354 40ef0ed6 Iustin Pop
  pinst = utils.NiceSort(pinst)
355 40ef0ed6 Iustin Pop
356 40ef0ed6 Iustin Pop
  retcode = 0
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 b21d8c7f Michael Hanselmann
  op = opcodes.OpMigrateNode(node_name=args[0], live=opts.live)
363 b21d8c7f Michael Hanselmann
  SubmitOpCode(op, cl=cl)
364 40ef0ed6 Iustin Pop
365 40ef0ed6 Iustin Pop
366 a8083063 Iustin Pop
def ShowNodeConfig(opts, args):
367 a8083063 Iustin Pop
  """Show node information.
368 a8083063 Iustin Pop
369 ebf366ee Iustin Pop
  @param opts: the command line options selected by the user
370 ebf366ee Iustin Pop
  @type args: list
371 ebf366ee Iustin Pop
  @param args: should either be an empty list, in which case
372 ebf366ee Iustin Pop
      we show information about all nodes, or should contain
373 ebf366ee Iustin Pop
      a list of nodes to be queried for information
374 ebf366ee Iustin Pop
  @rtype: int
375 ebf366ee Iustin Pop
  @return: the desired exit code
376 ebf366ee Iustin Pop
377 a8083063 Iustin Pop
  """
378 2e7b8369 Iustin Pop
  cl = GetClient()
379 2e7b8369 Iustin Pop
  result = cl.QueryNodes(fields=["name", "pip", "sip",
380 0b2454b9 Iustin Pop
                                 "pinst_list", "sinst_list",
381 0b2454b9 Iustin Pop
                                 "master_candidate", "drained", "offline"],
382 77921a95 Iustin Pop
                         names=args, use_locking=False)
383 a8083063 Iustin Pop
384 0b2454b9 Iustin Pop
  for (name, primary_ip, secondary_ip, pinst, sinst,
385 0b2454b9 Iustin Pop
       is_mc, drained, offline) in result:
386 3a24c527 Iustin Pop
    ToStdout("Node name: %s", name)
387 3a24c527 Iustin Pop
    ToStdout("  primary ip: %s", primary_ip)
388 3a24c527 Iustin Pop
    ToStdout("  secondary ip: %s", secondary_ip)
389 0b2454b9 Iustin Pop
    ToStdout("  master candidate: %s", is_mc)
390 0b2454b9 Iustin Pop
    ToStdout("  drained: %s", drained)
391 0b2454b9 Iustin Pop
    ToStdout("  offline: %s", offline)
392 a8083063 Iustin Pop
    if pinst:
393 3a24c527 Iustin Pop
      ToStdout("  primary for instances:")
394 ae07a1d3 Iustin Pop
      for iname in utils.NiceSort(pinst):
395 3a24c527 Iustin Pop
        ToStdout("    - %s", iname)
396 a8083063 Iustin Pop
    else:
397 3a24c527 Iustin Pop
      ToStdout("  primary for no instances")
398 a8083063 Iustin Pop
    if sinst:
399 3a24c527 Iustin Pop
      ToStdout("  secondary for instances:")
400 ae07a1d3 Iustin Pop
      for iname in utils.NiceSort(sinst):
401 3a24c527 Iustin Pop
        ToStdout("    - %s", iname)
402 a8083063 Iustin Pop
    else:
403 3a24c527 Iustin Pop
      ToStdout("  secondary for no instances")
404 a8083063 Iustin Pop
405 a8083063 Iustin Pop
  return 0
406 a8083063 Iustin Pop
407 a8083063 Iustin Pop
408 a8083063 Iustin Pop
def RemoveNode(opts, args):
409 ebf366ee Iustin Pop
  """Remove a node from the cluster.
410 ebf366ee Iustin Pop
411 ebf366ee Iustin Pop
  @param opts: the command line options selected by the user
412 ebf366ee Iustin Pop
  @type args: list
413 ebf366ee Iustin Pop
  @param args: should contain only one element, the name of
414 ebf366ee Iustin Pop
      the node to be removed
415 ebf366ee Iustin Pop
  @rtype: int
416 ebf366ee Iustin Pop
  @return: the desired exit code
417 ebf366ee Iustin Pop
418 ebf366ee Iustin Pop
  """
419 a8083063 Iustin Pop
  op = opcodes.OpRemoveNode(node_name=args[0])
420 a8083063 Iustin Pop
  SubmitOpCode(op)
421 ebf366ee Iustin Pop
  return 0
422 a8083063 Iustin Pop
423 a8083063 Iustin Pop
424 f5118ade Iustin Pop
def PowercycleNode(opts, args):
425 f5118ade Iustin Pop
  """Remove a node from the cluster.
426 f5118ade Iustin Pop
427 f5118ade Iustin Pop
  @param opts: the command line options selected by the user
428 f5118ade Iustin Pop
  @type args: list
429 f5118ade Iustin Pop
  @param args: should contain only one element, the name of
430 f5118ade Iustin Pop
      the node to be removed
431 f5118ade Iustin Pop
  @rtype: int
432 f5118ade Iustin Pop
  @return: the desired exit code
433 f5118ade Iustin Pop
434 f5118ade Iustin Pop
  """
435 f5118ade Iustin Pop
  node = args[0]
436 f5118ade Iustin Pop
  if (not opts.confirm and
437 f5118ade Iustin Pop
      not AskUser("Are you sure you want to hard powercycle node %s?" % node)):
438 f5118ade Iustin Pop
    return 2
439 f5118ade Iustin Pop
440 f5118ade Iustin Pop
  op = opcodes.OpPowercycleNode(node_name=node, force=opts.force)
441 f5118ade Iustin Pop
  result = SubmitOpCode(op)
442 f5118ade Iustin Pop
  ToStderr(result)
443 f5118ade Iustin Pop
  return 0
444 f5118ade Iustin Pop
445 f5118ade Iustin Pop
446 dcb93971 Michael Hanselmann
def ListVolumes(opts, args):
447 dcb93971 Michael Hanselmann
  """List logical volumes on node(s).
448 dcb93971 Michael Hanselmann
449 ebf366ee Iustin Pop
  @param opts: the command line options selected by the user
450 ebf366ee Iustin Pop
  @type args: list
451 ebf366ee Iustin Pop
  @param args: should either be an empty list, in which case
452 ebf366ee Iustin Pop
      we list data for all nodes, or contain a list of nodes
453 ebf366ee Iustin Pop
      to display data only for those
454 ebf366ee Iustin Pop
  @rtype: int
455 ebf366ee Iustin Pop
  @return: the desired exit code
456 ebf366ee Iustin Pop
457 dcb93971 Michael Hanselmann
  """
458 dcb93971 Michael Hanselmann
  if opts.output is None:
459 dcb93971 Michael Hanselmann
    selected_fields = ["node", "phys", "vg",
460 dcb93971 Michael Hanselmann
                       "name", "size", "instance"]
461 dcb93971 Michael Hanselmann
  else:
462 dcb93971 Michael Hanselmann
    selected_fields = opts.output.split(",")
463 dcb93971 Michael Hanselmann
464 dcb93971 Michael Hanselmann
  op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
465 dcb93971 Michael Hanselmann
  output = SubmitOpCode(op)
466 dcb93971 Michael Hanselmann
467 dcb93971 Michael Hanselmann
  if not opts.no_headers:
468 137161c9 Michael Hanselmann
    headers = {"node": "Node", "phys": "PhysDev",
469 137161c9 Michael Hanselmann
               "vg": "VG", "name": "Name",
470 137161c9 Michael Hanselmann
               "size": "Size", "instance": "Instance"}
471 137161c9 Michael Hanselmann
  else:
472 137161c9 Michael Hanselmann
    headers = None
473 137161c9 Michael Hanselmann
474 9fbfbb7b Iustin Pop
  unitfields = ["size"]
475 137161c9 Michael Hanselmann
476 137161c9 Michael Hanselmann
  numfields = ["size"]
477 137161c9 Michael Hanselmann
478 16be8703 Iustin Pop
  data = GenerateTable(separator=opts.separator, headers=headers,
479 16be8703 Iustin Pop
                       fields=selected_fields, unitfields=unitfields,
480 9fbfbb7b Iustin Pop
                       numfields=numfields, data=output, units=opts.units)
481 16be8703 Iustin Pop
482 16be8703 Iustin Pop
  for line in data:
483 3a24c527 Iustin Pop
    ToStdout(line)
484 dcb93971 Michael Hanselmann
485 dcb93971 Michael Hanselmann
  return 0
486 dcb93971 Michael Hanselmann
487 dcb93971 Michael Hanselmann
488 9b94905f Iustin Pop
def ListStorage(opts, args):
489 4007f57d Michael Hanselmann
  """List physical volumes on node(s).
490 4007f57d Michael Hanselmann
491 4007f57d Michael Hanselmann
  @param opts: the command line options selected by the user
492 4007f57d Michael Hanselmann
  @type args: list
493 4007f57d Michael Hanselmann
  @param args: should either be an empty list, in which case
494 4007f57d Michael Hanselmann
      we list data for all nodes, or contain a list of nodes
495 4007f57d Michael Hanselmann
      to display data only for those
496 4007f57d Michael Hanselmann
  @rtype: int
497 4007f57d Michael Hanselmann
  @return: the desired exit code
498 4007f57d Michael Hanselmann
499 4007f57d Michael Hanselmann
  """
500 53548798 Michael Hanselmann
  # TODO: Default to ST_FILE if LVM is disabled on the cluster
501 53548798 Michael Hanselmann
  if opts.user_storage_type is None:
502 53548798 Michael Hanselmann
    opts.user_storage_type = constants.ST_LVM_PV
503 53548798 Michael Hanselmann
504 86f5eae3 Michael Hanselmann
  storage_type = ConvertStorageType(opts.user_storage_type)
505 53548798 Michael Hanselmann
506 4007f57d Michael Hanselmann
  if opts.output is None:
507 620a85fd Iustin Pop
    selected_fields = _LIST_STOR_DEF_FIELDS
508 dc09c3cf Iustin Pop
  elif opts.output.startswith("+"):
509 620a85fd Iustin Pop
    selected_fields = _LIST_STOR_DEF_FIELDS + opts.output[1:].split(",")
510 4007f57d Michael Hanselmann
  else:
511 4007f57d Michael Hanselmann
    selected_fields = opts.output.split(",")
512 4007f57d Michael Hanselmann
513 4007f57d Michael Hanselmann
  op = opcodes.OpQueryNodeStorage(nodes=args,
514 53548798 Michael Hanselmann
                                  storage_type=storage_type,
515 4007f57d Michael Hanselmann
                                  output_fields=selected_fields)
516 4007f57d Michael Hanselmann
  output = SubmitOpCode(op)
517 4007f57d Michael Hanselmann
518 4007f57d Michael Hanselmann
  if not opts.no_headers:
519 4007f57d Michael Hanselmann
    headers = {
520 620a85fd Iustin Pop
      constants.SF_NODE: "Node",
521 620a85fd Iustin Pop
      constants.SF_TYPE: "Type",
522 4007f57d Michael Hanselmann
      constants.SF_NAME: "Name",
523 4007f57d Michael Hanselmann
      constants.SF_SIZE: "Size",
524 4007f57d Michael Hanselmann
      constants.SF_USED: "Used",
525 4007f57d Michael Hanselmann
      constants.SF_FREE: "Free",
526 4007f57d Michael Hanselmann
      constants.SF_ALLOCATABLE: "Allocatable",
527 4007f57d Michael Hanselmann
      }
528 4007f57d Michael Hanselmann
  else:
529 4007f57d Michael Hanselmann
    headers = None
530 4007f57d Michael Hanselmann
531 4007f57d Michael Hanselmann
  unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
532 4007f57d Michael Hanselmann
  numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
533 4007f57d Michael Hanselmann
534 dc09c3cf Iustin Pop
  # change raw values to nicer strings
535 dc09c3cf Iustin Pop
  for row in output:
536 dc09c3cf Iustin Pop
    for idx, field in enumerate(selected_fields):
537 dc09c3cf Iustin Pop
      val = row[idx]
538 dc09c3cf Iustin Pop
      if field == constants.SF_ALLOCATABLE:
539 dc09c3cf Iustin Pop
        if val:
540 dc09c3cf Iustin Pop
          val = "Y"
541 dc09c3cf Iustin Pop
        else:
542 dc09c3cf Iustin Pop
          val = "N"
543 dc09c3cf Iustin Pop
      row[idx] = str(val)
544 dc09c3cf Iustin Pop
545 4007f57d Michael Hanselmann
  data = GenerateTable(separator=opts.separator, headers=headers,
546 4007f57d Michael Hanselmann
                       fields=selected_fields, unitfields=unitfields,
547 4007f57d Michael Hanselmann
                       numfields=numfields, data=output, units=opts.units)
548 4007f57d Michael Hanselmann
549 4007f57d Michael Hanselmann
  for line in data:
550 4007f57d Michael Hanselmann
    ToStdout(line)
551 4007f57d Michael Hanselmann
552 4007f57d Michael Hanselmann
  return 0
553 4007f57d Michael Hanselmann
554 4007f57d Michael Hanselmann
555 9b94905f Iustin Pop
def ModifyStorage(opts, args):
556 0e89fc2d Michael Hanselmann
  """Modify storage volume on a node.
557 0e89fc2d Michael Hanselmann
558 0e89fc2d Michael Hanselmann
  @param opts: the command line options selected by the user
559 0e89fc2d Michael Hanselmann
  @type args: list
560 0e89fc2d Michael Hanselmann
  @param args: should contain 3 items: node name, storage type and volume name
561 0e89fc2d Michael Hanselmann
  @rtype: int
562 0e89fc2d Michael Hanselmann
  @return: the desired exit code
563 0e89fc2d Michael Hanselmann
564 0e89fc2d Michael Hanselmann
  """
565 0e89fc2d Michael Hanselmann
  (node_name, user_storage_type, volume_name) = args
566 0e89fc2d Michael Hanselmann
567 86f5eae3 Michael Hanselmann
  storage_type = ConvertStorageType(user_storage_type)
568 0e89fc2d Michael Hanselmann
569 0e89fc2d Michael Hanselmann
  changes = {}
570 0e89fc2d Michael Hanselmann
571 0e89fc2d Michael Hanselmann
  if opts.allocatable is not None:
572 0e89fc2d Michael Hanselmann
    changes[constants.SF_ALLOCATABLE] = (opts.allocatable == "yes")
573 0e89fc2d Michael Hanselmann
574 0e89fc2d Michael Hanselmann
  if changes:
575 0e89fc2d Michael Hanselmann
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
576 0e89fc2d Michael Hanselmann
                                     storage_type=storage_type,
577 0e89fc2d Michael Hanselmann
                                     name=volume_name,
578 0e89fc2d Michael Hanselmann
                                     changes=changes)
579 0e89fc2d Michael Hanselmann
    SubmitOpCode(op)
580 620a85fd Iustin Pop
  else:
581 620a85fd Iustin Pop
    ToStderr("No changes to perform, exiting.")
582 0e89fc2d Michael Hanselmann
583 0e89fc2d Michael Hanselmann
584 9b94905f Iustin Pop
def RepairStorage(opts, args):
585 1e3463f1 Michael Hanselmann
  """Repairs a storage volume on a node.
586 1e3463f1 Michael Hanselmann
587 1e3463f1 Michael Hanselmann
  @param opts: the command line options selected by the user
588 1e3463f1 Michael Hanselmann
  @type args: list
589 1e3463f1 Michael Hanselmann
  @param args: should contain 3 items: node name, storage type and volume name
590 1e3463f1 Michael Hanselmann
  @rtype: int
591 1e3463f1 Michael Hanselmann
  @return: the desired exit code
592 1e3463f1 Michael Hanselmann
593 1e3463f1 Michael Hanselmann
  """
594 1e3463f1 Michael Hanselmann
  (node_name, user_storage_type, volume_name) = args
595 1e3463f1 Michael Hanselmann
596 1e3463f1 Michael Hanselmann
  storage_type = ConvertStorageType(user_storage_type)
597 1e3463f1 Michael Hanselmann
598 1e3463f1 Michael Hanselmann
  op = opcodes.OpRepairNodeStorage(node_name=node_name,
599 1e3463f1 Michael Hanselmann
                                   storage_type=storage_type,
600 1e3463f1 Michael Hanselmann
                                   name=volume_name)
601 1e3463f1 Michael Hanselmann
  SubmitOpCode(op)
602 1e3463f1 Michael Hanselmann
603 1e3463f1 Michael Hanselmann
604 b31c8676 Iustin Pop
def SetNodeParams(opts, args):
605 b31c8676 Iustin Pop
  """Modifies a node.
606 b31c8676 Iustin Pop
607 b31c8676 Iustin Pop
  @param opts: the command line options selected by the user
608 b31c8676 Iustin Pop
  @type args: list
609 b31c8676 Iustin Pop
  @param args: should contain only one element, the node name
610 b31c8676 Iustin Pop
  @rtype: int
611 b31c8676 Iustin Pop
  @return: the desired exit code
612 b31c8676 Iustin Pop
613 b31c8676 Iustin Pop
  """
614 c9d443ea Iustin Pop
  if [opts.master_candidate, opts.drained, opts.offline].count(None) == 3:
615 b31c8676 Iustin Pop
    ToStderr("Please give at least one of the parameters.")
616 b31c8676 Iustin Pop
    return 1
617 b31c8676 Iustin Pop
618 3a5ba66a Iustin Pop
  if opts.master_candidate is not None:
619 3a5ba66a Iustin Pop
    candidate = opts.master_candidate == 'yes'
620 3a5ba66a Iustin Pop
  else:
621 3a5ba66a Iustin Pop
    candidate = None
622 3a5ba66a Iustin Pop
  if opts.offline is not None:
623 3a5ba66a Iustin Pop
    offline = opts.offline == 'yes'
624 3a5ba66a Iustin Pop
  else:
625 3a5ba66a Iustin Pop
    offline = None
626 c9d443ea Iustin Pop
627 c9d443ea Iustin Pop
  if opts.drained is not None:
628 c9d443ea Iustin Pop
    drained = opts.drained == 'yes'
629 c9d443ea Iustin Pop
  else:
630 c9d443ea Iustin Pop
    drained = None
631 b31c8676 Iustin Pop
  op = opcodes.OpSetNodeParams(node_name=args[0],
632 3a5ba66a Iustin Pop
                               master_candidate=candidate,
633 3a5ba66a Iustin Pop
                               offline=offline,
634 c9d443ea Iustin Pop
                               drained=drained,
635 3a5ba66a Iustin Pop
                               force=opts.force)
636 b31c8676 Iustin Pop
637 b31c8676 Iustin Pop
  # even if here we process the result, we allow submit only
638 b31c8676 Iustin Pop
  result = SubmitOrSend(op, opts)
639 b31c8676 Iustin Pop
640 b31c8676 Iustin Pop
  if result:
641 b31c8676 Iustin Pop
    ToStdout("Modified node %s", args[0])
642 b31c8676 Iustin Pop
    for param, data in result:
643 b31c8676 Iustin Pop
      ToStdout(" - %-5s -> %s", param, data)
644 b31c8676 Iustin Pop
  return 0
645 b31c8676 Iustin Pop
646 b31c8676 Iustin Pop
647 a8083063 Iustin Pop
commands = {
648 6ea815cf Iustin Pop
  'add': (
649 6ea815cf Iustin Pop
    AddNode, [ArgHost(min=1, max=1)],
650 064c21f8 Iustin Pop
    [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT],
651 6ea815cf Iustin Pop
    "[-s ip] [--readd] [--no-ssh-key-check] <node_name>",
652 6ea815cf Iustin Pop
    "Add a node to the cluster"),
653 6ea815cf Iustin Pop
  'evacuate': (
654 6ea815cf Iustin Pop
    EvacuateNode, ARGS_ONE_NODE,
655 064c21f8 Iustin Pop
    [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT],
656 6ea815cf Iustin Pop
    "[-f] {-I <iallocator> | -n <dst>} <node>",
657 6ea815cf Iustin Pop
    "Relocate the secondary instances from a node"
658 6ea815cf Iustin Pop
    " to other nodes (only for instances with drbd disk template)"),
659 6ea815cf Iustin Pop
  'failover': (
660 064c21f8 Iustin Pop
    FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT],
661 6ea815cf Iustin Pop
    "[-f] <node>",
662 6ea815cf Iustin Pop
    "Stops the primary instances on a node and start them on their"
663 6ea815cf Iustin Pop
    " secondary node (only for instances with drbd disk template)"),
664 6ea815cf Iustin Pop
  'migrate': (
665 064c21f8 Iustin Pop
    MigrateNode, ARGS_ONE_NODE, [FORCE_OPT, NONLIVE_OPT],
666 6ea815cf Iustin Pop
    "[-f] <node>",
667 6ea815cf Iustin Pop
    "Migrate all the primary instance on a node away from it"
668 6ea815cf Iustin Pop
    " (only for instances of type drbd)"),
669 6ea815cf Iustin Pop
  'info': (
670 064c21f8 Iustin Pop
    ShowNodeConfig, ARGS_MANY_NODES, [],
671 6ea815cf Iustin Pop
    "[<node_name>...]", "Show information about the node(s)"),
672 6ea815cf Iustin Pop
  'list': (
673 6ea815cf Iustin Pop
    ListNodes, ARGS_MANY_NODES,
674 064c21f8 Iustin Pop
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT],
675 6ea815cf Iustin Pop
    "[nodes...]",
676 6ea815cf Iustin Pop
    "Lists the nodes in the cluster. The available fields are (see the man"
677 6ea815cf Iustin Pop
    " page for details): %s. The default field list is (in order): %s." %
678 6ea815cf Iustin Pop
    (", ".join(_LIST_HEADERS), ", ".join(_LIST_DEF_FIELDS))),
679 6ea815cf Iustin Pop
  'modify': (
680 6ea815cf Iustin Pop
    SetNodeParams, ARGS_ONE_NODE,
681 064c21f8 Iustin Pop
    [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT],
682 6ea815cf Iustin Pop
    "<node_name>", "Alters the parameters of a node"),
683 6ea815cf Iustin Pop
  'powercycle': (
684 6ea815cf Iustin Pop
    PowercycleNode, ARGS_ONE_NODE,
685 064c21f8 Iustin Pop
    [FORCE_OPT, CONFIRM_OPT],
686 6ea815cf Iustin Pop
    "<node_name>", "Tries to forcefully powercycle a node"),
687 6ea815cf Iustin Pop
  'remove': (
688 064c21f8 Iustin Pop
    RemoveNode, ARGS_ONE_NODE, [],
689 6ea815cf Iustin Pop
    "<node_name>", "Removes a node from the cluster"),
690 6ea815cf Iustin Pop
  'volumes': (
691 6ea815cf Iustin Pop
    ListVolumes, [ArgNode()],
692 064c21f8 Iustin Pop
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
693 6ea815cf Iustin Pop
    "[<node_name>...]", "List logical volumes on node(s)"),
694 9b94905f Iustin Pop
  'list-storage': (
695 9b94905f Iustin Pop
    ListStorage, ARGS_MANY_NODES,
696 064c21f8 Iustin Pop
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT],
697 620a85fd Iustin Pop
    "[<node_name>...]", "List physical volumes on node(s). The available"
698 620a85fd Iustin Pop
    " fields are (see the man page for details): %s." %
699 620a85fd Iustin Pop
    (", ".join(_LIST_STOR_HEADERS))),
700 9b94905f Iustin Pop
  'modify-storage': (
701 9b94905f Iustin Pop
    ModifyStorage,
702 6ea815cf Iustin Pop
    [ArgNode(min=1, max=1),
703 6ea815cf Iustin Pop
     ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
704 6ea815cf Iustin Pop
     ArgFile(min=1, max=1)],
705 f7e41aa2 Iustin Pop
    [ALLOCATABLE_OPT],
706 064c21f8 Iustin Pop
    "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
707 9b94905f Iustin Pop
  'repair-storage': (
708 9b94905f Iustin Pop
    RepairStorage,
709 6ea815cf Iustin Pop
    [ArgNode(min=1, max=1),
710 6ea815cf Iustin Pop
     ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
711 6ea815cf Iustin Pop
     ArgFile(min=1, max=1)],
712 064c21f8 Iustin Pop
    [],
713 6ea815cf Iustin Pop
    "<node_name> <storage_type> <name>",
714 6ea815cf Iustin Pop
    "Repairs a storage volume on a node"),
715 6ea815cf Iustin Pop
  'list-tags': (
716 064c21f8 Iustin Pop
    ListTags, ARGS_ONE_NODE, [],
717 6ea815cf Iustin Pop
    "<node_name>", "List the tags of the given node"),
718 6ea815cf Iustin Pop
  'add-tags': (
719 064c21f8 Iustin Pop
    AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT],
720 6ea815cf Iustin Pop
    "<node_name> tag...", "Add tags to the given node"),
721 6ea815cf Iustin Pop
  'remove-tags': (
722 064c21f8 Iustin Pop
    RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT],
723 6ea815cf Iustin Pop
    "<node_name> tag...", "Remove tags from the given node"),
724 a8083063 Iustin Pop
  }
725 a8083063 Iustin Pop
726 a8083063 Iustin Pop
727 a8083063 Iustin Pop
if __name__ == '__main__':
728 846baef9 Iustin Pop
  sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_NODE}))