- Move --force option to cli.py
[ganeti-local] / lib / cmdlib.py
index e7acceb..a5f5a9c 100644 (file)
@@ -164,6 +164,49 @@ class NoHooksLU(LogicalUnit):
     return
 
 
+def _GetWantedNodes(lu, nodes):
+  """Returns list of checked and expanded nodes.
+
+  Args:
+    nodes: List of nodes (strings) or None for all
+
+  """
+  if nodes is not None and not isinstance(nodes, list):
+    raise errors.OpPrereqError, "Invalid argument type 'nodes'"
+
+  if nodes:
+    wanted_nodes = []
+
+    for name in nodes:
+      node = lu.cfg.GetNodeInfo(lu.cfg.ExpandNodeName(name))
+      if node is None:
+        raise errors.OpPrereqError, ("No such node name '%s'" % name)
+    wanted_nodes.append(node)
+
+    return wanted_nodes
+  else:
+    return [lu.cfg.GetNodeInfo(name) for name in lu.cfg.GetNodeList()]
+
+
+def _CheckOutputFields(static, dynamic, selected):
+  """Checks whether all selected fields are valid.
+
+  Args:
+    static: Static fields
+    dynamic: Dynamic fields
+
+  """
+  static_fields = frozenset(static)
+  dynamic_fields = frozenset(dynamic)
+
+  all_fields = static_fields | dynamic_fields
+
+  if not all_fields.issuperset(selected):
+    raise errors.OpPrereqError, ("Unknown output fields selected: %s"
+                                 % ",".join(frozenset(selected).
+                                            difference(all_fields)))
+
+
 def _UpdateEtcHosts(fullnode, ip):
   """Ensure a node has a correct entry in /etc/hosts.
 
@@ -423,7 +466,6 @@ class LUInitCluster(LogicalUnit):
     ourselves in the post-run node list.
 
     """
-
     env = {"CLUSTER": self.op.cluster_name,
            "MASTER": self.hostname['hostname_full']}
     return env, [], [self.hostname['hostname_full']]
@@ -580,6 +622,7 @@ class LUVerifyCluster(NoHooksLU):
       node: name of the node to check
       file_list: required list of files
       local_cksum: dictionary of local files and their checksums
+
     """
     # compares ganeti version
     local_version = constants.PROTOCOL_VERSION
@@ -695,7 +738,6 @@ class LUVerifyCluster(NoHooksLU):
           bad = True
     return bad
 
-
   def _VerifyOrphanInstances(self, instancelist, node_instance, feedback_fn):
     """Verify the list of running instances.
 
@@ -711,28 +753,6 @@ class LUVerifyCluster(NoHooksLU):
           bad = True
     return bad
 
-  def _VerifyNodeConfigFiles(self, ismaster, node, file_list, feedback_fn):
-    """Verify the list of node config files"""
-
-    bad = False
-    for file_name in constants.MASTER_CONFIGFILES:
-      if ismaster and file_name not in file_list:
-        feedback_fn("  - ERROR: master config file %s missing from master"
-                    " node %s" % (file_name, node))
-        bad = True
-      elif not ismaster and file_name in file_list:
-        feedback_fn("  - ERROR: master config file %s should not exist"
-                    " on non-master node %s" % (file_name, node))
-        bad = True
-
-    for file_name in constants.NODE_CONFIGFILES:
-      if file_name not in file_list:
-        feedback_fn("  - ERROR: config file %s missing from node %s" %
-                    (file_name, node))
-        bad = True
-
-    return bad
-
   def CheckPrereq(self):
     """Check prerequisites.
 
@@ -758,11 +778,12 @@ class LUVerifyCluster(NoHooksLU):
 
     # FIXME: verify OS list
     # do local checksums
-    file_names = constants.CLUSTER_CONF_FILES
+    file_names = list(self.sstore.GetFileList())
+    file_names.append(constants.SSL_CERT_FILE)
+    file_names.append(constants.CLUSTER_CONF_FILE)
     local_checksums = utils.FingerprintFiles(file_names)
 
     feedback_fn("* Gathering data (%d nodes)" % len(nodelist))
-    all_configfile = rpc.call_configfile_list(nodelist)
     all_volumeinfo = rpc.call_volume_list(nodelist, vg_name)
     all_instanceinfo = rpc.call_instance_list(nodelist)
     all_vglist = rpc.call_vg_list(nodelist)
@@ -780,16 +801,6 @@ class LUVerifyCluster(NoHooksLU):
                                 all_vglist[node], all_nvinfo[node],
                                 all_rversion[node], feedback_fn)
       bad = bad or result
-      # node_configfile
-      nodeconfigfile = all_configfile[node]
-
-      if not nodeconfigfile:
-        feedback_fn("  - ERROR: connection to %s failed" % (node))
-        bad = True
-        continue
-
-      bad = bad or self._VerifyNodeConfigFiles(node==master, node,
-                                               nodeconfigfile, feedback_fn)
 
       # node_volume
       volumeinfo = all_volumeinfo[node]
@@ -902,7 +913,6 @@ def _CheckDiskConsistency(cfgw, dev, node, on_primary):
   """Check that mirrors are not degraded.
 
   """
-
   cfgw.SetDiskID(dev, node)
 
   result = True
@@ -975,7 +985,6 @@ class LURemoveNode(LogicalUnit):
     Any errors are signalled by raising errors.OpPrereqError.
 
     """
-
     node = self.cfg.GetNodeInfo(self.cfg.ExpandNodeName(self.op.node_name))
     if node is None:
       logger.Error("Error: Node '%s' is unknown." % self.op.node_name)
@@ -1028,15 +1037,12 @@ class LUQueryNodes(NoHooksLU):
     This checks that the fields required are valid output fields.
 
     """
-    self.static_fields = frozenset(["name", "pinst", "sinst", "pip", "sip"])
     self.dynamic_fields = frozenset(["dtotal", "dfree",
                                      "mtotal", "mnode", "mfree"])
-    self.all_fields = self.static_fields | self.dynamic_fields
 
-    if not self.all_fields.issuperset(self.op.output_fields):
-      raise errors.OpPrereqError, ("Unknown output fields selected: %s"
-                                   % ",".join(frozenset(self.op.output_fields).
-                                              difference(self.all_fields)))
+    _CheckOutputFields(static=["name", "pinst", "sinst", "pip", "sip"],
+                       dynamic=self.dynamic_fields,
+                       selected=self.op.output_fields)
 
 
   def Exec(self, feedback_fn):
@@ -1106,40 +1112,71 @@ class LUQueryNodes(NoHooksLU):
     return output
 
 
-def _CheckNodesDirs(node_list, paths):
-  """Verify if the given nodes have the same files.
+class LUQueryNodeVolumes(NoHooksLU):
+  """Logical unit for getting volumes on node(s).
 
-  Args:
-    node_list: the list of node names to check
-    paths: the list of directories to checksum and compare
+  """
+  _OP_REQP = ["nodes", "output_fields"]
 
-  Returns:
-    list of (node, different_file, message); if empty, the files are in sync
+  def CheckPrereq(self):
+    """Check prerequisites.
 
-  """
-  file_names = []
-  for dir_name in paths:
-    flist = [os.path.join(dir_name, name) for name in os.listdir(dir_name)]
-    flist = [name for name in flist if os.path.isfile(name)]
-    file_names.extend(flist)
-
-  local_checksums = utils.FingerprintFiles(file_names)
-
-  results = []
-  verify_params = {'filelist': file_names}
-  all_node_results = rpc.call_node_verify(node_list, verify_params)
-  for node_name in node_list:
-    node_result = all_node_results.get(node_name, False)
-    if not node_result or 'filelist' not in node_result:
-      results.append((node_name, "'all files'", "node communication error"))
-      continue
-    remote_checksums = node_result['filelist']
-    for fname in local_checksums:
-      if fname not in remote_checksums:
-        results.append((node_name, fname, "missing file"))
-      elif remote_checksums[fname] != local_checksums[fname]:
-        results.append((node_name, fname, "wrong checksum"))
-  return results
+    This checks that the fields required are valid output fields.
+
+    """
+    self.nodes = _GetWantedNodes(self, self.op.nodes)
+
+    _CheckOutputFields(static=["node"],
+                       dynamic=["phys", "vg", "name", "size", "instance"],
+                       selected=self.op.output_fields)
+
+
+  def Exec(self, feedback_fn):
+    """Computes the list of nodes and their attributes.
+
+    """
+    nodenames = utils.NiceSort([node.name for node in self.nodes])
+    volumes = rpc.call_node_volumes(nodenames)
+
+    ilist = [self.cfg.GetInstanceInfo(iname) for iname
+             in self.cfg.GetInstanceList()]
+
+    lv_by_node = dict([(inst, inst.MapLVsByNode()) for inst in ilist])
+
+    output = []
+    for node in nodenames:
+      node_vols = volumes[node][:]
+      node_vols.sort(key=lambda vol: vol['dev'])
+
+      for vol in node_vols:
+        node_output = []
+        for field in self.op.output_fields:
+          if field == "node":
+            val = node
+          elif field == "phys":
+            val = vol['dev']
+          elif field == "vg":
+            val = vol['vg']
+          elif field == "name":
+            val = vol['name']
+          elif field == "size":
+            val = int(float(vol['size']))
+          elif field == "instance":
+            for inst in ilist:
+              if node not in lv_by_node[inst]:
+                continue
+              if vol['name'] in lv_by_node[inst][node]:
+                val = inst.name
+                break
+            else:
+              val = '-'
+          else:
+            raise errors.ParameterError, field
+          node_output.append(str(val))
+
+        output.append(node_output)
+
+    return output
 
 
 class LUAddNode(LogicalUnit):
@@ -1345,8 +1382,7 @@ class LUAddNode(LogicalUnit):
           logger.Error("copy of file %s to node %s failed" %
                        (fname, to_node))
 
-    to_copy = [constants.MASTER_CRON_FILE]
-    to_copy.extend(ss.GetFileList())
+    to_copy = ss.GetFileList()
     for fname in to_copy:
       if not ssh.CopyFileToNode(node, fname):
         logger.Error("could not copy file %s to node %s" % (fname, node))
@@ -1403,7 +1439,6 @@ class LUMasterFailover(LogicalUnit):
     master.
 
     """
-
     #TODO: do not rely on gethostname returning the FQDN
     logger.Info("setting master to %s, old master: %s" %
                 (self.new_master, self.old_master))
@@ -1477,16 +1512,8 @@ class LUClusterCopyFile(NoHooksLU):
     """
     if not os.path.exists(self.op.filename):
       raise errors.OpPrereqError("No such filename '%s'" % self.op.filename)
-    if self.op.nodes:
-      nodes = self.op.nodes
-    else:
-      nodes = self.cfg.GetNodeList()
-    self.nodes = []
-    for node in nodes:
-      nname = self.cfg.ExpandNodeName(node)
-      if nname is None:
-        raise errors.OpPrereqError, ("Node '%s' is unknown." % node)
-      self.nodes.append(nname)
+
+    self.nodes = _GetWantedNodes(self, self.op.nodes)
 
   def Exec(self, feedback_fn):
     """Copy a file from master to some nodes.
@@ -1540,16 +1567,7 @@ class LURunClusterCommand(NoHooksLU):
     It checks that the given list of nodes is valid.
 
     """
-    if self.op.nodes:
-      nodes = self.op.nodes
-    else:
-      nodes = self.cfg.GetNodeList()
-    self.nodes = []
-    for node in nodes:
-      nname = self.cfg.ExpandNodeName(node)
-      if nname is None:
-        raise errors.OpPrereqError, ("Node '%s' is unknown." % node)
-      self.nodes.append(nname)
+    self.nodes = _GetWantedNodes(self, self.op.nodes)
 
   def Exec(self, feedback_fn):
     """Run a command on some nodes.
@@ -1557,8 +1575,8 @@ class LURunClusterCommand(NoHooksLU):
     """
     data = []
     for node in self.nodes:
-      result = utils.RunCmd(["ssh", node, self.op.command])
-      data.append((node, result.cmd, result.output, result.exit_code))
+      result = utils.RunCmd(["ssh", node.name, self.op.command])
+      data.append((node.name, result.cmd, result.output, result.exit_code))
 
     return data
 
@@ -1630,6 +1648,17 @@ def _AssembleInstanceDisks(instance, cfg, ignore_secondaries=False):
   return disks_ok, device_info
 
 
+def _StartInstanceDisks(cfg, instance, force):
+  disks_ok, dummy = _AssembleInstanceDisks(instance, cfg,
+                                           ignore_secondaries=force)
+  if not disks_ok:
+    _ShutdownInstanceDisks(instance, cfg)
+    if force is not None and not force:
+      logger.Error("If the message above refers to a secondary node,"
+                   " you can retry the operation using '--force'.")
+    raise errors.OpExecError, ("Disk consistency error")
+
+
 class LUDeactivateInstanceDisks(NoHooksLU):
   """Shutdown an instance's disks.
 
@@ -1758,14 +1787,7 @@ class LUStartupInstance(LogicalUnit):
                                  (instance.name, node_current, memory,
                                   freememory))
 
-    disks_ok, dummy = _AssembleInstanceDisks(instance, self.cfg,
-                                             ignore_secondaries=force)
-    if not disks_ok:
-      _ShutdownInstanceDisks(instance, self.cfg)
-      if not force:
-        logger.Error("If the message above refers to a secondary node,"
-                     " you can retry the operation using '--force'.")
-      raise errors.OpExecError, ("Disk consistency error")
+    _StartInstanceDisks(self.cfg, instance, force)
 
     if not rpc.call_instance_start(node_current, instance, extra_args):
       _ShutdownInstanceDisks(instance, self.cfg)
@@ -1823,6 +1845,70 @@ class LUShutdownInstance(LogicalUnit):
     _ShutdownInstanceDisks(instance, self.cfg)
 
 
+class LUReinstallInstance(LogicalUnit):
+  """Reinstall an instance.
+
+  """
+  HPATH = "instance-reinstall"
+  HTYPE = constants.HTYPE_INSTANCE
+  _OP_REQP = ["instance_name"]
+
+  def BuildHooksEnv(self):
+    """Build hooks env.
+
+    This runs on master, primary and secondary nodes of the instance.
+
+    """
+    env = {
+      "INSTANCE_NAME": self.op.instance_name,
+      "INSTANCE_PRIMARY": self.instance.primary_node,
+      "INSTANCE_SECONDARIES": " ".join(self.instance.secondary_nodes),
+      }
+    nl = ([self.sstore.GetMasterNode(), self.instance.primary_node] +
+          list(self.instance.secondary_nodes))
+    return env, nl, nl
+
+  def CheckPrereq(self):
+    """Check prerequisites.
+
+    This checks that the instance is in the cluster and is not running.
+
+    """
+    instance = self.cfg.GetInstanceInfo(
+      self.cfg.ExpandInstanceName(self.op.instance_name))
+    if instance is None:
+      raise errors.OpPrereqError, ("Instance '%s' not known" %
+                                   self.op.instance_name)
+    if instance.disk_template == constants.DT_DISKLESS:
+      raise errors.OpPrereqError, ("Instance '%s' has no disks" %
+                                   self.op.instance_name)
+    if instance.status != "down":
+      raise errors.OpPrereqError, ("Instance '%s' is marked to be up" %
+                                   self.op.instance_name)
+    remote_info = rpc.call_instance_info(instance.primary_node, instance.name)
+    if remote_info:
+      raise errors.OpPrereqError, ("Instance '%s' is running on the node %s" %
+                                   (self.op.instance_name,
+                                    instance.primary_node))
+    self.instance = instance
+
+  def Exec(self, feedback_fn):
+    """Reinstall the instance.
+
+    """
+    inst = self.instance
+
+    _StartInstanceDisks(self.cfg, inst, None)
+    try:
+      feedback_fn("Running the instance OS create scripts...")
+      if not rpc.call_instance_os_add(inst.primary_node, inst, "sda", "sdb"):
+        raise errors.OpExecError, ("Could not install OS for instance %s "
+                                   "on node %s" %
+                                   (inst.name, inst.primary_node))
+    finally:
+      _ShutdownInstanceDisks(inst, self.cfg)
+
+
 class LURemoveInstance(LogicalUnit):
   """Remove an instance.
 
@@ -1884,7 +1970,7 @@ class LUQueryInstances(NoHooksLU):
   """Logical unit for querying instances.
 
   """
-  OP_REQP = ["output_fields"]
+  _OP_REQP = ["output_fields"]
 
   def CheckPrereq(self):
     """Check prerequisites.
@@ -1892,23 +1978,17 @@ class LUQueryInstances(NoHooksLU):
     This checks that the fields required are valid output fields.
 
     """
-
-    self.static_fields = frozenset(["name", "os", "pnode", "snodes",
-                                    "admin_state", "admin_ram",
-                                    "disk_template", "ip", "mac", "bridge"])
     self.dynamic_fields = frozenset(["oper_state", "oper_ram"])
-    self.all_fields = self.static_fields | self.dynamic_fields
-
-    if not self.all_fields.issuperset(self.op.output_fields):
-      raise errors.OpPrereqError, ("Unknown output fields selected: %s"
-                                   % ",".join(frozenset(self.op.output_fields).
-                                              difference(self.all_fields)))
+    _CheckOutputFields(static=["name", "os", "pnode", "snodes",
+                               "admin_state", "admin_ram",
+                               "disk_template", "ip", "mac", "bridge"],
+                       dynamic=self.dynamic_fields,
+                       selected=self.op.output_fields)
 
   def Exec(self, feedback_fn):
     """Computes the list of nodes and their attributes.
 
     """
-
     instance_names = utils.NiceSort(self.cfg.GetInstanceList())
     instance_list = [self.cfg.GetInstanceInfo(iname) for iname
                      in instance_names]
@@ -2116,7 +2196,6 @@ def _CreateBlockDevOnPrimary(cfg, node, device):
   This always creates all devices.
 
   """
-
   if device.children:
     for child in device.children:
       if not _CreateBlockDevOnPrimary(cfg, node, child):
@@ -2384,7 +2463,7 @@ class LUCreateInstance(LogicalUnit):
     # check primary node
     pnode = self.cfg.GetNodeInfo(self.cfg.ExpandNodeName(self.op.pnode))
     if pnode is None:
-      raise errors.OpPrereqError, ("Primary node '%s' is uknown" %
+      raise errors.OpPrereqError, ("Primary node '%s' is unknown" %
                                    self.op.pnode)
     self.op.pnode = pnode.name
     self.pnode = pnode
@@ -3028,7 +3107,6 @@ class LUQueryInstanceData(NoHooksLU):
 
   def Exec(self, feedback_fn):
     """Gather and return data"""
-
     result = {}
     for instance in self.wanted_instances:
       remote_info = rpc.call_instance_info(instance.primary_node,
@@ -3074,26 +3152,12 @@ class LUQueryNodeData(NoHooksLU):
     This only checks the optional node list against the existing names.
 
     """
-    if not isinstance(self.op.nodes, list):
-      raise errors.OpPrereqError, "Invalid argument type 'nodes'"
-    if self.op.nodes:
-      self.wanted_nodes = []
-      names = self.op.nodes
-      for name in names:
-        node = self.cfg.GetNodeInfo(self.cfg.ExpandNodeName(name))
-        if node is None:
-          raise errors.OpPrereqError, ("No such node name '%s'" % name)
-      self.wanted_nodes.append(node)
-    else:
-      self.wanted_nodes = [self.cfg.GetNodeInfo(name) for name
-                           in self.cfg.GetNodeList()]
-    return
+    self.wanted_nodes = _GetWantedNodes(self, self.op.nodes)
 
   def Exec(self, feedback_fn):
     """Compute and return the list of nodes.
 
     """
-
     ilist = [self.cfg.GetInstanceInfo(iname) for iname
              in self.cfg.GetInstanceList()]
     result = []
@@ -3214,18 +3278,9 @@ class LUQueryExports(NoHooksLU):
     """Check that the nodelist contains only existing nodes.
 
     """
-    nodes = getattr(self.op, "nodes", None)
-    if not nodes:
-      self.op.nodes = self.cfg.GetNodeList()
-    else:
-      expnodes = [self.cfg.ExpandNodeName(node) for node in nodes]
-      if expnodes.count(None) > 0:
-        raise errors.OpPrereqError, ("At least one of the given nodes %s"
-                                     " is unknown" % self.op.nodes)
-      self.op.nodes = expnodes
+    self.nodes = _GetWantedNodes(self, getattr(self.op, "nodes", None))
 
   def Exec(self, feedback_fn):
-
     """Compute the list of all the exported system images.
 
     Returns:
@@ -3234,7 +3289,7 @@ class LUQueryExports(NoHooksLU):
       that node.
 
     """
-    return rpc.call_export_list(self.op.nodes)
+    return rpc.call_export_list([node.name for node in self.nodes])
 
 
 class LUExportInstance(LogicalUnit):
@@ -3277,7 +3332,7 @@ class LUExportInstance(LogicalUnit):
     self.dst_node = self.cfg.GetNodeInfo(dst_node_short)
 
     if self.dst_node is None:
-      raise errors.OpPrereqError, ("Destination node '%s' is uknown." %
+      raise errors.OpPrereqError, ("Destination node '%s' is unknown." %
                                    self.op.target_node)
     self.op.target_node = self.dst_node.name