ensure-dirs: Set permissions on queue lock file
[ganeti-local] / lib / cli.py
index 13050dd..4aeb162 100644 (file)
@@ -168,6 +168,7 @@ __all__ = [
   "TAG_ADD_OPT",
   "TAG_SRC_OPT",
   "TIMEOUT_OPT",
+  "TO_GROUP_OPT",
   "UIDPOOL_OPT",
   "USEUNITS_OPT",
   "USE_REPL_NET_OPT",
@@ -516,7 +517,7 @@ def check_ident_key_val(option, opt, value):  # pylint: disable-msg=W0613
 
   """
   if ":" not in value:
-    ident, rest = value, ''
+    ident, rest = value, ""
   else:
     ident, rest = value.split(":", 1)
 
@@ -620,7 +621,7 @@ SEP_OPT = cli_option("--separator", default=None,
                            " (defaults to one space)"))
 
 USEUNITS_OPT = cli_option("--units", default=None,
-                          dest="units", choices=('h', 'm', 'g', 't'),
+                          dest="units", choices=("h", "m", "g", "t"),
                           help="Specify units for output (one of h/m/g/t)")
 
 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
@@ -803,7 +804,8 @@ NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
                            " times, if not given defaults to all nodes)",
                            completion_suggest=OPT_COMPL_ONE_NODE)
 
-NODEGROUP_OPT = cli_option("-g", "--node-group",
+NODEGROUP_OPT_NAME = "--node-group"
+NODEGROUP_OPT = cli_option("-g", NODEGROUP_OPT_NAME,
                            dest="nodegroup",
                            help="Node group (name or uuid)",
                            metavar="<nodegroup>",
@@ -979,7 +981,7 @@ VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
                                " [%s]" % constants.DEFAULT_VG),
                          metavar="VG", default=None)
 
-YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
+YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it",
                           help="Destroy cluster", action="store_true")
 
 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
@@ -1228,6 +1230,11 @@ STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
                                 action="store_true", default=False,
                                 help="Pause instance at startup")
 
+TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
+                          help="Destination node group (name or uuid)",
+                          default=None, action="append",
+                          completion_suggest=OPT_COMPL_ONE_NODEGROUP)
+
 
 #: Options provided by all commands
 COMMON_OPTS = [DEBUG_OPT]
@@ -1411,8 +1418,8 @@ def SplitNodeOption(value):
   """Splits the value of a --node option.
 
   """
-  if value and ':' in value:
-    return value.split(':', 1)
+  if value and ":" in value:
+    return value.split(":", 1)
   else:
     return (value, None)
 
@@ -1429,7 +1436,7 @@ def CalculateOSNames(os_name, os_variants):
 
   """
   if os_variants:
-    return ['%s+%s' % (os_name, v) for v in os_variants]
+    return ["%s+%s" % (os_name, v) for v in os_variants]
   else:
     return [os_name]
 
@@ -1471,12 +1478,12 @@ def AskUser(text, choices=None):
 
   """
   if choices is None:
-    choices = [('y', True, 'Perform the operation'),
-               ('n', False, 'Do not perform the operation')]
+    choices = [("y", True, "Perform the operation"),
+               ("n", False, "Do not perform the operation")]
   if not choices or not isinstance(choices, list):
     raise errors.ProgrammerError("Invalid choices argument to AskUser")
   for entry in choices:
-    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
+    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?":
       raise errors.ProgrammerError("Invalid choices element to AskUser")
 
   answer = choices[-1][1]
@@ -1491,18 +1498,18 @@ def AskUser(text, choices=None):
   try:
     chars = [entry[0] for entry in choices]
     chars[-1] = "[%s]" % chars[-1]
-    chars.append('?')
+    chars.append("?")
     maps = dict([(entry[0], entry[1]) for entry in choices])
     while True:
       f.write(text)
-      f.write('\n')
+      f.write("\n")
       f.write("/".join(chars))
       f.write(": ")
       line = f.readline(2).strip().lower()
       if line in maps:
         answer = maps[line]
         break
-      elif line == '?':
+      elif line == "?":
         for entry in choices:
           f.write(" %s - %s\n" % (entry[0], entry[2]))
         f.write("\n")
@@ -1749,7 +1756,7 @@ class StdioJobPollReportCb(JobPollReportCbBase):
       ToStderr("Job %s is waiting in queue", job_id)
       self.notified_queued = True
 
-    elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock:
+    elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
       ToStderr("Job %s is trying to acquire all necessary locks", job_id)
       self.notified_waitlock = True
 
@@ -1959,7 +1966,7 @@ def FormatError(err):
     retcode = 0
   else:
     obuf.write("Unhandled exception: %s" % msg)
-  return retcode, obuf.getvalue().rstrip('\n')
+  return retcode, obuf.getvalue().rstrip("\n")
 
 
 def GenericMain(commands, override=None, aliases=None):
@@ -2254,7 +2261,7 @@ class _RunWhileClusterStoppedHelper:
     """
     # Pause watcher by acquiring an exclusive lock on watcher state file
     self.feedback_fn("Blocking watcher")
-    watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
+    watcher_block = utils.FileLock.Open(constants.WATCHER_LOCK_FILE)
     try:
       # TODO: Currently, this just blocks. There's no timeout.
       # TODO: Should it be a shared lock?
@@ -2375,7 +2382,7 @@ def GenerateTable(headers, fields, separator, data,
 
   if separator is None:
     mlens = [0 for name in fields]
-    format_str = ' '.join(format_fields)
+    format_str = " ".join(format_fields)
   else:
     format_str = separator.replace("%", "%%").join(format_fields)
 
@@ -2414,7 +2421,7 @@ def GenerateTable(headers, fields, separator, data,
   for line in data:
     args = []
     if line is None:
-      line = ['-' for _ in fields]
+      line = ["-" for _ in fields]
     for idx in range(len(fields)):
       if separator is None:
         args.append(mlens[idx])
@@ -2654,18 +2661,7 @@ def GenericList(resource, fields, names, unit, separator, header, cl=None,
   if not names:
     names = None
 
-  if (force_filter or
-      (names and len(names) == 1 and qlang.MaybeFilter(names[0]))):
-    try:
-      (filter_text, ) = names
-    except ValueError:
-      raise errors.OpPrereqError("Exactly one argument must be given as a"
-                                 " filter")
-
-    logging.debug("Parsing '%s' as filter", filter_text)
-    filter_ = qlang.ParseFilter(filter_text)
-  else:
-    filter_ = qlang.MakeSimpleFilter("name", names)
+  filter_ = qlang.MakeFilter(names, force_filter)
 
   response = cl.Query(resource, fields, filter_)
 
@@ -2820,7 +2816,7 @@ def FormatTimestamp(ts):
 
   """
   if not isinstance (ts, (tuple, list)) or len(ts) != 2:
-    return '?'
+    return "?"
   sec, usec = ts
   return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
 
@@ -2843,11 +2839,11 @@ def ParseTimespec(value):
   if not value:
     raise errors.OpPrereqError("Empty time specification passed")
   suffix_map = {
-    's': 1,
-    'm': 60,
-    'h': 3600,
-    'd': 86400,
-    'w': 604800,
+    "s": 1,
+    "m": 60,
+    "h": 3600,
+    "d": 86400,
+    "w": 604800,
     }
   if value[-1] not in suffix_map:
     try:
@@ -2868,7 +2864,7 @@ def ParseTimespec(value):
 
 
 def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
-                   filter_master=False):
+                   filter_master=False, nodegroup=None):
   """Returns the names of online nodes.
 
   This function will also log a warning on stderr with the names of
@@ -2889,28 +2885,60 @@ def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
   @param filter_master: if True, do not return the master node in the list
       (useful in coordination with secondary_ips where we cannot check our
       node name against the list)
+  @type nodegroup: string
+  @param nodegroup: If set, only return nodes in this node group
 
   """
   if cl is None:
     cl = GetClient()
 
-  if secondary_ips:
-    name_idx = 2
-  else:
-    name_idx = 0
+  filter_ = []
+
+  if nodes:
+    filter_.append(qlang.MakeSimpleFilter("name", nodes))
+
+  if nodegroup is not None:
+    filter_.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
+                                 [qlang.OP_EQUAL, "group.uuid", nodegroup]])
 
   if filter_master:
-    master_node = cl.QueryConfigValues(["master_node"])[0]
-    filter_fn = lambda x: x != master_node
+    filter_.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
+
+  if filter_:
+    if len(filter_) > 1:
+      final_filter = [qlang.OP_AND] + filter_
+    else:
+      assert len(filter_) == 1
+      final_filter = filter_[0]
   else:
-    filter_fn = lambda _: True
+    final_filter = None
+
+  result = cl.Query(constants.QR_NODE, ["name", "offline", "sip"], final_filter)
+
+  def _IsOffline(row):
+    (_, (_, offline), _) = row
+    return offline
+
+  def _GetName(row):
+    ((_, name), _, _) = row
+    return name
+
+  def _GetSip(row):
+    (_, _, (_, sip)) = row
+    return sip
+
+  (offline, online) = compat.partition(result.data, _IsOffline)
 
-  result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
-                         use_locking=False)
-  offline = [row[0] for row in result if row[1]]
   if offline and not nowarn:
-    ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
-  return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
+    ToStderr("Note: skipping offline node(s): %s" %
+             utils.CommaJoin(map(_GetName, offline)))
+
+  if secondary_ips:
+    fn = _GetSip
+  else:
+    fn = _GetName
+
+  return map(fn, online)
 
 
 def _ToStream(stream, txt, *args):
@@ -2928,7 +2956,7 @@ def _ToStream(stream, txt, *args):
       stream.write(txt % args)
     else:
       stream.write(txt)
-    stream.write('\n')
+    stream.write("\n")
     stream.flush()
   except IOError, err:
     if err.errno == errno.EPIPE:
@@ -3033,7 +3061,7 @@ class JobExecutor(object):
     for job_data, status in zip(self.jobs, result):
       if (isinstance(status, list) and status and
           status[0] in (constants.JOB_STATUS_QUEUED,
-                        constants.JOB_STATUS_WAITLOCK,
+                        constants.JOB_STATUS_WAITING,
                         constants.JOB_STATUS_CANCELING)):
         # job is still present and waiting
         continue