Revision b0d85178 lib/cmdlib.py

b/lib/cmdlib.py
1115 1115
  ENODELVM = (TNODE, "ENODELVM")
1116 1116
  ENODEN1 = (TNODE, "ENODEN1")
1117 1117
  ENODENET = (TNODE, "ENODENET")
1118
  ENODEOS = (TNODE, "ENODEOS")
1118 1119
  ENODEORPHANINSTANCE = (TNODE, "ENODEORPHANINSTANCE")
1119 1120
  ENODEORPHANLV = (TNODE, "ENODEORPHANLV")
1120 1121
  ENODERPC = (TNODE, "ENODERPC")
......
1130 1131
  class NodeImage(object):
1131 1132
    """A class representing the logical and physical status of a node.
1132 1133

  
1134
    @type name: string
1135
    @ivar name: the node name to which this object refers
1133 1136
    @ivar volumes: a structure as returned from
1134 1137
        L{ganeti.backend.GetVolumeList} (runtime)
1135 1138
    @ivar instances: a list of running instances (runtime)
......
1149 1152
    @ivar hyp_fail: whether the RPC call didn't return the instance list
1150 1153
    @type ghost: boolean
1151 1154
    @ivar ghost: whether this is a known node or not (config)
1155
    @type os_fail: boolean
1156
    @ivar os_fail: whether the RPC call didn't return valid OS data
1157
    @type oslist: list
1158
    @ivar oslist: list of OSes as diagnosed by DiagnoseOS
1152 1159

  
1153 1160
    """
1154
    def __init__(self, offline=False):
1161
    def __init__(self, offline=False, name=None):
1162
      self.name = name
1155 1163
      self.volumes = {}
1156 1164
      self.instances = []
1157 1165
      self.pinst = []
......
1164 1172
      self.lvm_fail = False
1165 1173
      self.hyp_fail = False
1166 1174
      self.ghost = False
1175
      self.os_fail = False
1176
      self.oslist = {}
1167 1177

  
1168 1178
  def ExpandNames(self):
1169 1179
    self.needed_locks = {
......
1574 1584
      _ErrorIf(test, self.ENODEDRBD, node,
1575 1585
               "unallocated drbd minor %d is in use", minor)
1576 1586

  
1587
  def _UpdateNodeOS(self, ninfo, nresult, nimg):
1588
    """Builds the node OS structures.
1589

  
1590
    @type ninfo: L{objects.Node}
1591
    @param ninfo: the node to check
1592
    @param nresult: the remote results for the node
1593
    @param nimg: the node image object
1594

  
1595
    """
1596
    node = ninfo.name
1597
    _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1598

  
1599
    remote_os = nresult.get(constants.NV_OSLIST, None)
1600
    test = (not isinstance(remote_os, list) or
1601
            not compat.all(remote_os,
1602
                           lambda v: isinstance(v, list) and len(v) == 7))
1603

  
1604
    _ErrorIf(test, self.ENODEOS, node,
1605
             "node hasn't returned valid OS data")
1606

  
1607
    nimg.os_fail = test
1608

  
1609
    if test:
1610
      return
1611

  
1612
    os_dict = {}
1613

  
1614
    for (name, os_path, status, diagnose,
1615
         variants, parameters, api_ver) in nresult[constants.NV_OSLIST]:
1616

  
1617
      if name not in os_dict:
1618
        os_dict[name] = []
1619

  
1620
      # parameters is a list of lists instead of list of tuples due to
1621
      # JSON lacking a real tuple type, fix it:
1622
      parameters = [tuple(v) for v in parameters]
1623
      os_dict[name].append((os_path, status, diagnose,
1624
                            set(variants), set(parameters), set(api_ver)))
1625

  
1626
    nimg.oslist = os_dict
1627

  
1628
  def _VerifyNodeOS(self, ninfo, nimg, base):
1629
    """Verifies the node OS list.
1630

  
1631
    @type ninfo: L{objects.Node}
1632
    @param ninfo: the node to check
1633
    @param nimg: the node image object
1634
    @param base: the 'template' node we match against (e.g. from the master)
1635

  
1636
    """
1637
    node = ninfo.name
1638
    _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1639

  
1640
    assert not nimg.os_fail, "Entered _VerifyNodeOS with failed OS rpc?"
1641

  
1642
    for os_name, os_data in nimg.oslist.items():
1643
      assert os_data, "Empty OS status for OS %s?!" % os_name
1644
      f_path, f_status, f_diag, f_var, f_param, f_api = os_data[0]
1645
      _ErrorIf(not f_status, self.ENODEOS, node,
1646
               "Invalid OS %s (located at %s): %s", os_name, f_path, f_diag)
1647
      _ErrorIf(len(os_data) > 1, self.ENODEOS, node,
1648
               "OS '%s' has multiple entries (first one shadows the rest): %s",
1649
               os_name, utils.CommaJoin([v[0] for v in os_data]))
1650
      # this will catched in backend too
1651
      _ErrorIf(compat.any(f_api, lambda v: v >= constants.OS_API_V15)
1652
               and not f_var, self.ENODEOS, node,
1653
               "OS %s with API at least %d does not declare any variant",
1654
               os_name, constants.OS_API_V15)
1655
      # comparisons with the 'base' image
1656
      test = os_name not in base.oslist
1657
      _ErrorIf(test, self.ENODEOS, node,
1658
               "Extra OS %s not present on reference node (%s)",
1659
               os_name, base.name)
1660
      if test:
1661
        continue
1662
      assert base.oslist[os_name], "Base node has empty OS status?"
1663
      _, b_status, _, b_var, b_param, b_api = base.oslist[os_name][0]
1664
      if not b_status:
1665
        # base OS is invalid, skipping
1666
        continue
1667
      for kind, a, b in [("API version", f_api, b_api),
1668
                         ("variants list", f_var, b_var),
1669
                         ("parameters", f_param, b_param)]:
1670
        _ErrorIf(a != b, self.ENODEOS, node,
1671
                 "OS %s %s differs from reference node %s: %s vs. %s",
1672
                 kind, os_name, base.name,
1673
                 utils.CommaJoin(a), utils.CommaJoin(a))
1674

  
1675
    # check any missing OSes
1676
    missing = set(base.oslist.keys()).difference(nimg.oslist.keys())
1677
    _ErrorIf(missing, self.ENODEOS, node,
1678
             "OSes present on reference node %s but missing on this node: %s",
1679
             base.name, utils.CommaJoin(missing))
1680

  
1577 1681
  def _UpdateNodeVolumes(self, ninfo, nresult, nimg, vg_name):
1578 1682
    """Verifies and updates the node volume data.
1579 1683

  
......
1751 1855
      constants.NV_NODESETUP: None,
1752 1856
      constants.NV_TIME: None,
1753 1857
      constants.NV_MASTERIP: (master_node, master_ip),
1858
      constants.NV_OSLIST: None,
1754 1859
      }
1755 1860

  
1756 1861
    if vg_name is not None:
......
1760 1865
      node_verify_param[constants.NV_DRBDLIST] = None
1761 1866

  
1762 1867
    # Build our expected cluster state
1763
    node_image = dict((node.name, self.NodeImage(offline=node.offline))
1868
    node_image = dict((node.name, self.NodeImage(offline=node.offline,
1869
                                                 name=node.name))
1764 1870
                      for node in nodeinfo)
1765 1871

  
1766 1872
    for instance in instancelist:
......
1769 1875
      for nname in inst_config.all_nodes:
1770 1876
        if nname not in node_image:
1771 1877
          # ghost node
1772
          gnode = self.NodeImage()
1878
          gnode = self.NodeImage(name=nname)
1773 1879
          gnode.ghost = True
1774 1880
          node_image[nname] = gnode
1775 1881

  
......
1800 1906
    all_drbd_map = self.cfg.ComputeDRBDMap()
1801 1907

  
1802 1908
    feedback_fn("* Verifying node status")
1909

  
1910
    refos_img = None
1911

  
1803 1912
    for node_i in nodeinfo:
1804 1913
      node = node_i.name
1805 1914
      nimg = node_image[node]
......
1841 1950
      self._UpdateNodeVolumes(node_i, nresult, nimg, vg_name)
1842 1951
      self._UpdateNodeInstances(node_i, nresult, nimg)
1843 1952
      self._UpdateNodeInfo(node_i, nresult, nimg, vg_name)
1953
      self._UpdateNodeOS(node_i, nresult, nimg)
1954
      if not nimg.os_fail:
1955
        if refos_img is None:
1956
          refos_img = nimg
1957
        self._VerifyNodeOS(node_i, nimg, refos_img)
1844 1958

  
1845 1959
    feedback_fn("* Verifying instance status")
1846 1960
    for instance in instancelist:

Also available in: Unified diff