Version bump for 2.8.4 and NEWS update
[ganeti-local] / lib / cmdlib / operating_system.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Logical units dealing with OS."""
23
24 from ganeti import compat
25 from ganeti import locking
26 from ganeti import qlang
27 from ganeti import query
28 from ganeti.cmdlib.base import QueryBase, NoHooksLU
29
30
31 class OsQuery(QueryBase):
32   FIELDS = query.OS_FIELDS
33
34   def ExpandNames(self, lu):
35     # Lock all nodes in shared mode
36     # Temporary removal of locks, should be reverted later
37     # TODO: reintroduce locks when they are lighter-weight
38     lu.needed_locks = {}
39     #self.share_locks[locking.LEVEL_NODE] = 1
40     #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
41
42     # The following variables interact with _QueryBase._GetNames
43     if self.names:
44       self.wanted = self.names
45     else:
46       self.wanted = locking.ALL_SET
47
48     self.do_locking = self.use_locking
49
50   def DeclareLocks(self, lu, level):
51     pass
52
53   @staticmethod
54   def _DiagnoseByOS(rlist):
55     """Remaps a per-node return list into an a per-os per-node dictionary
56
57     @param rlist: a map with node names as keys and OS objects as values
58
59     @rtype: dict
60     @return: a dictionary with osnames as keys and as value another
61         map, with nodes as keys and tuples of (path, status, diagnose,
62         variants, parameters, api_versions) as values, eg::
63
64           {"debian-etch": {"node1": [(/usr/lib/..., True, "", [], []),
65                                      (/srv/..., False, "invalid api")],
66                            "node2": [(/srv/..., True, "", [], [])]}
67           }
68
69     """
70     all_os = {}
71     # we build here the list of nodes that didn't fail the RPC (at RPC
72     # level), so that nodes with a non-responding node daemon don't
73     # make all OSes invalid
74     good_nodes = [node_name for node_name in rlist
75                   if not rlist[node_name].fail_msg]
76     for node_name, nr in rlist.items():
77       if nr.fail_msg or not nr.payload:
78         continue
79       for (name, path, status, diagnose, variants,
80            params, api_versions) in nr.payload:
81         if name not in all_os:
82           # build a list of nodes for this os containing empty lists
83           # for each node in node_list
84           all_os[name] = {}
85           for nname in good_nodes:
86             all_os[name][nname] = []
87         # convert params from [name, help] to (name, help)
88         params = [tuple(v) for v in params]
89         all_os[name][node_name].append((path, status, diagnose,
90                                         variants, params, api_versions))
91     return all_os
92
93   def _GetQueryData(self, lu):
94     """Computes the list of nodes and their attributes.
95
96     """
97     # Locking is not used
98     assert not (compat.any(lu.glm.is_owned(level)
99                            for level in locking.LEVELS
100                            if level != locking.LEVEL_CLUSTER) or
101                 self.do_locking or self.use_locking)
102
103     valid_nodes = [node.name
104                    for node in lu.cfg.GetAllNodesInfo().values()
105                    if not node.offline and node.vm_capable]
106     pol = self._DiagnoseByOS(lu.rpc.call_os_diagnose(valid_nodes))
107     cluster = lu.cfg.GetClusterInfo()
108
109     data = {}
110
111     for (os_name, os_data) in pol.items():
112       info = query.OsInfo(name=os_name, valid=True, node_status=os_data,
113                           hidden=(os_name in cluster.hidden_os),
114                           blacklisted=(os_name in cluster.blacklisted_os))
115
116       variants = set()
117       parameters = set()
118       api_versions = set()
119
120       for idx, osl in enumerate(os_data.values()):
121         info.valid = bool(info.valid and osl and osl[0][1])
122         if not info.valid:
123           break
124
125         (node_variants, node_params, node_api) = osl[0][3:6]
126         if idx == 0:
127           # First entry
128           variants.update(node_variants)
129           parameters.update(node_params)
130           api_versions.update(node_api)
131         else:
132           # Filter out inconsistent values
133           variants.intersection_update(node_variants)
134           parameters.intersection_update(node_params)
135           api_versions.intersection_update(node_api)
136
137       info.variants = list(variants)
138       info.parameters = list(parameters)
139       info.api_versions = list(api_versions)
140
141       data[os_name] = info
142
143     # Prepare data in requested order
144     return [data[name] for name in self._GetNames(lu, pol.keys(), None)
145             if name in data]
146
147
148 class LUOsDiagnose(NoHooksLU):
149   """Logical unit for OS diagnose/query.
150
151   """
152   REQ_BGL = False
153
154   @staticmethod
155   def _BuildFilter(fields, names):
156     """Builds a filter for querying OSes.
157
158     """
159     name_filter = qlang.MakeSimpleFilter("name", names)
160
161     # Legacy behaviour: Hide hidden, blacklisted or invalid OSes if the
162     # respective field is not requested
163     status_filter = [[qlang.OP_NOT, [qlang.OP_TRUE, fname]]
164                      for fname in ["hidden", "blacklisted"]
165                      if fname not in fields]
166     if "valid" not in fields:
167       status_filter.append([qlang.OP_TRUE, "valid"])
168
169     if status_filter:
170       status_filter.insert(0, qlang.OP_AND)
171     else:
172       status_filter = None
173
174     if name_filter and status_filter:
175       return [qlang.OP_AND, name_filter, status_filter]
176     elif name_filter:
177       return name_filter
178     else:
179       return status_filter
180
181   def CheckArguments(self):
182     self.oq = OsQuery(self._BuildFilter(self.op.output_fields, self.op.names),
183                        self.op.output_fields, False)
184
185   def ExpandNames(self):
186     self.oq.ExpandNames(self)
187
188   def Exec(self, feedback_fn):
189     return self.oq.OldStyleQuery(self)