Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 69cf3abd

History | View | Annotate | Download (56.4 kB)

1 2f31098c Iustin Pop
#
2 a8083063 Iustin Pop
#
3 a8083063 Iustin Pop
4 7f93570a Iustin Pop
# Copyright (C) 2006, 2007, 2008, 2009, 2010 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 a8083063 Iustin Pop
"""Configuration management for Ganeti
23 a8083063 Iustin Pop

24 319856a9 Michael Hanselmann
This module provides the interface to the Ganeti cluster configuration.
25 a8083063 Iustin Pop

26 319856a9 Michael Hanselmann
The configuration data is stored on every node but is updated on the master
27 319856a9 Michael Hanselmann
only. After each update, the master distributes the data to the other nodes.
28 a8083063 Iustin Pop

29 319856a9 Michael Hanselmann
Currently, the data storage format is JSON. YAML was slow and consuming too
30 319856a9 Michael Hanselmann
much memory.
31 a8083063 Iustin Pop

32 a8083063 Iustin Pop
"""
33 a8083063 Iustin Pop
34 d367b66c Manuel Franceschini
# pylint: disable-msg=R0904
35 d367b66c Manuel Franceschini
# R0904: Too many public methods
36 d367b66c Manuel Franceschini
37 a8083063 Iustin Pop
import os
38 a8083063 Iustin Pop
import random
39 d8470559 Michael Hanselmann
import logging
40 d693c864 Iustin Pop
import time
41 a8083063 Iustin Pop
42 a8083063 Iustin Pop
from ganeti import errors
43 f78ede4e Guido Trotter
from ganeti import locking
44 a8083063 Iustin Pop
from ganeti import utils
45 a8083063 Iustin Pop
from ganeti import constants
46 a8083063 Iustin Pop
from ganeti import rpc
47 a8083063 Iustin Pop
from ganeti import objects
48 8d14b30d Iustin Pop
from ganeti import serializer
49 0fbae49a Balazs Lecz
from ganeti import uidpool
50 a744b676 Manuel Franceschini
from ganeti import netutils
51 e60c73a1 René Nussbaumer
from ganeti import runtime
52 243cdbcc Michael Hanselmann
53 243cdbcc Michael Hanselmann
54 7f93570a Iustin Pop
_config_lock = locking.SharedLock("ConfigWriter")
55 f78ede4e Guido Trotter
56 4fae38c5 Guido Trotter
# job id used for resource management at config upgrade time
57 8d9c3bef Michael Hanselmann
_UPGRADE_CONFIG_JID = "jid-cfg-upgrade"
58 4fae38c5 Guido Trotter
59 f78ede4e Guido Trotter
60 5b263ed7 Michael Hanselmann
def _ValidateConfig(data):
61 c41eea6e Iustin Pop
  """Verifies that a configuration objects looks valid.
62 c41eea6e Iustin Pop

63 c41eea6e Iustin Pop
  This only verifies the version of the configuration.
64 c41eea6e Iustin Pop

65 c41eea6e Iustin Pop
  @raise errors.ConfigurationError: if the version differs from what
66 c41eea6e Iustin Pop
      we expect
67 c41eea6e Iustin Pop

68 c41eea6e Iustin Pop
  """
69 5b263ed7 Michael Hanselmann
  if data.version != constants.CONFIG_VERSION:
70 4b63dc7a Iustin Pop
    raise errors.ConfigVersionMismatch(constants.CONFIG_VERSION, data.version)
71 a8083063 Iustin Pop
72 319856a9 Michael Hanselmann
73 013da361 Guido Trotter
class TemporaryReservationManager:
74 013da361 Guido Trotter
  """A temporary resource reservation manager.
75 013da361 Guido Trotter

76 013da361 Guido Trotter
  This is used to reserve resources in a job, before using them, making sure
77 013da361 Guido Trotter
  other jobs cannot get them in the meantime.
78 013da361 Guido Trotter

79 013da361 Guido Trotter
  """
80 013da361 Guido Trotter
  def __init__(self):
81 013da361 Guido Trotter
    self._ec_reserved = {}
82 013da361 Guido Trotter
83 013da361 Guido Trotter
  def Reserved(self, resource):
84 013da361 Guido Trotter
    for holder_reserved in self._ec_reserved.items():
85 013da361 Guido Trotter
      if resource in holder_reserved:
86 013da361 Guido Trotter
        return True
87 013da361 Guido Trotter
    return False
88 013da361 Guido Trotter
89 013da361 Guido Trotter
  def Reserve(self, ec_id, resource):
90 013da361 Guido Trotter
    if self.Reserved(resource):
91 013da361 Guido Trotter
      raise errors.ReservationError("Duplicate reservation for resource: %s." %
92 013da361 Guido Trotter
                                    (resource))
93 013da361 Guido Trotter
    if ec_id not in self._ec_reserved:
94 013da361 Guido Trotter
      self._ec_reserved[ec_id] = set([resource])
95 013da361 Guido Trotter
    else:
96 013da361 Guido Trotter
      self._ec_reserved[ec_id].add(resource)
97 013da361 Guido Trotter
98 013da361 Guido Trotter
  def DropECReservations(self, ec_id):
99 013da361 Guido Trotter
    if ec_id in self._ec_reserved:
100 013da361 Guido Trotter
      del self._ec_reserved[ec_id]
101 013da361 Guido Trotter
102 013da361 Guido Trotter
  def GetReserved(self):
103 013da361 Guido Trotter
    all_reserved = set()
104 013da361 Guido Trotter
    for holder_reserved in self._ec_reserved.values():
105 013da361 Guido Trotter
      all_reserved.update(holder_reserved)
106 013da361 Guido Trotter
    return all_reserved
107 013da361 Guido Trotter
108 013da361 Guido Trotter
  def Generate(self, existing, generate_one_fn, ec_id):
109 013da361 Guido Trotter
    """Generate a new resource of this type
110 013da361 Guido Trotter

111 013da361 Guido Trotter
    """
112 013da361 Guido Trotter
    assert callable(generate_one_fn)
113 013da361 Guido Trotter
114 013da361 Guido Trotter
    all_elems = self.GetReserved()
115 013da361 Guido Trotter
    all_elems.update(existing)
116 013da361 Guido Trotter
    retries = 64
117 013da361 Guido Trotter
    while retries > 0:
118 013da361 Guido Trotter
      new_resource = generate_one_fn()
119 013da361 Guido Trotter
      if new_resource is not None and new_resource not in all_elems:
120 013da361 Guido Trotter
        break
121 013da361 Guido Trotter
    else:
122 013da361 Guido Trotter
      raise errors.ConfigurationError("Not able generate new resource"
123 013da361 Guido Trotter
                                      " (last tried: %s)" % new_resource)
124 013da361 Guido Trotter
    self.Reserve(ec_id, new_resource)
125 013da361 Guido Trotter
    return new_resource
126 013da361 Guido Trotter
127 013da361 Guido Trotter
128 a8083063 Iustin Pop
class ConfigWriter:
129 098c0958 Michael Hanselmann
  """The interface to the cluster configuration.
130 a8083063 Iustin Pop

131 d8aee57e Iustin Pop
  @ivar _temporary_lvs: reservation manager for temporary LVs
132 d8aee57e Iustin Pop
  @ivar _all_rms: a list of all temporary reservation managers
133 d8aee57e Iustin Pop

134 098c0958 Michael Hanselmann
  """
135 eb180fe2 Iustin Pop
  def __init__(self, cfg_file=None, offline=False, _getents=runtime.GetEnts,
136 eb180fe2 Iustin Pop
               accept_foreign=False):
137 14e15659 Iustin Pop
    self.write_count = 0
138 f78ede4e Guido Trotter
    self._lock = _config_lock
139 a8083063 Iustin Pop
    self._config_data = None
140 a8083063 Iustin Pop
    self._offline = offline
141 a8083063 Iustin Pop
    if cfg_file is None:
142 a8083063 Iustin Pop
      self._cfg_file = constants.CLUSTER_CONF_FILE
143 a8083063 Iustin Pop
    else:
144 a8083063 Iustin Pop
      self._cfg_file = cfg_file
145 e60c73a1 René Nussbaumer
    self._getents = _getents
146 4fae38c5 Guido Trotter
    self._temporary_ids = TemporaryReservationManager()
147 a81c53c9 Iustin Pop
    self._temporary_drbds = {}
148 36b66e6e Guido Trotter
    self._temporary_macs = TemporaryReservationManager()
149 afa1386e Guido Trotter
    self._temporary_secrets = TemporaryReservationManager()
150 d8aee57e Iustin Pop
    self._temporary_lvs = TemporaryReservationManager()
151 d8aee57e Iustin Pop
    self._all_rms = [self._temporary_ids, self._temporary_macs,
152 d8aee57e Iustin Pop
                     self._temporary_secrets, self._temporary_lvs]
153 89e1fc26 Iustin Pop
    # Note: in order to prevent errors when resolving our name in
154 89e1fc26 Iustin Pop
    # _DistributeConfig, we compute it here once and reuse it; it's
155 89e1fc26 Iustin Pop
    # better to raise an error before starting to modify the config
156 89e1fc26 Iustin Pop
    # file than after it was modified
157 b705c7a6 Manuel Franceschini
    self._my_hostname = netutils.Hostname.GetSysName()
158 3c7f6c44 Iustin Pop
    self._last_cluster_serial = -1
159 bd407597 Iustin Pop
    self._cfg_id = None
160 eb180fe2 Iustin Pop
    self._OpenConfig(accept_foreign)
161 a8083063 Iustin Pop
162 a8083063 Iustin Pop
  # this method needs to be static, so that we can call it on the class
163 a8083063 Iustin Pop
  @staticmethod
164 a8083063 Iustin Pop
  def IsCluster():
165 a8083063 Iustin Pop
    """Check if the cluster is configured.
166 a8083063 Iustin Pop

167 a8083063 Iustin Pop
    """
168 a8083063 Iustin Pop
    return os.path.exists(constants.CLUSTER_CONF_FILE)
169 a8083063 Iustin Pop
170 36b66e6e Guido Trotter
  def _GenerateOneMAC(self):
171 36b66e6e Guido Trotter
    """Generate one mac address
172 36b66e6e Guido Trotter

173 36b66e6e Guido Trotter
    """
174 36b66e6e Guido Trotter
    prefix = self._config_data.cluster.mac_prefix
175 36b66e6e Guido Trotter
    byte1 = random.randrange(0, 256)
176 36b66e6e Guido Trotter
    byte2 = random.randrange(0, 256)
177 36b66e6e Guido Trotter
    byte3 = random.randrange(0, 256)
178 36b66e6e Guido Trotter
    mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
179 36b66e6e Guido Trotter
    return mac
180 36b66e6e Guido Trotter
181 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
182 36b66e6e Guido Trotter
  def GenerateMAC(self, ec_id):
183 a8083063 Iustin Pop
    """Generate a MAC for an instance.
184 a8083063 Iustin Pop

185 a8083063 Iustin Pop
    This should check the current instances for duplicates.
186 a8083063 Iustin Pop

187 a8083063 Iustin Pop
    """
188 36b66e6e Guido Trotter
    existing = self._AllMACs()
189 36b66e6e Guido Trotter
    return self._temporary_ids.Generate(existing, self._GenerateOneMAC, ec_id)
190 a8083063 Iustin Pop
191 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
192 36b66e6e Guido Trotter
  def ReserveMAC(self, mac, ec_id):
193 36b66e6e Guido Trotter
    """Reserve a MAC for an instance.
194 1862d460 Alexander Schreiber

195 1862d460 Alexander Schreiber
    This only checks instances managed by this cluster, it does not
196 1862d460 Alexander Schreiber
    check for potential collisions elsewhere.
197 1862d460 Alexander Schreiber

198 1862d460 Alexander Schreiber
    """
199 1862d460 Alexander Schreiber
    all_macs = self._AllMACs()
200 36b66e6e Guido Trotter
    if mac in all_macs:
201 36b66e6e Guido Trotter
      raise errors.ReservationError("mac already in use")
202 36b66e6e Guido Trotter
    else:
203 36b66e6e Guido Trotter
      self._temporary_macs.Reserve(mac, ec_id)
204 1862d460 Alexander Schreiber
205 f9518d38 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
206 d8aee57e Iustin Pop
  def ReserveLV(self, lv_name, ec_id):
207 d8aee57e Iustin Pop
    """Reserve an VG/LV pair for an instance.
208 d8aee57e Iustin Pop

209 d8aee57e Iustin Pop
    @type lv_name: string
210 d8aee57e Iustin Pop
    @param lv_name: the logical volume name to reserve
211 d8aee57e Iustin Pop

212 d8aee57e Iustin Pop
    """
213 d8aee57e Iustin Pop
    all_lvs = self._AllLVs()
214 d8aee57e Iustin Pop
    if lv_name in all_lvs:
215 d8aee57e Iustin Pop
      raise errors.ReservationError("LV already in use")
216 d8aee57e Iustin Pop
    else:
217 d8aee57e Iustin Pop
      self._temporary_lvs.Reserve(lv_name, ec_id)
218 d8aee57e Iustin Pop
219 d8aee57e Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
220 afa1386e Guido Trotter
  def GenerateDRBDSecret(self, ec_id):
221 f9518d38 Iustin Pop
    """Generate a DRBD secret.
222 f9518d38 Iustin Pop

223 f9518d38 Iustin Pop
    This checks the current disks for duplicates.
224 f9518d38 Iustin Pop

225 f9518d38 Iustin Pop
    """
226 afa1386e Guido Trotter
    return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
227 afa1386e Guido Trotter
                                            utils.GenerateSecret,
228 afa1386e Guido Trotter
                                            ec_id)
229 8d9c3bef Michael Hanselmann
230 34e54ebc Iustin Pop
  def _AllLVs(self):
231 923b1523 Iustin Pop
    """Compute the list of all LVs.
232 923b1523 Iustin Pop

233 923b1523 Iustin Pop
    """
234 923b1523 Iustin Pop
    lvnames = set()
235 923b1523 Iustin Pop
    for instance in self._config_data.instances.values():
236 923b1523 Iustin Pop
      node_data = instance.MapLVsByNode()
237 923b1523 Iustin Pop
      for lv_list in node_data.values():
238 923b1523 Iustin Pop
        lvnames.update(lv_list)
239 923b1523 Iustin Pop
    return lvnames
240 923b1523 Iustin Pop
241 34e54ebc Iustin Pop
  def _AllIDs(self, include_temporary):
242 34e54ebc Iustin Pop
    """Compute the list of all UUIDs and names we have.
243 34e54ebc Iustin Pop

244 34e54ebc Iustin Pop
    @type include_temporary: boolean
245 34e54ebc Iustin Pop
    @param include_temporary: whether to include the _temporary_ids set
246 34e54ebc Iustin Pop
    @rtype: set
247 34e54ebc Iustin Pop
    @return: a set of IDs
248 34e54ebc Iustin Pop

249 34e54ebc Iustin Pop
    """
250 34e54ebc Iustin Pop
    existing = set()
251 34e54ebc Iustin Pop
    if include_temporary:
252 4fae38c5 Guido Trotter
      existing.update(self._temporary_ids.GetReserved())
253 34e54ebc Iustin Pop
    existing.update(self._AllLVs())
254 34e54ebc Iustin Pop
    existing.update(self._config_data.instances.keys())
255 34e54ebc Iustin Pop
    existing.update(self._config_data.nodes.keys())
256 76d5d3a3 Iustin Pop
    existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
257 34e54ebc Iustin Pop
    return existing
258 34e54ebc Iustin Pop
259 4fae38c5 Guido Trotter
  def _GenerateUniqueID(self, ec_id):
260 430b923c Iustin Pop
    """Generate an unique UUID.
261 923b1523 Iustin Pop

262 923b1523 Iustin Pop
    This checks the current node, instances and disk names for
263 923b1523 Iustin Pop
    duplicates.
264 923b1523 Iustin Pop

265 c41eea6e Iustin Pop
    @rtype: string
266 c41eea6e Iustin Pop
    @return: the unique id
267 923b1523 Iustin Pop

268 923b1523 Iustin Pop
    """
269 4fae38c5 Guido Trotter
    existing = self._AllIDs(include_temporary=False)
270 4fae38c5 Guido Trotter
    return self._temporary_ids.Generate(existing, utils.NewUUID, ec_id)
271 923b1523 Iustin Pop
272 430b923c Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
273 4fae38c5 Guido Trotter
  def GenerateUniqueID(self, ec_id):
274 430b923c Iustin Pop
    """Generate an unique ID.
275 430b923c Iustin Pop

276 430b923c Iustin Pop
    This is just a wrapper over the unlocked version.
277 430b923c Iustin Pop

278 4fae38c5 Guido Trotter
    @type ec_id: string
279 4fae38c5 Guido Trotter
    @param ec_id: unique id for the job to reserve the id to
280 34d657ba Iustin Pop

281 34d657ba Iustin Pop
    """
282 4fae38c5 Guido Trotter
    return self._GenerateUniqueID(ec_id)
283 34d657ba Iustin Pop
284 a8083063 Iustin Pop
  def _AllMACs(self):
285 a8083063 Iustin Pop
    """Return all MACs present in the config.
286 a8083063 Iustin Pop

287 c41eea6e Iustin Pop
    @rtype: list
288 c41eea6e Iustin Pop
    @return: the list of all MACs
289 c41eea6e Iustin Pop

290 a8083063 Iustin Pop
    """
291 a8083063 Iustin Pop
    result = []
292 a8083063 Iustin Pop
    for instance in self._config_data.instances.values():
293 a8083063 Iustin Pop
      for nic in instance.nics:
294 a8083063 Iustin Pop
        result.append(nic.mac)
295 a8083063 Iustin Pop
296 a8083063 Iustin Pop
    return result
297 a8083063 Iustin Pop
298 f9518d38 Iustin Pop
  def _AllDRBDSecrets(self):
299 f9518d38 Iustin Pop
    """Return all DRBD secrets present in the config.
300 f9518d38 Iustin Pop

301 c41eea6e Iustin Pop
    @rtype: list
302 c41eea6e Iustin Pop
    @return: the list of all DRBD secrets
303 c41eea6e Iustin Pop

304 f9518d38 Iustin Pop
    """
305 f9518d38 Iustin Pop
    def helper(disk, result):
306 f9518d38 Iustin Pop
      """Recursively gather secrets from this disk."""
307 f9518d38 Iustin Pop
      if disk.dev_type == constants.DT_DRBD8:
308 f9518d38 Iustin Pop
        result.append(disk.logical_id[5])
309 f9518d38 Iustin Pop
      if disk.children:
310 f9518d38 Iustin Pop
        for child in disk.children:
311 f9518d38 Iustin Pop
          helper(child, result)
312 f9518d38 Iustin Pop
313 f9518d38 Iustin Pop
    result = []
314 f9518d38 Iustin Pop
    for instance in self._config_data.instances.values():
315 f9518d38 Iustin Pop
      for disk in instance.disks:
316 f9518d38 Iustin Pop
        helper(disk, result)
317 f9518d38 Iustin Pop
318 f9518d38 Iustin Pop
    return result
319 f9518d38 Iustin Pop
320 4b98ac29 Iustin Pop
  def _CheckDiskIDs(self, disk, l_ids, p_ids):
321 4b98ac29 Iustin Pop
    """Compute duplicate disk IDs
322 4b98ac29 Iustin Pop

323 4b98ac29 Iustin Pop
    @type disk: L{objects.Disk}
324 4b98ac29 Iustin Pop
    @param disk: the disk at which to start searching
325 4b98ac29 Iustin Pop
    @type l_ids: list
326 4b98ac29 Iustin Pop
    @param l_ids: list of current logical ids
327 4b98ac29 Iustin Pop
    @type p_ids: list
328 4b98ac29 Iustin Pop
    @param p_ids: list of current physical ids
329 4b98ac29 Iustin Pop
    @rtype: list
330 4b98ac29 Iustin Pop
    @return: a list of error messages
331 4b98ac29 Iustin Pop

332 4b98ac29 Iustin Pop
    """
333 4b98ac29 Iustin Pop
    result = []
334 25ae22e4 Iustin Pop
    if disk.logical_id is not None:
335 25ae22e4 Iustin Pop
      if disk.logical_id in l_ids:
336 25ae22e4 Iustin Pop
        result.append("duplicate logical id %s" % str(disk.logical_id))
337 25ae22e4 Iustin Pop
      else:
338 25ae22e4 Iustin Pop
        l_ids.append(disk.logical_id)
339 25ae22e4 Iustin Pop
    if disk.physical_id is not None:
340 25ae22e4 Iustin Pop
      if disk.physical_id in p_ids:
341 25ae22e4 Iustin Pop
        result.append("duplicate physical id %s" % str(disk.physical_id))
342 25ae22e4 Iustin Pop
      else:
343 25ae22e4 Iustin Pop
        p_ids.append(disk.physical_id)
344 4b98ac29 Iustin Pop
345 4b98ac29 Iustin Pop
    if disk.children:
346 4b98ac29 Iustin Pop
      for child in disk.children:
347 4b98ac29 Iustin Pop
        result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
348 4b98ac29 Iustin Pop
    return result
349 4b98ac29 Iustin Pop
350 4a89c54a Iustin Pop
  def _UnlockedVerifyConfig(self):
351 a8efbb40 Iustin Pop
    """Verify function.
352 a8efbb40 Iustin Pop

353 4a89c54a Iustin Pop
    @rtype: list
354 4a89c54a Iustin Pop
    @return: a list of error messages; a non-empty list signifies
355 4a89c54a Iustin Pop
        configuration errors
356 4a89c54a Iustin Pop

357 a8083063 Iustin Pop
    """
358 a8083063 Iustin Pop
    result = []
359 a8083063 Iustin Pop
    seen_macs = []
360 48ce9fd9 Iustin Pop
    ports = {}
361 a8083063 Iustin Pop
    data = self._config_data
362 4b98ac29 Iustin Pop
    seen_lids = []
363 4b98ac29 Iustin Pop
    seen_pids = []
364 9a5fba23 Guido Trotter
365 9a5fba23 Guido Trotter
    # global cluster checks
366 9a5fba23 Guido Trotter
    if not data.cluster.enabled_hypervisors:
367 9a5fba23 Guido Trotter
      result.append("enabled hypervisors list doesn't have any entries")
368 9a5fba23 Guido Trotter
    invalid_hvs = set(data.cluster.enabled_hypervisors) - constants.HYPER_TYPES
369 9a5fba23 Guido Trotter
    if invalid_hvs:
370 9a5fba23 Guido Trotter
      result.append("enabled hypervisors contains invalid entries: %s" %
371 9a5fba23 Guido Trotter
                    invalid_hvs)
372 9f3ac970 Iustin Pop
    missing_hvp = (set(data.cluster.enabled_hypervisors) -
373 9f3ac970 Iustin Pop
                   set(data.cluster.hvparams.keys()))
374 9f3ac970 Iustin Pop
    if missing_hvp:
375 9f3ac970 Iustin Pop
      result.append("hypervisor parameters missing for the enabled"
376 9f3ac970 Iustin Pop
                    " hypervisor(s) %s" % utils.CommaJoin(missing_hvp))
377 9a5fba23 Guido Trotter
378 9a5fba23 Guido Trotter
    if data.cluster.master_node not in data.nodes:
379 9a5fba23 Guido Trotter
      result.append("cluster has invalid primary node '%s'" %
380 9a5fba23 Guido Trotter
                    data.cluster.master_node)
381 9a5fba23 Guido Trotter
382 9a5fba23 Guido Trotter
    # per-instance checks
383 a8083063 Iustin Pop
    for instance_name in data.instances:
384 a8083063 Iustin Pop
      instance = data.instances[instance_name]
385 81196341 Iustin Pop
      if instance.name != instance_name:
386 81196341 Iustin Pop
        result.append("instance '%s' is indexed by wrong name '%s'" %
387 81196341 Iustin Pop
                      (instance.name, instance_name))
388 a8083063 Iustin Pop
      if instance.primary_node not in data.nodes:
389 8522ceeb Iustin Pop
        result.append("instance '%s' has invalid primary node '%s'" %
390 a8083063 Iustin Pop
                      (instance_name, instance.primary_node))
391 a8083063 Iustin Pop
      for snode in instance.secondary_nodes:
392 a8083063 Iustin Pop
        if snode not in data.nodes:
393 8522ceeb Iustin Pop
          result.append("instance '%s' has invalid secondary node '%s'" %
394 a8083063 Iustin Pop
                        (instance_name, snode))
395 a8083063 Iustin Pop
      for idx, nic in enumerate(instance.nics):
396 a8083063 Iustin Pop
        if nic.mac in seen_macs:
397 8522ceeb Iustin Pop
          result.append("instance '%s' has NIC %d mac %s duplicate" %
398 a8083063 Iustin Pop
                        (instance_name, idx, nic.mac))
399 a8083063 Iustin Pop
        else:
400 a8083063 Iustin Pop
          seen_macs.append(nic.mac)
401 48ce9fd9 Iustin Pop
402 48ce9fd9 Iustin Pop
      # gather the drbd ports for duplicate checks
403 48ce9fd9 Iustin Pop
      for dsk in instance.disks:
404 48ce9fd9 Iustin Pop
        if dsk.dev_type in constants.LDS_DRBD:
405 48ce9fd9 Iustin Pop
          tcp_port = dsk.logical_id[2]
406 48ce9fd9 Iustin Pop
          if tcp_port not in ports:
407 48ce9fd9 Iustin Pop
            ports[tcp_port] = []
408 48ce9fd9 Iustin Pop
          ports[tcp_port].append((instance.name, "drbd disk %s" % dsk.iv_name))
409 48ce9fd9 Iustin Pop
      # gather network port reservation
410 48ce9fd9 Iustin Pop
      net_port = getattr(instance, "network_port", None)
411 48ce9fd9 Iustin Pop
      if net_port is not None:
412 48ce9fd9 Iustin Pop
        if net_port not in ports:
413 48ce9fd9 Iustin Pop
          ports[net_port] = []
414 48ce9fd9 Iustin Pop
        ports[net_port].append((instance.name, "network port"))
415 48ce9fd9 Iustin Pop
416 332d0e37 Iustin Pop
      # instance disk verify
417 332d0e37 Iustin Pop
      for idx, disk in enumerate(instance.disks):
418 332d0e37 Iustin Pop
        result.extend(["instance '%s' disk %d error: %s" %
419 332d0e37 Iustin Pop
                       (instance.name, idx, msg) for msg in disk.Verify()])
420 4b98ac29 Iustin Pop
        result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
421 332d0e37 Iustin Pop
422 48ce9fd9 Iustin Pop
    # cluster-wide pool of free ports
423 a8efbb40 Iustin Pop
    for free_port in data.cluster.tcpudp_port_pool:
424 48ce9fd9 Iustin Pop
      if free_port not in ports:
425 48ce9fd9 Iustin Pop
        ports[free_port] = []
426 48ce9fd9 Iustin Pop
      ports[free_port].append(("cluster", "port marked as free"))
427 48ce9fd9 Iustin Pop
428 48ce9fd9 Iustin Pop
    # compute tcp/udp duplicate ports
429 48ce9fd9 Iustin Pop
    keys = ports.keys()
430 48ce9fd9 Iustin Pop
    keys.sort()
431 48ce9fd9 Iustin Pop
    for pnum in keys:
432 48ce9fd9 Iustin Pop
      pdata = ports[pnum]
433 48ce9fd9 Iustin Pop
      if len(pdata) > 1:
434 1f864b60 Iustin Pop
        txt = utils.CommaJoin(["%s/%s" % val for val in pdata])
435 48ce9fd9 Iustin Pop
        result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
436 48ce9fd9 Iustin Pop
437 48ce9fd9 Iustin Pop
    # highest used tcp port check
438 48ce9fd9 Iustin Pop
    if keys:
439 a8efbb40 Iustin Pop
      if keys[-1] > data.cluster.highest_used_port:
440 48ce9fd9 Iustin Pop
        result.append("Highest used port mismatch, saved %s, computed %s" %
441 a8efbb40 Iustin Pop
                      (data.cluster.highest_used_port, keys[-1]))
442 a8efbb40 Iustin Pop
443 3a26773f Iustin Pop
    if not data.nodes[data.cluster.master_node].master_candidate:
444 3a26773f Iustin Pop
      result.append("Master node is not a master candidate")
445 3a26773f Iustin Pop
446 4a89c54a Iustin Pop
    # master candidate checks
447 e623dbe3 Guido Trotter
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats()
448 ec0292f1 Iustin Pop
    if mc_now < mc_max:
449 ec0292f1 Iustin Pop
      result.append("Not enough master candidates: actual %d, target %d" %
450 ec0292f1 Iustin Pop
                    (mc_now, mc_max))
451 48ce9fd9 Iustin Pop
452 5bf07049 Iustin Pop
    # node checks
453 81196341 Iustin Pop
    for node_name, node in data.nodes.items():
454 81196341 Iustin Pop
      if node.name != node_name:
455 81196341 Iustin Pop
        result.append("Node '%s' is indexed by wrong name '%s'" %
456 81196341 Iustin Pop
                      (node.name, node_name))
457 5bf07049 Iustin Pop
      if [node.master_candidate, node.drained, node.offline].count(True) > 1:
458 5bf07049 Iustin Pop
        result.append("Node %s state is invalid: master_candidate=%s,"
459 5bf07049 Iustin Pop
                      " drain=%s, offline=%s" %
460 3d889a7d Michael Hanselmann
                      (node.name, node.master_candidate, node.drained,
461 5bf07049 Iustin Pop
                       node.offline))
462 5bf07049 Iustin Pop
463 6520ba14 Guido Trotter
    # nodegroups checks
464 ace16501 Guido Trotter
    nodegroups_names = set()
465 6520ba14 Guido Trotter
    for nodegroup_uuid in data.nodegroups:
466 6520ba14 Guido Trotter
      nodegroup = data.nodegroups[nodegroup_uuid]
467 6520ba14 Guido Trotter
      if nodegroup.uuid != nodegroup_uuid:
468 6520ba14 Guido Trotter
        result.append("nodegroup '%s' (uuid: '%s') indexed by wrong uuid '%s'"
469 6520ba14 Guido Trotter
                      % (nodegroup.name, nodegroup.uuid, nodegroup_uuid))
470 485ba212 Guido Trotter
      if utils.UUID_RE.match(nodegroup.name.lower()):
471 485ba212 Guido Trotter
        result.append("nodegroup '%s' (uuid: '%s') has uuid-like name" %
472 485ba212 Guido Trotter
                      (nodegroup.name, nodegroup.uuid))
473 ace16501 Guido Trotter
      if nodegroup.name in nodegroups_names:
474 ace16501 Guido Trotter
        result.append("duplicate nodegroup name '%s'" % nodegroup.name)
475 ace16501 Guido Trotter
      else:
476 ace16501 Guido Trotter
        nodegroups_names.add(nodegroup.name)
477 6520ba14 Guido Trotter
478 4a89c54a Iustin Pop
    # drbd minors check
479 1122eb25 Iustin Pop
    _, duplicates = self._UnlockedComputeDRBDMap()
480 4a89c54a Iustin Pop
    for node, minor, instance_a, instance_b in duplicates:
481 4a89c54a Iustin Pop
      result.append("DRBD minor %d on node %s is assigned twice to instances"
482 4a89c54a Iustin Pop
                    " %s and %s" % (minor, node, instance_a, instance_b))
483 4a89c54a Iustin Pop
484 0ce8f948 Iustin Pop
    # IP checks
485 b8716596 Michael Hanselmann
    default_nicparams = data.cluster.nicparams[constants.PP_DEFAULT]
486 b8716596 Michael Hanselmann
    ips = {}
487 b8716596 Michael Hanselmann
488 b8716596 Michael Hanselmann
    def _AddIpAddress(ip, name):
489 b8716596 Michael Hanselmann
      ips.setdefault(ip, []).append(name)
490 b8716596 Michael Hanselmann
491 b8716596 Michael Hanselmann
    _AddIpAddress(data.cluster.master_ip, "cluster_ip")
492 0ce8f948 Iustin Pop
493 0ce8f948 Iustin Pop
    for node in data.nodes.values():
494 b8716596 Michael Hanselmann
      _AddIpAddress(node.primary_ip, "node:%s/primary" % node.name)
495 0ce8f948 Iustin Pop
      if node.secondary_ip != node.primary_ip:
496 b8716596 Michael Hanselmann
        _AddIpAddress(node.secondary_ip, "node:%s/secondary" % node.name)
497 b8716596 Michael Hanselmann
498 b8716596 Michael Hanselmann
    for instance in data.instances.values():
499 b8716596 Michael Hanselmann
      for idx, nic in enumerate(instance.nics):
500 b8716596 Michael Hanselmann
        if nic.ip is None:
501 b8716596 Michael Hanselmann
          continue
502 b8716596 Michael Hanselmann
503 b8716596 Michael Hanselmann
        nicparams = objects.FillDict(default_nicparams, nic.nicparams)
504 b8716596 Michael Hanselmann
        nic_mode = nicparams[constants.NIC_MODE]
505 b8716596 Michael Hanselmann
        nic_link = nicparams[constants.NIC_LINK]
506 b8716596 Michael Hanselmann
507 b8716596 Michael Hanselmann
        if nic_mode == constants.NIC_MODE_BRIDGED:
508 b8716596 Michael Hanselmann
          link = "bridge:%s" % nic_link
509 b8716596 Michael Hanselmann
        elif nic_mode == constants.NIC_MODE_ROUTED:
510 b8716596 Michael Hanselmann
          link = "route:%s" % nic_link
511 b8716596 Michael Hanselmann
        else:
512 b8716596 Michael Hanselmann
          raise errors.ProgrammerError("NIC mode '%s' not handled" % nic_mode)
513 b8716596 Michael Hanselmann
514 b8716596 Michael Hanselmann
        _AddIpAddress("%s/%s" % (link, nic.ip),
515 b8716596 Michael Hanselmann
                      "instance:%s/nic:%d" % (instance.name, idx))
516 0ce8f948 Iustin Pop
517 0ce8f948 Iustin Pop
    for ip, owners in ips.items():
518 0ce8f948 Iustin Pop
      if len(owners) > 1:
519 0ce8f948 Iustin Pop
        result.append("IP address %s is used by multiple owners: %s" %
520 1f864b60 Iustin Pop
                      (ip, utils.CommaJoin(owners)))
521 b8716596 Michael Hanselmann
522 a8083063 Iustin Pop
    return result
523 a8083063 Iustin Pop
524 4a89c54a Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
525 4a89c54a Iustin Pop
  def VerifyConfig(self):
526 4a89c54a Iustin Pop
    """Verify function.
527 4a89c54a Iustin Pop

528 4a89c54a Iustin Pop
    This is just a wrapper over L{_UnlockedVerifyConfig}.
529 4a89c54a Iustin Pop

530 4a89c54a Iustin Pop
    @rtype: list
531 4a89c54a Iustin Pop
    @return: a list of error messages; a non-empty list signifies
532 4a89c54a Iustin Pop
        configuration errors
533 4a89c54a Iustin Pop

534 4a89c54a Iustin Pop
    """
535 4a89c54a Iustin Pop
    return self._UnlockedVerifyConfig()
536 4a89c54a Iustin Pop
537 f78ede4e Guido Trotter
  def _UnlockedSetDiskID(self, disk, node_name):
538 a8083063 Iustin Pop
    """Convert the unique ID to the ID needed on the target nodes.
539 a8083063 Iustin Pop

540 a8083063 Iustin Pop
    This is used only for drbd, which needs ip/port configuration.
541 a8083063 Iustin Pop

542 a8083063 Iustin Pop
    The routine descends down and updates its children also, because
543 a8083063 Iustin Pop
    this helps when the only the top device is passed to the remote
544 a8083063 Iustin Pop
    node.
545 a8083063 Iustin Pop

546 f78ede4e Guido Trotter
    This function is for internal use, when the config lock is already held.
547 f78ede4e Guido Trotter

548 a8083063 Iustin Pop
    """
549 a8083063 Iustin Pop
    if disk.children:
550 a8083063 Iustin Pop
      for child in disk.children:
551 f78ede4e Guido Trotter
        self._UnlockedSetDiskID(child, node_name)
552 a8083063 Iustin Pop
553 a8083063 Iustin Pop
    if disk.logical_id is None and disk.physical_id is not None:
554 a8083063 Iustin Pop
      return
555 ffa1c0dc Iustin Pop
    if disk.dev_type == constants.LD_DRBD8:
556 f9518d38 Iustin Pop
      pnode, snode, port, pminor, sminor, secret = disk.logical_id
557 a8083063 Iustin Pop
      if node_name not in (pnode, snode):
558 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
559 3ecf6786 Iustin Pop
                                        node_name)
560 f78ede4e Guido Trotter
      pnode_info = self._UnlockedGetNodeInfo(pnode)
561 f78ede4e Guido Trotter
      snode_info = self._UnlockedGetNodeInfo(snode)
562 a8083063 Iustin Pop
      if pnode_info is None or snode_info is None:
563 a8083063 Iustin Pop
        raise errors.ConfigurationError("Can't find primary or secondary node"
564 a8083063 Iustin Pop
                                        " for %s" % str(disk))
565 ffa1c0dc Iustin Pop
      p_data = (pnode_info.secondary_ip, port)
566 ffa1c0dc Iustin Pop
      s_data = (snode_info.secondary_ip, port)
567 a8083063 Iustin Pop
      if pnode == node_name:
568 f9518d38 Iustin Pop
        disk.physical_id = p_data + s_data + (pminor, secret)
569 a8083063 Iustin Pop
      else: # it must be secondary, we tested above
570 f9518d38 Iustin Pop
        disk.physical_id = s_data + p_data + (sminor, secret)
571 a8083063 Iustin Pop
    else:
572 a8083063 Iustin Pop
      disk.physical_id = disk.logical_id
573 a8083063 Iustin Pop
    return
574 a8083063 Iustin Pop
575 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
576 f78ede4e Guido Trotter
  def SetDiskID(self, disk, node_name):
577 f78ede4e Guido Trotter
    """Convert the unique ID to the ID needed on the target nodes.
578 f78ede4e Guido Trotter

579 f78ede4e Guido Trotter
    This is used only for drbd, which needs ip/port configuration.
580 f78ede4e Guido Trotter

581 f78ede4e Guido Trotter
    The routine descends down and updates its children also, because
582 f78ede4e Guido Trotter
    this helps when the only the top device is passed to the remote
583 f78ede4e Guido Trotter
    node.
584 f78ede4e Guido Trotter

585 f78ede4e Guido Trotter
    """
586 f78ede4e Guido Trotter
    return self._UnlockedSetDiskID(disk, node_name)
587 f78ede4e Guido Trotter
588 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
589 b2fddf63 Iustin Pop
  def AddTcpUdpPort(self, port):
590 b2fddf63 Iustin Pop
    """Adds a new port to the available port pool.
591 b2fddf63 Iustin Pop

592 b2fddf63 Iustin Pop
    """
593 264bb3c5 Michael Hanselmann
    if not isinstance(port, int):
594 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed for port")
595 264bb3c5 Michael Hanselmann
596 b2fddf63 Iustin Pop
    self._config_data.cluster.tcpudp_port_pool.add(port)
597 264bb3c5 Michael Hanselmann
    self._WriteConfig()
598 264bb3c5 Michael Hanselmann
599 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
600 b2fddf63 Iustin Pop
  def GetPortList(self):
601 264bb3c5 Michael Hanselmann
    """Returns a copy of the current port list.
602 264bb3c5 Michael Hanselmann

603 264bb3c5 Michael Hanselmann
    """
604 b2fddf63 Iustin Pop
    return self._config_data.cluster.tcpudp_port_pool.copy()
605 264bb3c5 Michael Hanselmann
606 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
607 a8083063 Iustin Pop
  def AllocatePort(self):
608 a8083063 Iustin Pop
    """Allocate a port.
609 a8083063 Iustin Pop

610 b2fddf63 Iustin Pop
    The port will be taken from the available port pool or from the
611 b2fddf63 Iustin Pop
    default port range (and in this case we increase
612 b2fddf63 Iustin Pop
    highest_used_port).
613 a8083063 Iustin Pop

614 a8083063 Iustin Pop
    """
615 264bb3c5 Michael Hanselmann
    # If there are TCP/IP ports configured, we use them first.
616 b2fddf63 Iustin Pop
    if self._config_data.cluster.tcpudp_port_pool:
617 b2fddf63 Iustin Pop
      port = self._config_data.cluster.tcpudp_port_pool.pop()
618 264bb3c5 Michael Hanselmann
    else:
619 264bb3c5 Michael Hanselmann
      port = self._config_data.cluster.highest_used_port + 1
620 264bb3c5 Michael Hanselmann
      if port >= constants.LAST_DRBD_PORT:
621 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("The highest used port is greater"
622 3ecf6786 Iustin Pop
                                        " than %s. Aborting." %
623 3ecf6786 Iustin Pop
                                        constants.LAST_DRBD_PORT)
624 264bb3c5 Michael Hanselmann
      self._config_data.cluster.highest_used_port = port
625 a8083063 Iustin Pop
626 a8083063 Iustin Pop
    self._WriteConfig()
627 a8083063 Iustin Pop
    return port
628 a8083063 Iustin Pop
629 6d2e83d5 Iustin Pop
  def _UnlockedComputeDRBDMap(self):
630 a81c53c9 Iustin Pop
    """Compute the used DRBD minor/nodes.
631 a81c53c9 Iustin Pop

632 4a89c54a Iustin Pop
    @rtype: (dict, list)
633 c41eea6e Iustin Pop
    @return: dictionary of node_name: dict of minor: instance_name;
634 c41eea6e Iustin Pop
        the returned dict will have all the nodes in it (even if with
635 4a89c54a Iustin Pop
        an empty list), and a list of duplicates; if the duplicates
636 4a89c54a Iustin Pop
        list is not empty, the configuration is corrupted and its caller
637 4a89c54a Iustin Pop
        should raise an exception
638 a81c53c9 Iustin Pop

639 a81c53c9 Iustin Pop
    """
640 a81c53c9 Iustin Pop
    def _AppendUsedPorts(instance_name, disk, used):
641 4a89c54a Iustin Pop
      duplicates = []
642 f9518d38 Iustin Pop
      if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
643 7c4d6c7b Michael Hanselmann
        node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
644 7c4d6c7b Michael Hanselmann
        for node, port in ((node_a, minor_a), (node_b, minor_b)):
645 4a89c54a Iustin Pop
          assert node in used, ("Node '%s' of instance '%s' not found"
646 4a89c54a Iustin Pop
                                " in node list" % (node, instance_name))
647 a81c53c9 Iustin Pop
          if port in used[node]:
648 4a89c54a Iustin Pop
            duplicates.append((node, port, instance_name, used[node][port]))
649 4a89c54a Iustin Pop
          else:
650 4a89c54a Iustin Pop
            used[node][port] = instance_name
651 a81c53c9 Iustin Pop
      if disk.children:
652 a81c53c9 Iustin Pop
        for child in disk.children:
653 4a89c54a Iustin Pop
          duplicates.extend(_AppendUsedPorts(instance_name, child, used))
654 4a89c54a Iustin Pop
      return duplicates
655 a81c53c9 Iustin Pop
656 4a89c54a Iustin Pop
    duplicates = []
657 a81c53c9 Iustin Pop
    my_dict = dict((node, {}) for node in self._config_data.nodes)
658 79b26a7a Iustin Pop
    for instance in self._config_data.instances.itervalues():
659 79b26a7a Iustin Pop
      for disk in instance.disks:
660 79b26a7a Iustin Pop
        duplicates.extend(_AppendUsedPorts(instance.name, disk, my_dict))
661 a81c53c9 Iustin Pop
    for (node, minor), instance in self._temporary_drbds.iteritems():
662 79b26a7a Iustin Pop
      if minor in my_dict[node] and my_dict[node][minor] != instance:
663 4a89c54a Iustin Pop
        duplicates.append((node, minor, instance, my_dict[node][minor]))
664 4a89c54a Iustin Pop
      else:
665 4a89c54a Iustin Pop
        my_dict[node][minor] = instance
666 4a89c54a Iustin Pop
    return my_dict, duplicates
667 a81c53c9 Iustin Pop
668 a81c53c9 Iustin Pop
  @locking.ssynchronized(_config_lock)
669 6d2e83d5 Iustin Pop
  def ComputeDRBDMap(self):
670 6d2e83d5 Iustin Pop
    """Compute the used DRBD minor/nodes.
671 6d2e83d5 Iustin Pop

672 6d2e83d5 Iustin Pop
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
673 6d2e83d5 Iustin Pop

674 6d2e83d5 Iustin Pop
    @return: dictionary of node_name: dict of minor: instance_name;
675 6d2e83d5 Iustin Pop
        the returned dict will have all the nodes in it (even if with
676 6d2e83d5 Iustin Pop
        an empty list).
677 6d2e83d5 Iustin Pop

678 6d2e83d5 Iustin Pop
    """
679 4a89c54a Iustin Pop
    d_map, duplicates = self._UnlockedComputeDRBDMap()
680 4a89c54a Iustin Pop
    if duplicates:
681 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
682 4a89c54a Iustin Pop
                                      str(duplicates))
683 4a89c54a Iustin Pop
    return d_map
684 6d2e83d5 Iustin Pop
685 6d2e83d5 Iustin Pop
  @locking.ssynchronized(_config_lock)
686 a81c53c9 Iustin Pop
  def AllocateDRBDMinor(self, nodes, instance):
687 a81c53c9 Iustin Pop
    """Allocate a drbd minor.
688 a81c53c9 Iustin Pop

689 a81c53c9 Iustin Pop
    The free minor will be automatically computed from the existing
690 a81c53c9 Iustin Pop
    devices. A node can be given multiple times in order to allocate
691 a81c53c9 Iustin Pop
    multiple minors. The result is the list of minors, in the same
692 a81c53c9 Iustin Pop
    order as the passed nodes.
693 a81c53c9 Iustin Pop

694 32388e6d Iustin Pop
    @type instance: string
695 32388e6d Iustin Pop
    @param instance: the instance for which we allocate minors
696 32388e6d Iustin Pop

697 a81c53c9 Iustin Pop
    """
698 32388e6d Iustin Pop
    assert isinstance(instance, basestring), \
699 4a89c54a Iustin Pop
           "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
700 32388e6d Iustin Pop
701 4a89c54a Iustin Pop
    d_map, duplicates = self._UnlockedComputeDRBDMap()
702 4a89c54a Iustin Pop
    if duplicates:
703 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
704 4a89c54a Iustin Pop
                                      str(duplicates))
705 a81c53c9 Iustin Pop
    result = []
706 a81c53c9 Iustin Pop
    for nname in nodes:
707 a81c53c9 Iustin Pop
      ndata = d_map[nname]
708 a81c53c9 Iustin Pop
      if not ndata:
709 a81c53c9 Iustin Pop
        # no minors used, we can start at 0
710 a81c53c9 Iustin Pop
        result.append(0)
711 a81c53c9 Iustin Pop
        ndata[0] = instance
712 d48663e4 Iustin Pop
        self._temporary_drbds[(nname, 0)] = instance
713 a81c53c9 Iustin Pop
        continue
714 a81c53c9 Iustin Pop
      keys = ndata.keys()
715 a81c53c9 Iustin Pop
      keys.sort()
716 a81c53c9 Iustin Pop
      ffree = utils.FirstFree(keys)
717 a81c53c9 Iustin Pop
      if ffree is None:
718 a81c53c9 Iustin Pop
        # return the next minor
719 a81c53c9 Iustin Pop
        # TODO: implement high-limit check
720 a81c53c9 Iustin Pop
        minor = keys[-1] + 1
721 a81c53c9 Iustin Pop
      else:
722 a81c53c9 Iustin Pop
        minor = ffree
723 4a89c54a Iustin Pop
      # double-check minor against current instances
724 4a89c54a Iustin Pop
      assert minor not in d_map[nname], \
725 4a89c54a Iustin Pop
             ("Attempt to reuse allocated DRBD minor %d on node %s,"
726 4a89c54a Iustin Pop
              " already allocated to instance %s" %
727 4a89c54a Iustin Pop
              (minor, nname, d_map[nname][minor]))
728 a81c53c9 Iustin Pop
      ndata[minor] = instance
729 4a89c54a Iustin Pop
      # double-check minor against reservation
730 4a89c54a Iustin Pop
      r_key = (nname, minor)
731 4a89c54a Iustin Pop
      assert r_key not in self._temporary_drbds, \
732 4a89c54a Iustin Pop
             ("Attempt to reuse reserved DRBD minor %d on node %s,"
733 4a89c54a Iustin Pop
              " reserved for instance %s" %
734 4a89c54a Iustin Pop
              (minor, nname, self._temporary_drbds[r_key]))
735 4a89c54a Iustin Pop
      self._temporary_drbds[r_key] = instance
736 4a89c54a Iustin Pop
      result.append(minor)
737 a81c53c9 Iustin Pop
    logging.debug("Request to allocate drbd minors, input: %s, returning %s",
738 a81c53c9 Iustin Pop
                  nodes, result)
739 a81c53c9 Iustin Pop
    return result
740 a81c53c9 Iustin Pop
741 61cf6b5e Iustin Pop
  def _UnlockedReleaseDRBDMinors(self, instance):
742 a81c53c9 Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
743 a81c53c9 Iustin Pop

744 a81c53c9 Iustin Pop
    @type instance: string
745 a81c53c9 Iustin Pop
    @param instance: the instance for which temporary minors should be
746 a81c53c9 Iustin Pop
                     released
747 a81c53c9 Iustin Pop

748 a81c53c9 Iustin Pop
    """
749 32388e6d Iustin Pop
    assert isinstance(instance, basestring), \
750 32388e6d Iustin Pop
           "Invalid argument passed to ReleaseDRBDMinors"
751 a81c53c9 Iustin Pop
    for key, name in self._temporary_drbds.items():
752 a81c53c9 Iustin Pop
      if name == instance:
753 a81c53c9 Iustin Pop
        del self._temporary_drbds[key]
754 a81c53c9 Iustin Pop
755 61cf6b5e Iustin Pop
  @locking.ssynchronized(_config_lock)
756 61cf6b5e Iustin Pop
  def ReleaseDRBDMinors(self, instance):
757 61cf6b5e Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
758 61cf6b5e Iustin Pop

759 61cf6b5e Iustin Pop
    This should be called on the error paths, on the success paths
760 61cf6b5e Iustin Pop
    it's automatically called by the ConfigWriter add and update
761 61cf6b5e Iustin Pop
    functions.
762 61cf6b5e Iustin Pop

763 61cf6b5e Iustin Pop
    This function is just a wrapper over L{_UnlockedReleaseDRBDMinors}.
764 61cf6b5e Iustin Pop

765 61cf6b5e Iustin Pop
    @type instance: string
766 61cf6b5e Iustin Pop
    @param instance: the instance for which temporary minors should be
767 61cf6b5e Iustin Pop
                     released
768 61cf6b5e Iustin Pop

769 61cf6b5e Iustin Pop
    """
770 61cf6b5e Iustin Pop
    self._UnlockedReleaseDRBDMinors(instance)
771 61cf6b5e Iustin Pop
772 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
773 4a8b186a Michael Hanselmann
  def GetConfigVersion(self):
774 4a8b186a Michael Hanselmann
    """Get the configuration version.
775 4a8b186a Michael Hanselmann

776 4a8b186a Michael Hanselmann
    @return: Config version
777 4a8b186a Michael Hanselmann

778 4a8b186a Michael Hanselmann
    """
779 4a8b186a Michael Hanselmann
    return self._config_data.version
780 4a8b186a Michael Hanselmann
781 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
782 4a8b186a Michael Hanselmann
  def GetClusterName(self):
783 4a8b186a Michael Hanselmann
    """Get cluster name.
784 4a8b186a Michael Hanselmann

785 4a8b186a Michael Hanselmann
    @return: Cluster name
786 4a8b186a Michael Hanselmann

787 4a8b186a Michael Hanselmann
    """
788 4a8b186a Michael Hanselmann
    return self._config_data.cluster.cluster_name
789 4a8b186a Michael Hanselmann
790 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
791 4a8b186a Michael Hanselmann
  def GetMasterNode(self):
792 4a8b186a Michael Hanselmann
    """Get the hostname of the master node for this cluster.
793 4a8b186a Michael Hanselmann

794 4a8b186a Michael Hanselmann
    @return: Master hostname
795 4a8b186a Michael Hanselmann

796 4a8b186a Michael Hanselmann
    """
797 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_node
798 4a8b186a Michael Hanselmann
799 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
800 4a8b186a Michael Hanselmann
  def GetMasterIP(self):
801 4a8b186a Michael Hanselmann
    """Get the IP of the master node for this cluster.
802 4a8b186a Michael Hanselmann

803 4a8b186a Michael Hanselmann
    @return: Master IP
804 4a8b186a Michael Hanselmann

805 4a8b186a Michael Hanselmann
    """
806 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_ip
807 4a8b186a Michael Hanselmann
808 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
809 4a8b186a Michael Hanselmann
  def GetMasterNetdev(self):
810 4a8b186a Michael Hanselmann
    """Get the master network device for this cluster.
811 4a8b186a Michael Hanselmann

812 4a8b186a Michael Hanselmann
    """
813 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_netdev
814 4a8b186a Michael Hanselmann
815 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
816 4a8b186a Michael Hanselmann
  def GetFileStorageDir(self):
817 4a8b186a Michael Hanselmann
    """Get the file storage dir for this cluster.
818 4a8b186a Michael Hanselmann

819 4a8b186a Michael Hanselmann
    """
820 4a8b186a Michael Hanselmann
    return self._config_data.cluster.file_storage_dir
821 4a8b186a Michael Hanselmann
822 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
823 4a8b186a Michael Hanselmann
  def GetHypervisorType(self):
824 4a8b186a Michael Hanselmann
    """Get the hypervisor type for this cluster.
825 4a8b186a Michael Hanselmann

826 4a8b186a Michael Hanselmann
    """
827 066f465d Guido Trotter
    return self._config_data.cluster.enabled_hypervisors[0]
828 4a8b186a Michael Hanselmann
829 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
830 a8083063 Iustin Pop
  def GetHostKey(self):
831 a8083063 Iustin Pop
    """Return the rsa hostkey from the config.
832 a8083063 Iustin Pop

833 c41eea6e Iustin Pop
    @rtype: string
834 c41eea6e Iustin Pop
    @return: the rsa hostkey
835 a8083063 Iustin Pop

836 a8083063 Iustin Pop
    """
837 a8083063 Iustin Pop
    return self._config_data.cluster.rsahostkeypub
838 a8083063 Iustin Pop
839 bf4af505 Apollon Oikonomopoulos
  @locking.ssynchronized(_config_lock, shared=1)
840 bf4af505 Apollon Oikonomopoulos
  def GetDefaultIAllocator(self):
841 bf4af505 Apollon Oikonomopoulos
    """Get the default instance allocator for this cluster.
842 bf4af505 Apollon Oikonomopoulos

843 bf4af505 Apollon Oikonomopoulos
    """
844 bf4af505 Apollon Oikonomopoulos
    return self._config_data.cluster.default_iallocator
845 bf4af505 Apollon Oikonomopoulos
846 868a98ca Manuel Franceschini
  @locking.ssynchronized(_config_lock, shared=1)
847 868a98ca Manuel Franceschini
  def GetPrimaryIPFamily(self):
848 868a98ca Manuel Franceschini
    """Get cluster primary ip family.
849 868a98ca Manuel Franceschini

850 868a98ca Manuel Franceschini
    @return: primary ip family
851 868a98ca Manuel Franceschini

852 868a98ca Manuel Franceschini
    """
853 868a98ca Manuel Franceschini
    return self._config_data.cluster.primary_ip_family
854 868a98ca Manuel Franceschini
855 eaa98a04 Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
856 eaa98a04 Guido Trotter
  def LookupNodeGroup(self, target):
857 412b3531 Guido Trotter
    """Lookup a node group's UUID.
858 eaa98a04 Guido Trotter

859 eaa98a04 Guido Trotter
    @type target: string or None
860 412b3531 Guido Trotter
    @param target: group name or UUID or None to look for the default
861 eaa98a04 Guido Trotter
    @rtype: string
862 412b3531 Guido Trotter
    @return: nodegroup UUID
863 eaa98a04 Guido Trotter
    @raises errors.OpPrereqError: when the target group cannot be found
864 eaa98a04 Guido Trotter

865 eaa98a04 Guido Trotter
    """
866 eaa98a04 Guido Trotter
    if target is None:
867 eaa98a04 Guido Trotter
      if len(self._config_data.nodegroups) != 1:
868 eaa98a04 Guido Trotter
        raise errors.OpPrereqError("More than one nodegroup exists. Target"
869 eaa98a04 Guido Trotter
                                   " group must be specified explicitely.")
870 eaa98a04 Guido Trotter
      else:
871 eaa98a04 Guido Trotter
        return self._config_data.nodegroups.keys()[0]
872 eaa98a04 Guido Trotter
    if target in self._config_data.nodegroups:
873 eaa98a04 Guido Trotter
      return target
874 eaa98a04 Guido Trotter
    for nodegroup in self._config_data.nodegroups.values():
875 eaa98a04 Guido Trotter
      if nodegroup.name == target:
876 eaa98a04 Guido Trotter
        return nodegroup.uuid
877 eaa98a04 Guido Trotter
    raise errors.OpPrereqError("Nodegroup '%s' not found", target)
878 eaa98a04 Guido Trotter
879 622444e5 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
880 648e4196 Guido Trotter
  def GetNodeGroup(self, uuid):
881 648e4196 Guido Trotter
    """Lookup a node group.
882 648e4196 Guido Trotter

883 648e4196 Guido Trotter
    @type uuid: string
884 648e4196 Guido Trotter
    @param uuid: group UUID
885 648e4196 Guido Trotter
    @rtype: L{objects.NodeGroup} or None
886 648e4196 Guido Trotter
    @return: nodegroup object, or None if not found
887 648e4196 Guido Trotter

888 648e4196 Guido Trotter
    """
889 648e4196 Guido Trotter
    if uuid not in self._config_data.nodegroups:
890 648e4196 Guido Trotter
      return None
891 648e4196 Guido Trotter
892 648e4196 Guido Trotter
    return self._config_data.nodegroups[uuid]
893 648e4196 Guido Trotter
894 648e4196 Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
895 622444e5 Iustin Pop
  def GetAllNodeGroupsInfo(self):
896 622444e5 Iustin Pop
    """Get the configuration of all node groups.
897 622444e5 Iustin Pop

898 622444e5 Iustin Pop
    """
899 622444e5 Iustin Pop
    return dict(self._config_data.nodegroups)
900 622444e5 Iustin Pop
901 1ac6f2ad Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
902 1ac6f2ad Guido Trotter
  def GetNodeGroupList(self):
903 1ac6f2ad Guido Trotter
    """Get a list of node groups.
904 1ac6f2ad Guido Trotter

905 1ac6f2ad Guido Trotter
    """
906 1ac6f2ad Guido Trotter
    return self._config_data.nodegroups.keys()
907 1ac6f2ad Guido Trotter
908 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
909 0debfb35 Guido Trotter
  def AddInstance(self, instance, ec_id):
910 a8083063 Iustin Pop
    """Add an instance to the config.
911 a8083063 Iustin Pop

912 a8083063 Iustin Pop
    This should be used after creating a new instance.
913 a8083063 Iustin Pop

914 c41eea6e Iustin Pop
    @type instance: L{objects.Instance}
915 c41eea6e Iustin Pop
    @param instance: the instance object
916 c41eea6e Iustin Pop

917 a8083063 Iustin Pop
    """
918 a8083063 Iustin Pop
    if not isinstance(instance, objects.Instance):
919 a8083063 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
920 a8083063 Iustin Pop
921 e00fb268 Iustin Pop
    if instance.disk_template != constants.DT_DISKLESS:
922 e00fb268 Iustin Pop
      all_lvs = instance.MapLVsByNode()
923 74a48621 Iustin Pop
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
924 923b1523 Iustin Pop
925 e4640214 Guido Trotter
    all_macs = self._AllMACs()
926 e4640214 Guido Trotter
    for nic in instance.nics:
927 e4640214 Guido Trotter
      if nic.mac in all_macs:
928 e4640214 Guido Trotter
        raise errors.ConfigurationError("Cannot add instance %s:"
929 430b923c Iustin Pop
                                        " MAC address '%s' already in use." %
930 430b923c Iustin Pop
                                        (instance.name, nic.mac))
931 430b923c Iustin Pop
932 0debfb35 Guido Trotter
    self._EnsureUUID(instance, ec_id)
933 e4640214 Guido Trotter
934 b989e85d Iustin Pop
    instance.serial_no = 1
935 d693c864 Iustin Pop
    instance.ctime = instance.mtime = time.time()
936 a8083063 Iustin Pop
    self._config_data.instances[instance.name] = instance
937 81a49123 Iustin Pop
    self._config_data.cluster.serial_no += 1
938 61cf6b5e Iustin Pop
    self._UnlockedReleaseDRBDMinors(instance.name)
939 a8083063 Iustin Pop
    self._WriteConfig()
940 a8083063 Iustin Pop
941 0debfb35 Guido Trotter
  def _EnsureUUID(self, item, ec_id):
942 430b923c Iustin Pop
    """Ensures a given object has a valid UUID.
943 430b923c Iustin Pop

944 430b923c Iustin Pop
    @param item: the instance or node to be checked
945 0debfb35 Guido Trotter
    @param ec_id: the execution context id for the uuid reservation
946 430b923c Iustin Pop

947 430b923c Iustin Pop
    """
948 430b923c Iustin Pop
    if not item.uuid:
949 4fae38c5 Guido Trotter
      item.uuid = self._GenerateUniqueID(ec_id)
950 be0fc05d Iustin Pop
    elif item.uuid in self._AllIDs(include_temporary=True):
951 be0fc05d Iustin Pop
      raise errors.ConfigurationError("Cannot add '%s': UUID %s already"
952 be0fc05d Iustin Pop
                                      " in use" % (item.name, item.uuid))
953 430b923c Iustin Pop
954 6a408fb2 Iustin Pop
  def _SetInstanceStatus(self, instance_name, status):
955 6a408fb2 Iustin Pop
    """Set the instance's status to a given value.
956 a8083063 Iustin Pop

957 a8083063 Iustin Pop
    """
958 0d68c45d Iustin Pop
    assert isinstance(status, bool), \
959 0d68c45d Iustin Pop
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
960 a8083063 Iustin Pop
961 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
962 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" %
963 3ecf6786 Iustin Pop
                                      instance_name)
964 a8083063 Iustin Pop
    instance = self._config_data.instances[instance_name]
965 0d68c45d Iustin Pop
    if instance.admin_up != status:
966 0d68c45d Iustin Pop
      instance.admin_up = status
967 b989e85d Iustin Pop
      instance.serial_no += 1
968 d693c864 Iustin Pop
      instance.mtime = time.time()
969 455a3445 Iustin Pop
      self._WriteConfig()
970 a8083063 Iustin Pop
971 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
972 6a408fb2 Iustin Pop
  def MarkInstanceUp(self, instance_name):
973 6a408fb2 Iustin Pop
    """Mark the instance status to up in the config.
974 6a408fb2 Iustin Pop

975 6a408fb2 Iustin Pop
    """
976 0d68c45d Iustin Pop
    self._SetInstanceStatus(instance_name, True)
977 6a408fb2 Iustin Pop
978 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
979 a8083063 Iustin Pop
  def RemoveInstance(self, instance_name):
980 a8083063 Iustin Pop
    """Remove the instance from the configuration.
981 a8083063 Iustin Pop

982 a8083063 Iustin Pop
    """
983 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
984 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
985 a8083063 Iustin Pop
    del self._config_data.instances[instance_name]
986 81a49123 Iustin Pop
    self._config_data.cluster.serial_no += 1
987 a8083063 Iustin Pop
    self._WriteConfig()
988 a8083063 Iustin Pop
989 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
990 fc95f88f Iustin Pop
  def RenameInstance(self, old_name, new_name):
991 fc95f88f Iustin Pop
    """Rename an instance.
992 fc95f88f Iustin Pop

993 fc95f88f Iustin Pop
    This needs to be done in ConfigWriter and not by RemoveInstance
994 fc95f88f Iustin Pop
    combined with AddInstance as only we can guarantee an atomic
995 fc95f88f Iustin Pop
    rename.
996 fc95f88f Iustin Pop

997 fc95f88f Iustin Pop
    """
998 fc95f88f Iustin Pop
    if old_name not in self._config_data.instances:
999 fc95f88f Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
1000 fc95f88f Iustin Pop
    inst = self._config_data.instances[old_name]
1001 fc95f88f Iustin Pop
    del self._config_data.instances[old_name]
1002 fc95f88f Iustin Pop
    inst.name = new_name
1003 b23c4333 Manuel Franceschini
1004 b23c4333 Manuel Franceschini
    for disk in inst.disks:
1005 b23c4333 Manuel Franceschini
      if disk.dev_type == constants.LD_FILE:
1006 b23c4333 Manuel Franceschini
        # rename the file paths in logical and physical id
1007 b23c4333 Manuel Franceschini
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
1008 b23c4333 Manuel Franceschini
        disk.physical_id = disk.logical_id = (disk.logical_id[0],
1009 c4feafe8 Iustin Pop
                                              utils.PathJoin(file_storage_dir,
1010 c4feafe8 Iustin Pop
                                                             inst.name,
1011 c4feafe8 Iustin Pop
                                                             disk.iv_name))
1012 b23c4333 Manuel Franceschini
1013 1fc34c48 Michael Hanselmann
    # Force update of ssconf files
1014 1fc34c48 Michael Hanselmann
    self._config_data.cluster.serial_no += 1
1015 1fc34c48 Michael Hanselmann
1016 fc95f88f Iustin Pop
    self._config_data.instances[inst.name] = inst
1017 fc95f88f Iustin Pop
    self._WriteConfig()
1018 fc95f88f Iustin Pop
1019 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1020 a8083063 Iustin Pop
  def MarkInstanceDown(self, instance_name):
1021 a8083063 Iustin Pop
    """Mark the status of an instance to down in the configuration.
1022 a8083063 Iustin Pop

1023 a8083063 Iustin Pop
    """
1024 0d68c45d Iustin Pop
    self._SetInstanceStatus(instance_name, False)
1025 a8083063 Iustin Pop
1026 94bbfece Iustin Pop
  def _UnlockedGetInstanceList(self):
1027 94bbfece Iustin Pop
    """Get the list of instances.
1028 94bbfece Iustin Pop

1029 94bbfece Iustin Pop
    This function is for internal use, when the config lock is already held.
1030 94bbfece Iustin Pop

1031 94bbfece Iustin Pop
    """
1032 94bbfece Iustin Pop
    return self._config_data.instances.keys()
1033 94bbfece Iustin Pop
1034 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1035 a8083063 Iustin Pop
  def GetInstanceList(self):
1036 a8083063 Iustin Pop
    """Get the list of instances.
1037 a8083063 Iustin Pop

1038 c41eea6e Iustin Pop
    @return: array of instances, ex. ['instance2.example.com',
1039 c41eea6e Iustin Pop
        'instance1.example.com']
1040 a8083063 Iustin Pop

1041 a8083063 Iustin Pop
    """
1042 94bbfece Iustin Pop
    return self._UnlockedGetInstanceList()
1043 a8083063 Iustin Pop
1044 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1045 a8083063 Iustin Pop
  def ExpandInstanceName(self, short_name):
1046 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
1047 a8083063 Iustin Pop

1048 a8083063 Iustin Pop
    """
1049 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
1050 bcdf16d7 Guido Trotter
                                    self._config_data.instances.keys(),
1051 bcdf16d7 Guido Trotter
                                    case_sensitive=False)
1052 a8083063 Iustin Pop
1053 94bbfece Iustin Pop
  def _UnlockedGetInstanceInfo(self, instance_name):
1054 5bbd3f7f Michael Hanselmann
    """Returns information about an instance.
1055 94bbfece Iustin Pop

1056 94bbfece Iustin Pop
    This function is for internal use, when the config lock is already held.
1057 94bbfece Iustin Pop

1058 94bbfece Iustin Pop
    """
1059 94bbfece Iustin Pop
    if instance_name not in self._config_data.instances:
1060 94bbfece Iustin Pop
      return None
1061 94bbfece Iustin Pop
1062 94bbfece Iustin Pop
    return self._config_data.instances[instance_name]
1063 94bbfece Iustin Pop
1064 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1065 a8083063 Iustin Pop
  def GetInstanceInfo(self, instance_name):
1066 5bbd3f7f Michael Hanselmann
    """Returns information about an instance.
1067 a8083063 Iustin Pop

1068 5bbd3f7f Michael Hanselmann
    It takes the information from the configuration file. Other information of
1069 a8083063 Iustin Pop
    an instance are taken from the live systems.
1070 a8083063 Iustin Pop

1071 c41eea6e Iustin Pop
    @param instance_name: name of the instance, e.g.
1072 c41eea6e Iustin Pop
        I{instance1.example.com}
1073 a8083063 Iustin Pop

1074 c41eea6e Iustin Pop
    @rtype: L{objects.Instance}
1075 c41eea6e Iustin Pop
    @return: the instance object
1076 a8083063 Iustin Pop

1077 a8083063 Iustin Pop
    """
1078 94bbfece Iustin Pop
    return self._UnlockedGetInstanceInfo(instance_name)
1079 a8083063 Iustin Pop
1080 0b2de758 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
1081 0b2de758 Iustin Pop
  def GetAllInstancesInfo(self):
1082 0b2de758 Iustin Pop
    """Get the configuration of all instances.
1083 0b2de758 Iustin Pop

1084 0b2de758 Iustin Pop
    @rtype: dict
1085 5fcc718f Iustin Pop
    @return: dict of (instance, instance_info), where instance_info is what
1086 0b2de758 Iustin Pop
              would GetInstanceInfo return for the node
1087 0b2de758 Iustin Pop

1088 0b2de758 Iustin Pop
    """
1089 64d3bd52 Guido Trotter
    my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
1090 64d3bd52 Guido Trotter
                    for instance in self._UnlockedGetInstanceList()])
1091 0b2de758 Iustin Pop
    return my_dict
1092 0b2de758 Iustin Pop
1093 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1094 0debfb35 Guido Trotter
  def AddNode(self, node, ec_id):
1095 a8083063 Iustin Pop
    """Add a node to the configuration.
1096 a8083063 Iustin Pop

1097 c41eea6e Iustin Pop
    @type node: L{objects.Node}
1098 c41eea6e Iustin Pop
    @param node: a Node instance
1099 a8083063 Iustin Pop

1100 a8083063 Iustin Pop
    """
1101 099c52ad Iustin Pop
    logging.info("Adding node %s to configuration", node.name)
1102 d8470559 Michael Hanselmann
1103 0debfb35 Guido Trotter
    self._EnsureUUID(node, ec_id)
1104 430b923c Iustin Pop
1105 b989e85d Iustin Pop
    node.serial_no = 1
1106 d693c864 Iustin Pop
    node.ctime = node.mtime = time.time()
1107 f936c153 Iustin Pop
    self._UnlockedAddNodeToGroup(node.name, node.group)
1108 a8083063 Iustin Pop
    self._config_data.nodes[node.name] = node
1109 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
1110 a8083063 Iustin Pop
    self._WriteConfig()
1111 a8083063 Iustin Pop
1112 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1113 a8083063 Iustin Pop
  def RemoveNode(self, node_name):
1114 a8083063 Iustin Pop
    """Remove a node from the configuration.
1115 a8083063 Iustin Pop

1116 a8083063 Iustin Pop
    """
1117 099c52ad Iustin Pop
    logging.info("Removing node %s from configuration", node_name)
1118 d8470559 Michael Hanselmann
1119 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
1120 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
1121 a8083063 Iustin Pop
1122 190e3cb6 Guido Trotter
    self._UnlockedRemoveNodeFromGroup(self._config_data.nodes[node_name])
1123 a8083063 Iustin Pop
    del self._config_data.nodes[node_name]
1124 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
1125 a8083063 Iustin Pop
    self._WriteConfig()
1126 a8083063 Iustin Pop
1127 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1128 a8083063 Iustin Pop
  def ExpandNodeName(self, short_name):
1129 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
1130 a8083063 Iustin Pop

1131 a8083063 Iustin Pop
    """
1132 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
1133 bcdf16d7 Guido Trotter
                                    self._config_data.nodes.keys(),
1134 bcdf16d7 Guido Trotter
                                    case_sensitive=False)
1135 a8083063 Iustin Pop
1136 f78ede4e Guido Trotter
  def _UnlockedGetNodeInfo(self, node_name):
1137 a8083063 Iustin Pop
    """Get the configuration of a node, as stored in the config.
1138 a8083063 Iustin Pop

1139 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
1140 c41eea6e Iustin Pop
    held.
1141 f78ede4e Guido Trotter

1142 c41eea6e Iustin Pop
    @param node_name: the node name, e.g. I{node1.example.com}
1143 a8083063 Iustin Pop

1144 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
1145 c41eea6e Iustin Pop
    @return: the node object
1146 a8083063 Iustin Pop

1147 a8083063 Iustin Pop
    """
1148 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
1149 a8083063 Iustin Pop
      return None
1150 a8083063 Iustin Pop
1151 a8083063 Iustin Pop
    return self._config_data.nodes[node_name]
1152 a8083063 Iustin Pop
1153 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1154 f78ede4e Guido Trotter
  def GetNodeInfo(self, node_name):
1155 f78ede4e Guido Trotter
    """Get the configuration of a node, as stored in the config.
1156 f78ede4e Guido Trotter

1157 c41eea6e Iustin Pop
    This is just a locked wrapper over L{_UnlockedGetNodeInfo}.
1158 f78ede4e Guido Trotter

1159 c41eea6e Iustin Pop
    @param node_name: the node name, e.g. I{node1.example.com}
1160 c41eea6e Iustin Pop

1161 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
1162 c41eea6e Iustin Pop
    @return: the node object
1163 f78ede4e Guido Trotter

1164 f78ede4e Guido Trotter
    """
1165 f78ede4e Guido Trotter
    return self._UnlockedGetNodeInfo(node_name)
1166 f78ede4e Guido Trotter
1167 8bf9e9a5 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
1168 8bf9e9a5 Iustin Pop
  def GetNodeInstances(self, node_name):
1169 8bf9e9a5 Iustin Pop
    """Get the instances of a node, as stored in the config.
1170 8bf9e9a5 Iustin Pop

1171 8bf9e9a5 Iustin Pop
    @param node_name: the node name, e.g. I{node1.example.com}
1172 8bf9e9a5 Iustin Pop

1173 8bf9e9a5 Iustin Pop
    @rtype: (list, list)
1174 8bf9e9a5 Iustin Pop
    @return: a tuple with two lists: the primary and the secondary instances
1175 8bf9e9a5 Iustin Pop

1176 8bf9e9a5 Iustin Pop
    """
1177 8bf9e9a5 Iustin Pop
    pri = []
1178 8bf9e9a5 Iustin Pop
    sec = []
1179 8bf9e9a5 Iustin Pop
    for inst in self._config_data.instances.values():
1180 8bf9e9a5 Iustin Pop
      if inst.primary_node == node_name:
1181 8bf9e9a5 Iustin Pop
        pri.append(inst.name)
1182 8bf9e9a5 Iustin Pop
      if node_name in inst.secondary_nodes:
1183 8bf9e9a5 Iustin Pop
        sec.append(inst.name)
1184 8bf9e9a5 Iustin Pop
    return (pri, sec)
1185 8bf9e9a5 Iustin Pop
1186 f78ede4e Guido Trotter
  def _UnlockedGetNodeList(self):
1187 a8083063 Iustin Pop
    """Return the list of nodes which are in the configuration.
1188 a8083063 Iustin Pop

1189 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
1190 c41eea6e Iustin Pop
    held.
1191 c41eea6e Iustin Pop

1192 c41eea6e Iustin Pop
    @rtype: list
1193 f78ede4e Guido Trotter

1194 a8083063 Iustin Pop
    """
1195 a8083063 Iustin Pop
    return self._config_data.nodes.keys()
1196 a8083063 Iustin Pop
1197 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1198 f78ede4e Guido Trotter
  def GetNodeList(self):
1199 f78ede4e Guido Trotter
    """Return the list of nodes which are in the configuration.
1200 f78ede4e Guido Trotter

1201 f78ede4e Guido Trotter
    """
1202 f78ede4e Guido Trotter
    return self._UnlockedGetNodeList()
1203 f78ede4e Guido Trotter
1204 6819dc49 Iustin Pop
  def _UnlockedGetOnlineNodeList(self):
1205 94a02bb5 Iustin Pop
    """Return the list of nodes which are online.
1206 94a02bb5 Iustin Pop

1207 94a02bb5 Iustin Pop
    """
1208 94a02bb5 Iustin Pop
    all_nodes = [self._UnlockedGetNodeInfo(node)
1209 94a02bb5 Iustin Pop
                 for node in self._UnlockedGetNodeList()]
1210 94a02bb5 Iustin Pop
    return [node.name for node in all_nodes if not node.offline]
1211 94a02bb5 Iustin Pop
1212 94a02bb5 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
1213 6819dc49 Iustin Pop
  def GetOnlineNodeList(self):
1214 6819dc49 Iustin Pop
    """Return the list of nodes which are online.
1215 6819dc49 Iustin Pop

1216 6819dc49 Iustin Pop
    """
1217 6819dc49 Iustin Pop
    return self._UnlockedGetOnlineNodeList()
1218 6819dc49 Iustin Pop
1219 6819dc49 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
1220 8bf9e9a5 Iustin Pop
  def GetNonVmCapableNodeList(self):
1221 8bf9e9a5 Iustin Pop
    """Return the list of nodes which are not vm capable.
1222 8bf9e9a5 Iustin Pop

1223 8bf9e9a5 Iustin Pop
    """
1224 8bf9e9a5 Iustin Pop
    all_nodes = [self._UnlockedGetNodeInfo(node)
1225 8bf9e9a5 Iustin Pop
                 for node in self._UnlockedGetNodeList()]
1226 8bf9e9a5 Iustin Pop
    return [node.name for node in all_nodes if not node.vm_capable]
1227 8bf9e9a5 Iustin Pop
1228 8bf9e9a5 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
1229 d65e5776 Iustin Pop
  def GetAllNodesInfo(self):
1230 d65e5776 Iustin Pop
    """Get the configuration of all nodes.
1231 d65e5776 Iustin Pop

1232 d65e5776 Iustin Pop
    @rtype: dict
1233 ec0292f1 Iustin Pop
    @return: dict of (node, node_info), where node_info is what
1234 d65e5776 Iustin Pop
              would GetNodeInfo return for the node
1235 d65e5776 Iustin Pop

1236 d65e5776 Iustin Pop
    """
1237 d65e5776 Iustin Pop
    my_dict = dict([(node, self._UnlockedGetNodeInfo(node))
1238 d65e5776 Iustin Pop
                    for node in self._UnlockedGetNodeList()])
1239 d65e5776 Iustin Pop
    return my_dict
1240 d65e5776 Iustin Pop
1241 23f06b2b Iustin Pop
  def _UnlockedGetMasterCandidateStats(self, exceptions=None):
1242 ec0292f1 Iustin Pop
    """Get the number of current and maximum desired and possible candidates.
1243 ec0292f1 Iustin Pop

1244 23f06b2b Iustin Pop
    @type exceptions: list
1245 23f06b2b Iustin Pop
    @param exceptions: if passed, list of nodes that should be ignored
1246 ec0292f1 Iustin Pop
    @rtype: tuple
1247 e623dbe3 Guido Trotter
    @return: tuple of (current, desired and possible, possible)
1248 ec0292f1 Iustin Pop

1249 ec0292f1 Iustin Pop
    """
1250 e623dbe3 Guido Trotter
    mc_now = mc_should = mc_max = 0
1251 23f06b2b Iustin Pop
    for node in self._config_data.nodes.values():
1252 23f06b2b Iustin Pop
      if exceptions and node.name in exceptions:
1253 23f06b2b Iustin Pop
        continue
1254 490acd18 Iustin Pop
      if not (node.offline or node.drained) and node.master_capable:
1255 ec0292f1 Iustin Pop
        mc_max += 1
1256 ec0292f1 Iustin Pop
      if node.master_candidate:
1257 ec0292f1 Iustin Pop
        mc_now += 1
1258 e623dbe3 Guido Trotter
    mc_should = min(mc_max, self._config_data.cluster.candidate_pool_size)
1259 e623dbe3 Guido Trotter
    return (mc_now, mc_should, mc_max)
1260 ec0292f1 Iustin Pop
1261 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
1262 23f06b2b Iustin Pop
  def GetMasterCandidateStats(self, exceptions=None):
1263 ec0292f1 Iustin Pop
    """Get the number of current and maximum possible candidates.
1264 ec0292f1 Iustin Pop

1265 ec0292f1 Iustin Pop
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
1266 ec0292f1 Iustin Pop

1267 23f06b2b Iustin Pop
    @type exceptions: list
1268 23f06b2b Iustin Pop
    @param exceptions: if passed, list of nodes that should be ignored
1269 ec0292f1 Iustin Pop
    @rtype: tuple
1270 ec0292f1 Iustin Pop
    @return: tuple of (current, max)
1271 ec0292f1 Iustin Pop

1272 ec0292f1 Iustin Pop
    """
1273 23f06b2b Iustin Pop
    return self._UnlockedGetMasterCandidateStats(exceptions)
1274 ec0292f1 Iustin Pop
1275 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock)
1276 44485f49 Guido Trotter
  def MaintainCandidatePool(self, exceptions):
1277 ec0292f1 Iustin Pop
    """Try to grow the candidate pool to the desired size.
1278 ec0292f1 Iustin Pop

1279 44485f49 Guido Trotter
    @type exceptions: list
1280 44485f49 Guido Trotter
    @param exceptions: if passed, list of nodes that should be ignored
1281 ec0292f1 Iustin Pop
    @rtype: list
1282 ee513a66 Iustin Pop
    @return: list with the adjusted nodes (L{objects.Node} instances)
1283 ec0292f1 Iustin Pop

1284 ec0292f1 Iustin Pop
    """
1285 44485f49 Guido Trotter
    mc_now, mc_max, _ = self._UnlockedGetMasterCandidateStats(exceptions)
1286 ec0292f1 Iustin Pop
    mod_list = []
1287 ec0292f1 Iustin Pop
    if mc_now < mc_max:
1288 ec0292f1 Iustin Pop
      node_list = self._config_data.nodes.keys()
1289 ec0292f1 Iustin Pop
      random.shuffle(node_list)
1290 ec0292f1 Iustin Pop
      for name in node_list:
1291 ec0292f1 Iustin Pop
        if mc_now >= mc_max:
1292 ec0292f1 Iustin Pop
          break
1293 ec0292f1 Iustin Pop
        node = self._config_data.nodes[name]
1294 44485f49 Guido Trotter
        if (node.master_candidate or node.offline or node.drained or
1295 490acd18 Iustin Pop
            node.name in exceptions or not node.master_capable):
1296 ec0292f1 Iustin Pop
          continue
1297 ee513a66 Iustin Pop
        mod_list.append(node)
1298 ec0292f1 Iustin Pop
        node.master_candidate = True
1299 ec0292f1 Iustin Pop
        node.serial_no += 1
1300 ec0292f1 Iustin Pop
        mc_now += 1
1301 ec0292f1 Iustin Pop
      if mc_now != mc_max:
1302 ec0292f1 Iustin Pop
        # this should not happen
1303 ec0292f1 Iustin Pop
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
1304 ec0292f1 Iustin Pop
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
1305 ec0292f1 Iustin Pop
      if mod_list:
1306 ec0292f1 Iustin Pop
        self._config_data.cluster.serial_no += 1
1307 ec0292f1 Iustin Pop
        self._WriteConfig()
1308 ec0292f1 Iustin Pop
1309 ec0292f1 Iustin Pop
    return mod_list
1310 ec0292f1 Iustin Pop
1311 190e3cb6 Guido Trotter
  def _UnlockedAddNodeToGroup(self, node_name, nodegroup_uuid):
1312 190e3cb6 Guido Trotter
    """Add a given node to the specified group.
1313 190e3cb6 Guido Trotter

1314 190e3cb6 Guido Trotter
    """
1315 190e3cb6 Guido Trotter
    if nodegroup_uuid not in self._config_data.nodegroups:
1316 190e3cb6 Guido Trotter
      # This can happen if a node group gets deleted between its lookup and
1317 190e3cb6 Guido Trotter
      # when we're adding the first node to it, since we don't keep a lock in
1318 190e3cb6 Guido Trotter
      # the meantime. It's ok though, as we'll fail cleanly if the node group
1319 190e3cb6 Guido Trotter
      # is not found anymore.
1320 f936c153 Iustin Pop
      raise errors.OpExecError("Unknown node group: %s" % nodegroup_uuid)
1321 190e3cb6 Guido Trotter
    if node_name not in self._config_data.nodegroups[nodegroup_uuid].members:
1322 190e3cb6 Guido Trotter
      self._config_data.nodegroups[nodegroup_uuid].members.append(node_name)
1323 190e3cb6 Guido Trotter
1324 190e3cb6 Guido Trotter
  def _UnlockedRemoveNodeFromGroup(self, node):
1325 190e3cb6 Guido Trotter
    """Remove a given node from its group.
1326 190e3cb6 Guido Trotter

1327 190e3cb6 Guido Trotter
    """
1328 f936c153 Iustin Pop
    nodegroup = node.group
1329 190e3cb6 Guido Trotter
    if nodegroup not in self._config_data.nodegroups:
1330 f936c153 Iustin Pop
      logging.warning("Warning: node '%s' has unknown node group '%s'"
1331 190e3cb6 Guido Trotter
                      " (while being removed from it)", node.name, nodegroup)
1332 190e3cb6 Guido Trotter
    nodegroup_obj = self._config_data.nodegroups[nodegroup]
1333 190e3cb6 Guido Trotter
    if node.name not in nodegroup_obj.members:
1334 f936c153 Iustin Pop
      logging.warning("Warning: node '%s' not a member of its node group '%s'"
1335 190e3cb6 Guido Trotter
                      " (while being removed from it)", node.name, nodegroup)
1336 190e3cb6 Guido Trotter
    else:
1337 190e3cb6 Guido Trotter
      nodegroup_obj.members.remove(node.name)
1338 190e3cb6 Guido Trotter
1339 a8083063 Iustin Pop
  def _BumpSerialNo(self):
1340 a8083063 Iustin Pop
    """Bump up the serial number of the config.
1341 a8083063 Iustin Pop

1342 a8083063 Iustin Pop
    """
1343 9d38c6e1 Iustin Pop
    self._config_data.serial_no += 1
1344 d693c864 Iustin Pop
    self._config_data.mtime = time.time()
1345 a8083063 Iustin Pop
1346 76d5d3a3 Iustin Pop
  def _AllUUIDObjects(self):
1347 76d5d3a3 Iustin Pop
    """Returns all objects with uuid attributes.
1348 76d5d3a3 Iustin Pop

1349 76d5d3a3 Iustin Pop
    """
1350 76d5d3a3 Iustin Pop
    return (self._config_data.instances.values() +
1351 76d5d3a3 Iustin Pop
            self._config_data.nodes.values() +
1352 3df43542 Guido Trotter
            self._config_data.nodegroups.values() +
1353 76d5d3a3 Iustin Pop
            [self._config_data.cluster])
1354 76d5d3a3 Iustin Pop
1355 eb180fe2 Iustin Pop
  def _OpenConfig(self, accept_foreign):
1356 a8083063 Iustin Pop
    """Read the config data from disk.
1357 a8083063 Iustin Pop

1358 a8083063 Iustin Pop
    """
1359 13998ef2 Michael Hanselmann
    raw_data = utils.ReadFile(self._cfg_file)
1360 13998ef2 Michael Hanselmann
1361 a8083063 Iustin Pop
    try:
1362 13998ef2 Michael Hanselmann
      data = objects.ConfigData.FromDict(serializer.Load(raw_data))
1363 13998ef2 Michael Hanselmann
    except Exception, err:
1364 13998ef2 Michael Hanselmann
      raise errors.ConfigurationError(err)
1365 5b263ed7 Michael Hanselmann
1366 5b263ed7 Michael Hanselmann
    # Make sure the configuration has the right version
1367 5b263ed7 Michael Hanselmann
    _ValidateConfig(data)
1368 5b263ed7 Michael Hanselmann
1369 a8083063 Iustin Pop
    if (not hasattr(data, 'cluster') or
1370 243cdbcc Michael Hanselmann
        not hasattr(data.cluster, 'rsahostkeypub')):
1371 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Incomplete configuration"
1372 243cdbcc Michael Hanselmann
                                      " (missing cluster.rsahostkeypub)")
1373 90d726a8 Iustin Pop
1374 eb180fe2 Iustin Pop
    if data.cluster.master_node != self._my_hostname and not accept_foreign:
1375 eb180fe2 Iustin Pop
      msg = ("The configuration denotes node %s as master, while my"
1376 eb180fe2 Iustin Pop
             " hostname is %s; opening a foreign configuration is only"
1377 eb180fe2 Iustin Pop
             " possible in accept_foreign mode" %
1378 eb180fe2 Iustin Pop
             (data.cluster.master_node, self._my_hostname))
1379 eb180fe2 Iustin Pop
      raise errors.ConfigurationError(msg)
1380 eb180fe2 Iustin Pop
1381 90d726a8 Iustin Pop
    # Upgrade configuration if needed
1382 90d726a8 Iustin Pop
    data.UpgradeConfig()
1383 90d726a8 Iustin Pop
1384 a8083063 Iustin Pop
    self._config_data = data
1385 3c7f6c44 Iustin Pop
    # reset the last serial as -1 so that the next write will cause
1386 0779e3aa Iustin Pop
    # ssconf update
1387 0779e3aa Iustin Pop
    self._last_cluster_serial = -1
1388 a8083063 Iustin Pop
1389 76d5d3a3 Iustin Pop
    # And finally run our (custom) config upgrade sequence
1390 76d5d3a3 Iustin Pop
    self._UpgradeConfig()
1391 76d5d3a3 Iustin Pop
1392 bd407597 Iustin Pop
    self._cfg_id = utils.GetFileID(path=self._cfg_file)
1393 bd407597 Iustin Pop
1394 76d5d3a3 Iustin Pop
  def _UpgradeConfig(self):
1395 76d5d3a3 Iustin Pop
    """Run upgrade steps that cannot be done purely in the objects.
1396 76d5d3a3 Iustin Pop

1397 76d5d3a3 Iustin Pop
    This is because some data elements need uniqueness across the
1398 76d5d3a3 Iustin Pop
    whole configuration, etc.
1399 76d5d3a3 Iustin Pop

1400 111c4e2f Guido Trotter
    @warning: this function will call L{_WriteConfig()}, but also
1401 111c4e2f Guido Trotter
        L{DropECReservations} so it needs to be called only from a
1402 111c4e2f Guido Trotter
        "safe" place (the constructor). If one wanted to call it with
1403 111c4e2f Guido Trotter
        the lock held, a DropECReservationUnlocked would need to be
1404 111c4e2f Guido Trotter
        created first, to avoid causing deadlock.
1405 76d5d3a3 Iustin Pop

1406 76d5d3a3 Iustin Pop
    """
1407 76d5d3a3 Iustin Pop
    modified = False
1408 76d5d3a3 Iustin Pop
    for item in self._AllUUIDObjects():
1409 76d5d3a3 Iustin Pop
      if item.uuid is None:
1410 4fae38c5 Guido Trotter
        item.uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
1411 76d5d3a3 Iustin Pop
        modified = True
1412 f9e81396 Guido Trotter
    if not self._config_data.nodegroups:
1413 f9e81396 Guido Trotter
      default_nodegroup_uuid = self._GenerateUniqueID(_UPGRADE_CONFIG_JID)
1414 f9e81396 Guido Trotter
      default_nodegroup = objects.NodeGroup(
1415 f9e81396 Guido Trotter
          uuid=default_nodegroup_uuid,
1416 f9e81396 Guido Trotter
          name="default",
1417 f9e81396 Guido Trotter
          members=[],
1418 f9e81396 Guido Trotter
          )
1419 f9e81396 Guido Trotter
      self._config_data.nodegroups[default_nodegroup_uuid] = default_nodegroup
1420 f9e81396 Guido Trotter
      modified = True
1421 190e3cb6 Guido Trotter
    for node in self._config_data.nodes.values():
1422 f936c153 Iustin Pop
      if not node.group:
1423 f936c153 Iustin Pop
        node.group = self.LookupNodeGroup(None)
1424 190e3cb6 Guido Trotter
        modified = True
1425 190e3cb6 Guido Trotter
      # This is technically *not* an upgrade, but needs to be done both when
1426 190e3cb6 Guido Trotter
      # nodegroups are being added, and upon normally loading the config,
1427 190e3cb6 Guido Trotter
      # because the members list of a node group is discarded upon
1428 190e3cb6 Guido Trotter
      # serializing/deserializing the object.
1429 f936c153 Iustin Pop
      self._UnlockedAddNodeToGroup(node.name, node.group)
1430 76d5d3a3 Iustin Pop
    if modified:
1431 76d5d3a3 Iustin Pop
      self._WriteConfig()
1432 4fae38c5 Guido Trotter
      # This is ok even if it acquires the internal lock, as _UpgradeConfig is
1433 4fae38c5 Guido Trotter
      # only called at config init time, without the lock held
1434 4fae38c5 Guido Trotter
      self.DropECReservations(_UPGRADE_CONFIG_JID)
1435 4fae38c5 Guido Trotter
1436 a4eae71f Michael Hanselmann
  def _DistributeConfig(self, feedback_fn):
1437 a8083063 Iustin Pop
    """Distribute the configuration to the other nodes.
1438 a8083063 Iustin Pop

1439 a8083063 Iustin Pop
    Currently, this only copies the configuration file. In the future,
1440 a8083063 Iustin Pop
    it could be used to encapsulate the 2/3-phase update mechanism.
1441 a8083063 Iustin Pop

1442 a8083063 Iustin Pop
    """
1443 a8083063 Iustin Pop
    if self._offline:
1444 a8083063 Iustin Pop
      return True
1445 a4eae71f Michael Hanselmann
1446 a8083063 Iustin Pop
    bad = False
1447 a8083063 Iustin Pop
1448 6a5b8b4b Iustin Pop
    node_list = []
1449 6a5b8b4b Iustin Pop
    addr_list = []
1450 6a5b8b4b Iustin Pop
    myhostname = self._my_hostname
1451 6b294c53 Iustin Pop
    # we can skip checking whether _UnlockedGetNodeInfo returns None
1452 6b294c53 Iustin Pop
    # since the node list comes from _UnlocketGetNodeList, and we are
1453 6b294c53 Iustin Pop
    # called with the lock held, so no modifications should take place
1454 6b294c53 Iustin Pop
    # in between
1455 6a5b8b4b Iustin Pop
    for node_name in self._UnlockedGetNodeList():
1456 6a5b8b4b Iustin Pop
      if node_name == myhostname:
1457 6a5b8b4b Iustin Pop
        continue
1458 6a5b8b4b Iustin Pop
      node_info = self._UnlockedGetNodeInfo(node_name)
1459 6a5b8b4b Iustin Pop
      if not node_info.master_candidate:
1460 6a5b8b4b Iustin Pop
        continue
1461 6a5b8b4b Iustin Pop
      node_list.append(node_info.name)
1462 6a5b8b4b Iustin Pop
      addr_list.append(node_info.primary_ip)
1463 6b294c53 Iustin Pop
1464 6a5b8b4b Iustin Pop
    result = rpc.RpcRunner.call_upload_file(node_list, self._cfg_file,
1465 6a5b8b4b Iustin Pop
                                            address_list=addr_list)
1466 1b54fc6c Guido Trotter
    for to_node, to_result in result.items():
1467 3cebe102 Michael Hanselmann
      msg = to_result.fail_msg
1468 1b54fc6c Guido Trotter
      if msg:
1469 1b54fc6c Guido Trotter
        msg = ("Copy of file %s to node %s failed: %s" %
1470 dd7db360 Iustin Pop
               (self._cfg_file, to_node, msg))
1471 1b54fc6c Guido Trotter
        logging.error(msg)
1472 a4eae71f Michael Hanselmann
1473 a4eae71f Michael Hanselmann
        if feedback_fn:
1474 a4eae71f Michael Hanselmann
          feedback_fn(msg)
1475 a4eae71f Michael Hanselmann
1476 a8083063 Iustin Pop
        bad = True
1477 a4eae71f Michael Hanselmann
1478 a8083063 Iustin Pop
    return not bad
1479 a8083063 Iustin Pop
1480 a4eae71f Michael Hanselmann
  def _WriteConfig(self, destination=None, feedback_fn=None):
1481 a8083063 Iustin Pop
    """Write the configuration data to persistent storage.
1482 a8083063 Iustin Pop

1483 a8083063 Iustin Pop
    """
1484 a4eae71f Michael Hanselmann
    assert feedback_fn is None or callable(feedback_fn)
1485 a4eae71f Michael Hanselmann
1486 d2231b8c Iustin Pop
    # Warn on config errors, but don't abort the save - the
1487 d2231b8c Iustin Pop
    # configuration has already been modified, and we can't revert;
1488 d2231b8c Iustin Pop
    # the best we can do is to warn the user and save as is, leaving
1489 d2231b8c Iustin Pop
    # recovery to the user
1490 4a89c54a Iustin Pop
    config_errors = self._UnlockedVerifyConfig()
1491 4a89c54a Iustin Pop
    if config_errors:
1492 d2231b8c Iustin Pop
      errmsg = ("Configuration data is not consistent: %s" %
1493 1f864b60 Iustin Pop
                (utils.CommaJoin(config_errors)))
1494 d2231b8c Iustin Pop
      logging.critical(errmsg)
1495 d2231b8c Iustin Pop
      if feedback_fn:
1496 d2231b8c Iustin Pop
        feedback_fn(errmsg)
1497 d2231b8c Iustin Pop
1498 a8083063 Iustin Pop
    if destination is None:
1499 a8083063 Iustin Pop
      destination = self._cfg_file
1500 a8083063 Iustin Pop
    self._BumpSerialNo()
1501 8d14b30d Iustin Pop
    txt = serializer.Dump(self._config_data.ToDict())
1502 13998ef2 Michael Hanselmann
1503 e60c73a1 René Nussbaumer
    getents = self._getents()
1504 bd407597 Iustin Pop
    try:
1505 bd407597 Iustin Pop
      fd = utils.SafeWriteFile(destination, self._cfg_id, data=txt,
1506 bd407597 Iustin Pop
                               close=False, gid=getents.confd_gid, mode=0640)
1507 bd407597 Iustin Pop
    except errors.LockError:
1508 bd407597 Iustin Pop
      raise errors.ConfigurationError("The configuration file has been"
1509 bd407597 Iustin Pop
                                      " modified since the last write, cannot"
1510 bd407597 Iustin Pop
                                      " update")
1511 bd407597 Iustin Pop
    try:
1512 bd407597 Iustin Pop
      self._cfg_id = utils.GetFileID(fd=fd)
1513 bd407597 Iustin Pop
    finally:
1514 bd407597 Iustin Pop
      os.close(fd)
1515 13998ef2 Michael Hanselmann
1516 14e15659 Iustin Pop
    self.write_count += 1
1517 3d3a04bc Iustin Pop
1518 f56618e0 Iustin Pop
    # and redistribute the config file to master candidates
1519 a4eae71f Michael Hanselmann
    self._DistributeConfig(feedback_fn)
1520 a8083063 Iustin Pop
1521 54d1a06e Michael Hanselmann
    # Write ssconf files on all nodes (including locally)
1522 0779e3aa Iustin Pop
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
1523 d9a855f1 Michael Hanselmann
      if not self._offline:
1524 cd34faf2 Michael Hanselmann
        result = rpc.RpcRunner.call_write_ssconf_files(
1525 6819dc49 Iustin Pop
          self._UnlockedGetOnlineNodeList(),
1526 e1e75d00 Iustin Pop
          self._UnlockedGetSsconfValues())
1527 a4eae71f Michael Hanselmann
1528 e1e75d00 Iustin Pop
        for nname, nresu in result.items():
1529 3cebe102 Michael Hanselmann
          msg = nresu.fail_msg
1530 e1e75d00 Iustin Pop
          if msg:
1531 a4eae71f Michael Hanselmann
            errmsg = ("Error while uploading ssconf files to"
1532 a4eae71f Michael Hanselmann
                      " node %s: %s" % (nname, msg))
1533 a4eae71f Michael Hanselmann
            logging.warning(errmsg)
1534 a4eae71f Michael Hanselmann
1535 a4eae71f Michael Hanselmann
            if feedback_fn:
1536 a4eae71f Michael Hanselmann
              feedback_fn(errmsg)
1537 a4eae71f Michael Hanselmann
1538 0779e3aa Iustin Pop
      self._last_cluster_serial = self._config_data.cluster.serial_no
1539 54d1a06e Michael Hanselmann
1540 03d1dba2 Michael Hanselmann
  def _UnlockedGetSsconfValues(self):
1541 054596f0 Iustin Pop
    """Return the values needed by ssconf.
1542 054596f0 Iustin Pop

1543 054596f0 Iustin Pop
    @rtype: dict
1544 054596f0 Iustin Pop
    @return: a dictionary with keys the ssconf names and values their
1545 054596f0 Iustin Pop
        associated value
1546 054596f0 Iustin Pop

1547 054596f0 Iustin Pop
    """
1548 a3316e4a Iustin Pop
    fn = "\n".join
1549 81a49123 Iustin Pop
    instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
1550 a3316e4a Iustin Pop
    node_names = utils.NiceSort(self._UnlockedGetNodeList())
1551 a3316e4a Iustin Pop
    node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
1552 c3029d0a Luca Bigliardi
    node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
1553 5909fb97 Luca Bigliardi
                    for ninfo in node_info]
1554 c3029d0a Luca Bigliardi
    node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
1555 5909fb97 Luca Bigliardi
                    for ninfo in node_info]
1556 a3316e4a Iustin Pop
1557 81a49123 Iustin Pop
    instance_data = fn(instance_names)
1558 a3316e4a Iustin Pop
    off_data = fn(node.name for node in node_info if node.offline)
1559 81a49123 Iustin Pop
    on_data = fn(node.name for node in node_info if not node.offline)
1560 a3316e4a Iustin Pop
    mc_data = fn(node.name for node in node_info if node.master_candidate)
1561 8113a52e Luca Bigliardi
    mc_ips_data = fn(node.primary_ip for node in node_info
1562 8113a52e Luca Bigliardi
                     if node.master_candidate)
1563 a3316e4a Iustin Pop
    node_data = fn(node_names)
1564 f9780ccd Luca Bigliardi
    node_pri_ips_data = fn(node_pri_ips)
1565 f9780ccd Luca Bigliardi
    node_snd_ips_data = fn(node_snd_ips)
1566 f56618e0 Iustin Pop
1567 054596f0 Iustin Pop
    cluster = self._config_data.cluster
1568 5d60b3bd Iustin Pop
    cluster_tags = fn(cluster.GetTags())
1569 4f7a6a10 Iustin Pop
1570 4f7a6a10 Iustin Pop
    hypervisor_list = fn(cluster.enabled_hypervisors)
1571 4f7a6a10 Iustin Pop
1572 0fbae49a Balazs Lecz
    uid_pool = uidpool.FormatUidPool(cluster.uid_pool, separator="\n")
1573 0fbae49a Balazs Lecz
1574 6f076453 Guido Trotter
    nodegroups = ["%s %s" % (nodegroup.uuid, nodegroup.name) for nodegroup in
1575 6f076453 Guido Trotter
                  self._config_data.nodegroups.values()]
1576 6f076453 Guido Trotter
    nodegroups_data = fn(utils.NiceSort(nodegroups))
1577 6f076453 Guido Trotter
1578 03d1dba2 Michael Hanselmann
    return {
1579 054596f0 Iustin Pop
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
1580 5d60b3bd Iustin Pop
      constants.SS_CLUSTER_TAGS: cluster_tags,
1581 054596f0 Iustin Pop
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
1582 a3316e4a Iustin Pop
      constants.SS_MASTER_CANDIDATES: mc_data,
1583 8113a52e Luca Bigliardi
      constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
1584 054596f0 Iustin Pop
      constants.SS_MASTER_IP: cluster.master_ip,
1585 054596f0 Iustin Pop
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
1586 054596f0 Iustin Pop
      constants.SS_MASTER_NODE: cluster.master_node,
1587 a3316e4a Iustin Pop
      constants.SS_NODE_LIST: node_data,
1588 f9780ccd Luca Bigliardi
      constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
1589 f9780ccd Luca Bigliardi
      constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
1590 a3316e4a Iustin Pop
      constants.SS_OFFLINE_NODES: off_data,
1591 81a49123 Iustin Pop
      constants.SS_ONLINE_NODES: on_data,
1592 868a98ca Manuel Franceschini
      constants.SS_PRIMARY_IP_FAMILY: str(cluster.primary_ip_family),
1593 81a49123 Iustin Pop
      constants.SS_INSTANCE_LIST: instance_data,
1594 8a113c7a Iustin Pop
      constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
1595 4f7a6a10 Iustin Pop
      constants.SS_HYPERVISOR_LIST: hypervisor_list,
1596 5c465a95 Iustin Pop
      constants.SS_MAINTAIN_NODE_HEALTH: str(cluster.maintain_node_health),
1597 0fbae49a Balazs Lecz
      constants.SS_UID_POOL: uid_pool,
1598 6f076453 Guido Trotter
      constants.SS_NODEGROUPS: nodegroups_data,
1599 03d1dba2 Michael Hanselmann
      }
1600 03d1dba2 Michael Hanselmann
1601 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1602 d367b66c Manuel Franceschini
  def GetSsconfValues(self):
1603 d367b66c Manuel Franceschini
    """Wrapper using lock around _UnlockedGetSsconf().
1604 d367b66c Manuel Franceschini

1605 d367b66c Manuel Franceschini
    """
1606 d367b66c Manuel Franceschini
    return self._UnlockedGetSsconfValues()
1607 d367b66c Manuel Franceschini
1608 d367b66c Manuel Franceschini
  @locking.ssynchronized(_config_lock, shared=1)
1609 a8083063 Iustin Pop
  def GetVGName(self):
1610 a8083063 Iustin Pop
    """Return the volume group name.
1611 a8083063 Iustin Pop

1612 a8083063 Iustin Pop
    """
1613 a8083063 Iustin Pop
    return self._config_data.cluster.volume_group_name
1614 a8083063 Iustin Pop
1615 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1616 89ff8e15 Manuel Franceschini
  def SetVGName(self, vg_name):
1617 89ff8e15 Manuel Franceschini
    """Set the volume group name.
1618 89ff8e15 Manuel Franceschini

1619 89ff8e15 Manuel Franceschini
    """
1620 2d4011cd Manuel Franceschini
    self._config_data.cluster.volume_group_name = vg_name
1621 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
1622 89ff8e15 Manuel Franceschini
    self._WriteConfig()
1623 89ff8e15 Manuel Franceschini
1624 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1625 9e33896b Luca Bigliardi
  def GetDRBDHelper(self):
1626 9e33896b Luca Bigliardi
    """Return DRBD usermode helper.
1627 9e33896b Luca Bigliardi

1628 9e33896b Luca Bigliardi
    """
1629 9e33896b Luca Bigliardi
    return self._config_data.cluster.drbd_usermode_helper
1630 9e33896b Luca Bigliardi
1631 9e33896b Luca Bigliardi
  @locking.ssynchronized(_config_lock)
1632 9e33896b Luca Bigliardi
  def SetDRBDHelper(self, drbd_helper):
1633 9e33896b Luca Bigliardi
    """Set DRBD usermode helper.
1634 9e33896b Luca Bigliardi

1635 9e33896b Luca Bigliardi
    """
1636 9e33896b Luca Bigliardi
    self._config_data.cluster.drbd_usermode_helper = drbd_helper
1637 9e33896b Luca Bigliardi
    self._config_data.cluster.serial_no += 1
1638 9e33896b Luca Bigliardi
    self._WriteConfig()
1639 9e33896b Luca Bigliardi
1640 9e33896b Luca Bigliardi
  @locking.ssynchronized(_config_lock, shared=1)
1641 a8083063 Iustin Pop
  def GetMACPrefix(self):
1642 a8083063 Iustin Pop
    """Return the mac prefix.
1643 a8083063 Iustin Pop

1644 a8083063 Iustin Pop
    """
1645 a8083063 Iustin Pop
    return self._config_data.cluster.mac_prefix
1646 62779dd0 Iustin Pop
1647 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1648 62779dd0 Iustin Pop
  def GetClusterInfo(self):
1649 5bbd3f7f Michael Hanselmann
    """Returns information about the cluster
1650 62779dd0 Iustin Pop

1651 c41eea6e Iustin Pop
    @rtype: L{objects.Cluster}
1652 c41eea6e Iustin Pop
    @return: the cluster object
1653 62779dd0 Iustin Pop

1654 62779dd0 Iustin Pop
    """
1655 62779dd0 Iustin Pop
    return self._config_data.cluster
1656 e00fb268 Iustin Pop
1657 51cb1581 Luca Bigliardi
  @locking.ssynchronized(_config_lock, shared=1)
1658 51cb1581 Luca Bigliardi
  def HasAnyDiskOfType(self, dev_type):
1659 51cb1581 Luca Bigliardi
    """Check if in there is at disk of the given type in the configuration.
1660 51cb1581 Luca Bigliardi

1661 51cb1581 Luca Bigliardi
    """
1662 51cb1581 Luca Bigliardi
    return self._config_data.HasAnyDiskOfType(dev_type)
1663 51cb1581 Luca Bigliardi
1664 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1665 a4eae71f Michael Hanselmann
  def Update(self, target, feedback_fn):
1666 e00fb268 Iustin Pop
    """Notify function to be called after updates.
1667 e00fb268 Iustin Pop

1668 e00fb268 Iustin Pop
    This function must be called when an object (as returned by
1669 e00fb268 Iustin Pop
    GetInstanceInfo, GetNodeInfo, GetCluster) has been updated and the
1670 e00fb268 Iustin Pop
    caller wants the modifications saved to the backing store. Note
1671 e00fb268 Iustin Pop
    that all modified objects will be saved, but the target argument
1672 e00fb268 Iustin Pop
    is the one the caller wants to ensure that it's saved.
1673 e00fb268 Iustin Pop

1674 c41eea6e Iustin Pop
    @param target: an instance of either L{objects.Cluster},
1675 c41eea6e Iustin Pop
        L{objects.Node} or L{objects.Instance} which is existing in
1676 c41eea6e Iustin Pop
        the cluster
1677 a4eae71f Michael Hanselmann
    @param feedback_fn: Callable feedback function
1678 c41eea6e Iustin Pop

1679 e00fb268 Iustin Pop
    """
1680 e00fb268 Iustin Pop
    if self._config_data is None:
1681 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Configuration file not read,"
1682 3ecf6786 Iustin Pop
                                   " cannot save.")
1683 f34901f8 Iustin Pop
    update_serial = False
1684 e00fb268 Iustin Pop
    if isinstance(target, objects.Cluster):
1685 e00fb268 Iustin Pop
      test = target == self._config_data.cluster
1686 e00fb268 Iustin Pop
    elif isinstance(target, objects.Node):
1687 e00fb268 Iustin Pop
      test = target in self._config_data.nodes.values()
1688 f34901f8 Iustin Pop
      update_serial = True
1689 e00fb268 Iustin Pop
    elif isinstance(target, objects.Instance):
1690 e00fb268 Iustin Pop
      test = target in self._config_data.instances.values()
1691 e00fb268 Iustin Pop
    else:
1692 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
1693 3ecf6786 Iustin Pop
                                   " ConfigWriter.Update" % type(target))
1694 e00fb268 Iustin Pop
    if not test:
1695 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Configuration updated since object"
1696 3ecf6786 Iustin Pop
                                      " has been read or unknown object")
1697 f34901f8 Iustin Pop
    target.serial_no += 1
1698 d693c864 Iustin Pop
    target.mtime = now = time.time()
1699 f34901f8 Iustin Pop
1700 cff4c037 Iustin Pop
    if update_serial:
1701 f34901f8 Iustin Pop
      # for node updates, we need to increase the cluster serial too
1702 f34901f8 Iustin Pop
      self._config_data.cluster.serial_no += 1
1703 d693c864 Iustin Pop
      self._config_data.cluster.mtime = now
1704 b989e85d Iustin Pop
1705 61cf6b5e Iustin Pop
    if isinstance(target, objects.Instance):
1706 61cf6b5e Iustin Pop
      self._UnlockedReleaseDRBDMinors(target.name)
1707 61cf6b5e Iustin Pop
1708 a4eae71f Michael Hanselmann
    self._WriteConfig(feedback_fn=feedback_fn)
1709 73064714 Guido Trotter
1710 73064714 Guido Trotter
  @locking.ssynchronized(_config_lock)
1711 73064714 Guido Trotter
  def DropECReservations(self, ec_id):
1712 73064714 Guido Trotter
    """Drop per-execution-context reservations
1713 73064714 Guido Trotter

1714 73064714 Guido Trotter
    """
1715 d8aee57e Iustin Pop
    for rm in self._all_rms:
1716 d8aee57e Iustin Pop
      rm.DropECReservations(ec_id)