Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 34cb5617

History | View | Annotate | Download (43.1 kB)

1 2f31098c Iustin Pop
#
2 a8083063 Iustin Pop
#
3 a8083063 Iustin Pop
4 a8083063 Iustin Pop
# Copyright (C) 2006, 2007 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 a8083063 Iustin Pop
import os
35 a8083063 Iustin Pop
import tempfile
36 a8083063 Iustin Pop
import random
37 d8470559 Michael Hanselmann
import logging
38 d693c864 Iustin Pop
import time
39 a8083063 Iustin Pop
40 a8083063 Iustin Pop
from ganeti import errors
41 f78ede4e Guido Trotter
from ganeti import locking
42 a8083063 Iustin Pop
from ganeti import utils
43 a8083063 Iustin Pop
from ganeti import constants
44 a8083063 Iustin Pop
from ganeti import rpc
45 a8083063 Iustin Pop
from ganeti import objects
46 8d14b30d Iustin Pop
from ganeti import serializer
47 243cdbcc Michael Hanselmann
48 243cdbcc Michael Hanselmann
49 f78ede4e Guido Trotter
_config_lock = locking.SharedLock()
50 f78ede4e Guido Trotter
51 f78ede4e Guido Trotter
52 5b263ed7 Michael Hanselmann
def _ValidateConfig(data):
53 c41eea6e Iustin Pop
  """Verifies that a configuration objects looks valid.
54 c41eea6e Iustin Pop

55 c41eea6e Iustin Pop
  This only verifies the version of the configuration.
56 c41eea6e Iustin Pop

57 c41eea6e Iustin Pop
  @raise errors.ConfigurationError: if the version differs from what
58 c41eea6e Iustin Pop
      we expect
59 c41eea6e Iustin Pop

60 c41eea6e Iustin Pop
  """
61 5b263ed7 Michael Hanselmann
  if data.version != constants.CONFIG_VERSION:
62 243cdbcc Michael Hanselmann
    raise errors.ConfigurationError("Cluster configuration version"
63 243cdbcc Michael Hanselmann
                                    " mismatch, got %s instead of %s" %
64 5b263ed7 Michael Hanselmann
                                    (data.version,
65 243cdbcc Michael Hanselmann
                                     constants.CONFIG_VERSION))
66 a8083063 Iustin Pop
67 319856a9 Michael Hanselmann
68 a8083063 Iustin Pop
class ConfigWriter:
69 098c0958 Michael Hanselmann
  """The interface to the cluster configuration.
70 a8083063 Iustin Pop

71 098c0958 Michael Hanselmann
  """
72 a8083063 Iustin Pop
  def __init__(self, cfg_file=None, offline=False):
73 14e15659 Iustin Pop
    self.write_count = 0
74 f78ede4e Guido Trotter
    self._lock = _config_lock
75 a8083063 Iustin Pop
    self._config_data = None
76 a8083063 Iustin Pop
    self._offline = offline
77 a8083063 Iustin Pop
    if cfg_file is None:
78 a8083063 Iustin Pop
      self._cfg_file = constants.CLUSTER_CONF_FILE
79 a8083063 Iustin Pop
    else:
80 a8083063 Iustin Pop
      self._cfg_file = cfg_file
81 923b1523 Iustin Pop
    self._temporary_ids = set()
82 a81c53c9 Iustin Pop
    self._temporary_drbds = {}
83 e7d81ba0 Iustin Pop
    self._temporary_macs = set()
84 89e1fc26 Iustin Pop
    # Note: in order to prevent errors when resolving our name in
85 89e1fc26 Iustin Pop
    # _DistributeConfig, we compute it here once and reuse it; it's
86 89e1fc26 Iustin Pop
    # better to raise an error before starting to modify the config
87 89e1fc26 Iustin Pop
    # file than after it was modified
88 89e1fc26 Iustin Pop
    self._my_hostname = utils.HostInfo().name
89 3c7f6c44 Iustin Pop
    self._last_cluster_serial = -1
90 3d3a04bc Iustin Pop
    self._OpenConfig()
91 a8083063 Iustin Pop
92 a8083063 Iustin Pop
  # this method needs to be static, so that we can call it on the class
93 a8083063 Iustin Pop
  @staticmethod
94 a8083063 Iustin Pop
  def IsCluster():
95 a8083063 Iustin Pop
    """Check if the cluster is configured.
96 a8083063 Iustin Pop

97 a8083063 Iustin Pop
    """
98 a8083063 Iustin Pop
    return os.path.exists(constants.CLUSTER_CONF_FILE)
99 a8083063 Iustin Pop
100 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
101 a8083063 Iustin Pop
  def GenerateMAC(self):
102 a8083063 Iustin Pop
    """Generate a MAC for an instance.
103 a8083063 Iustin Pop

104 a8083063 Iustin Pop
    This should check the current instances for duplicates.
105 a8083063 Iustin Pop

106 a8083063 Iustin Pop
    """
107 a8083063 Iustin Pop
    prefix = self._config_data.cluster.mac_prefix
108 a8083063 Iustin Pop
    all_macs = self._AllMACs()
109 a8083063 Iustin Pop
    retries = 64
110 a8083063 Iustin Pop
    while retries > 0:
111 a8083063 Iustin Pop
      byte1 = random.randrange(0, 256)
112 a8083063 Iustin Pop
      byte2 = random.randrange(0, 256)
113 a8083063 Iustin Pop
      byte3 = random.randrange(0, 256)
114 a8083063 Iustin Pop
      mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
115 e7d81ba0 Iustin Pop
      if mac not in all_macs and mac not in self._temporary_macs:
116 a8083063 Iustin Pop
        break
117 a8083063 Iustin Pop
      retries -= 1
118 a8083063 Iustin Pop
    else:
119 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Can't generate unique MAC")
120 e7d81ba0 Iustin Pop
    self._temporary_macs.add(mac)
121 a8083063 Iustin Pop
    return mac
122 a8083063 Iustin Pop
123 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
124 1862d460 Alexander Schreiber
  def IsMacInUse(self, mac):
125 1862d460 Alexander Schreiber
    """Predicate: check if the specified MAC is in use in the Ganeti cluster.
126 1862d460 Alexander Schreiber

127 1862d460 Alexander Schreiber
    This only checks instances managed by this cluster, it does not
128 1862d460 Alexander Schreiber
    check for potential collisions elsewhere.
129 1862d460 Alexander Schreiber

130 1862d460 Alexander Schreiber
    """
131 1862d460 Alexander Schreiber
    all_macs = self._AllMACs()
132 e7d81ba0 Iustin Pop
    return mac in all_macs or mac in self._temporary_macs
133 1862d460 Alexander Schreiber
134 f9518d38 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
135 f9518d38 Iustin Pop
  def GenerateDRBDSecret(self):
136 f9518d38 Iustin Pop
    """Generate a DRBD secret.
137 f9518d38 Iustin Pop

138 f9518d38 Iustin Pop
    This checks the current disks for duplicates.
139 f9518d38 Iustin Pop

140 f9518d38 Iustin Pop
    """
141 f9518d38 Iustin Pop
    all_secrets = self._AllDRBDSecrets()
142 f9518d38 Iustin Pop
    retries = 64
143 f9518d38 Iustin Pop
    while retries > 0:
144 f9518d38 Iustin Pop
      secret = utils.GenerateSecret()
145 f9518d38 Iustin Pop
      if secret not in all_secrets:
146 f9518d38 Iustin Pop
        break
147 f9518d38 Iustin Pop
      retries -= 1
148 f9518d38 Iustin Pop
    else:
149 f9518d38 Iustin Pop
      raise errors.ConfigurationError("Can't generate unique DRBD secret")
150 f9518d38 Iustin Pop
    return secret
151 f9518d38 Iustin Pop
152 34e54ebc Iustin Pop
  def _AllLVs(self):
153 923b1523 Iustin Pop
    """Compute the list of all LVs.
154 923b1523 Iustin Pop

155 923b1523 Iustin Pop
    """
156 923b1523 Iustin Pop
    lvnames = set()
157 923b1523 Iustin Pop
    for instance in self._config_data.instances.values():
158 923b1523 Iustin Pop
      node_data = instance.MapLVsByNode()
159 923b1523 Iustin Pop
      for lv_list in node_data.values():
160 923b1523 Iustin Pop
        lvnames.update(lv_list)
161 923b1523 Iustin Pop
    return lvnames
162 923b1523 Iustin Pop
163 34e54ebc Iustin Pop
  def _AllIDs(self, include_temporary):
164 34e54ebc Iustin Pop
    """Compute the list of all UUIDs and names we have.
165 34e54ebc Iustin Pop

166 34e54ebc Iustin Pop
    @type include_temporary: boolean
167 34e54ebc Iustin Pop
    @param include_temporary: whether to include the _temporary_ids set
168 34e54ebc Iustin Pop
    @rtype: set
169 34e54ebc Iustin Pop
    @return: a set of IDs
170 34e54ebc Iustin Pop

171 34e54ebc Iustin Pop
    """
172 34e54ebc Iustin Pop
    existing = set()
173 34e54ebc Iustin Pop
    if include_temporary:
174 34e54ebc Iustin Pop
      existing.update(self._temporary_ids)
175 34e54ebc Iustin Pop
    existing.update(self._AllLVs())
176 34e54ebc Iustin Pop
    existing.update(self._config_data.instances.keys())
177 34e54ebc Iustin Pop
    existing.update(self._config_data.nodes.keys())
178 76d5d3a3 Iustin Pop
    existing.update([i.uuid for i in self._AllUUIDObjects() if i.uuid])
179 34e54ebc Iustin Pop
    return existing
180 34e54ebc Iustin Pop
181 430b923c Iustin Pop
  def _GenerateUniqueID(self, exceptions=None):
182 430b923c Iustin Pop
    """Generate an unique UUID.
183 923b1523 Iustin Pop

184 923b1523 Iustin Pop
    This checks the current node, instances and disk names for
185 923b1523 Iustin Pop
    duplicates.
186 923b1523 Iustin Pop

187 430b923c Iustin Pop
    @param exceptions: a list with some other names which should be
188 430b923c Iustin Pop
        checked for uniqueness (used for example when you want to get
189 430b923c Iustin Pop
        more than one id at one time without adding each one in turn
190 430b923c Iustin Pop
        to the config file)
191 923b1523 Iustin Pop

192 c41eea6e Iustin Pop
    @rtype: string
193 c41eea6e Iustin Pop
    @return: the unique id
194 923b1523 Iustin Pop

195 923b1523 Iustin Pop
    """
196 34e54ebc Iustin Pop
    existing = self._AllIDs(include_temporary=True)
197 923b1523 Iustin Pop
    if exceptions is not None:
198 923b1523 Iustin Pop
      existing.update(exceptions)
199 923b1523 Iustin Pop
    retries = 64
200 923b1523 Iustin Pop
    while retries > 0:
201 24818e8f Michael Hanselmann
      unique_id = utils.NewUUID()
202 923b1523 Iustin Pop
      if unique_id not in existing and unique_id is not None:
203 923b1523 Iustin Pop
        break
204 923b1523 Iustin Pop
    else:
205 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Not able generate an unique ID"
206 3ecf6786 Iustin Pop
                                      " (last tried ID: %s" % unique_id)
207 923b1523 Iustin Pop
    self._temporary_ids.add(unique_id)
208 923b1523 Iustin Pop
    return unique_id
209 923b1523 Iustin Pop
210 430b923c Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
211 430b923c Iustin Pop
  def GenerateUniqueID(self, exceptions=None):
212 430b923c Iustin Pop
    """Generate an unique ID.
213 430b923c Iustin Pop

214 430b923c Iustin Pop
    This is just a wrapper over the unlocked version.
215 430b923c Iustin Pop

216 430b923c Iustin Pop
    """
217 430b923c Iustin Pop
    return self._GenerateUniqueID(exceptions=exceptions)
218 430b923c Iustin Pop
219 34d657ba Iustin Pop
  def _CleanupTemporaryIDs(self):
220 34d657ba Iustin Pop
    """Cleanups the _temporary_ids structure.
221 34d657ba Iustin Pop

222 34d657ba Iustin Pop
    """
223 34d657ba Iustin Pop
    existing = self._AllIDs(include_temporary=False)
224 34d657ba Iustin Pop
    self._temporary_ids = self._temporary_ids - existing
225 34d657ba Iustin Pop
226 a8083063 Iustin Pop
  def _AllMACs(self):
227 a8083063 Iustin Pop
    """Return all MACs present in the config.
228 a8083063 Iustin Pop

229 c41eea6e Iustin Pop
    @rtype: list
230 c41eea6e Iustin Pop
    @return: the list of all MACs
231 c41eea6e Iustin Pop

232 a8083063 Iustin Pop
    """
233 a8083063 Iustin Pop
    result = []
234 a8083063 Iustin Pop
    for instance in self._config_data.instances.values():
235 a8083063 Iustin Pop
      for nic in instance.nics:
236 a8083063 Iustin Pop
        result.append(nic.mac)
237 a8083063 Iustin Pop
238 a8083063 Iustin Pop
    return result
239 a8083063 Iustin Pop
240 f9518d38 Iustin Pop
  def _AllDRBDSecrets(self):
241 f9518d38 Iustin Pop
    """Return all DRBD secrets present in the config.
242 f9518d38 Iustin Pop

243 c41eea6e Iustin Pop
    @rtype: list
244 c41eea6e Iustin Pop
    @return: the list of all DRBD secrets
245 c41eea6e Iustin Pop

246 f9518d38 Iustin Pop
    """
247 f9518d38 Iustin Pop
    def helper(disk, result):
248 f9518d38 Iustin Pop
      """Recursively gather secrets from this disk."""
249 f9518d38 Iustin Pop
      if disk.dev_type == constants.DT_DRBD8:
250 f9518d38 Iustin Pop
        result.append(disk.logical_id[5])
251 f9518d38 Iustin Pop
      if disk.children:
252 f9518d38 Iustin Pop
        for child in disk.children:
253 f9518d38 Iustin Pop
          helper(child, result)
254 f9518d38 Iustin Pop
255 f9518d38 Iustin Pop
    result = []
256 f9518d38 Iustin Pop
    for instance in self._config_data.instances.values():
257 f9518d38 Iustin Pop
      for disk in instance.disks:
258 f9518d38 Iustin Pop
        helper(disk, result)
259 f9518d38 Iustin Pop
260 f9518d38 Iustin Pop
    return result
261 f9518d38 Iustin Pop
262 4b98ac29 Iustin Pop
  def _CheckDiskIDs(self, disk, l_ids, p_ids):
263 4b98ac29 Iustin Pop
    """Compute duplicate disk IDs
264 4b98ac29 Iustin Pop

265 4b98ac29 Iustin Pop
    @type disk: L{objects.Disk}
266 4b98ac29 Iustin Pop
    @param disk: the disk at which to start searching
267 4b98ac29 Iustin Pop
    @type l_ids: list
268 4b98ac29 Iustin Pop
    @param l_ids: list of current logical ids
269 4b98ac29 Iustin Pop
    @type p_ids: list
270 4b98ac29 Iustin Pop
    @param p_ids: list of current physical ids
271 4b98ac29 Iustin Pop
    @rtype: list
272 4b98ac29 Iustin Pop
    @return: a list of error messages
273 4b98ac29 Iustin Pop

274 4b98ac29 Iustin Pop
    """
275 4b98ac29 Iustin Pop
    result = []
276 25ae22e4 Iustin Pop
    if disk.logical_id is not None:
277 25ae22e4 Iustin Pop
      if disk.logical_id in l_ids:
278 25ae22e4 Iustin Pop
        result.append("duplicate logical id %s" % str(disk.logical_id))
279 25ae22e4 Iustin Pop
      else:
280 25ae22e4 Iustin Pop
        l_ids.append(disk.logical_id)
281 25ae22e4 Iustin Pop
    if disk.physical_id is not None:
282 25ae22e4 Iustin Pop
      if disk.physical_id in p_ids:
283 25ae22e4 Iustin Pop
        result.append("duplicate physical id %s" % str(disk.physical_id))
284 25ae22e4 Iustin Pop
      else:
285 25ae22e4 Iustin Pop
        p_ids.append(disk.physical_id)
286 4b98ac29 Iustin Pop
287 4b98ac29 Iustin Pop
    if disk.children:
288 4b98ac29 Iustin Pop
      for child in disk.children:
289 4b98ac29 Iustin Pop
        result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
290 4b98ac29 Iustin Pop
    return result
291 4b98ac29 Iustin Pop
292 4a89c54a Iustin Pop
  def _UnlockedVerifyConfig(self):
293 a8efbb40 Iustin Pop
    """Verify function.
294 a8efbb40 Iustin Pop

295 4a89c54a Iustin Pop
    @rtype: list
296 4a89c54a Iustin Pop
    @return: a list of error messages; a non-empty list signifies
297 4a89c54a Iustin Pop
        configuration errors
298 4a89c54a Iustin Pop

299 a8083063 Iustin Pop
    """
300 a8083063 Iustin Pop
    result = []
301 a8083063 Iustin Pop
    seen_macs = []
302 48ce9fd9 Iustin Pop
    ports = {}
303 a8083063 Iustin Pop
    data = self._config_data
304 4b98ac29 Iustin Pop
    seen_lids = []
305 4b98ac29 Iustin Pop
    seen_pids = []
306 9a5fba23 Guido Trotter
307 9a5fba23 Guido Trotter
    # global cluster checks
308 9a5fba23 Guido Trotter
    if not data.cluster.enabled_hypervisors:
309 9a5fba23 Guido Trotter
      result.append("enabled hypervisors list doesn't have any entries")
310 9a5fba23 Guido Trotter
    invalid_hvs = set(data.cluster.enabled_hypervisors) - constants.HYPER_TYPES
311 9a5fba23 Guido Trotter
    if invalid_hvs:
312 9a5fba23 Guido Trotter
      result.append("enabled hypervisors contains invalid entries: %s" %
313 9a5fba23 Guido Trotter
                    invalid_hvs)
314 9a5fba23 Guido Trotter
315 9a5fba23 Guido Trotter
    if data.cluster.master_node not in data.nodes:
316 9a5fba23 Guido Trotter
      result.append("cluster has invalid primary node '%s'" %
317 9a5fba23 Guido Trotter
                    data.cluster.master_node)
318 9a5fba23 Guido Trotter
319 9a5fba23 Guido Trotter
    # per-instance checks
320 a8083063 Iustin Pop
    for instance_name in data.instances:
321 a8083063 Iustin Pop
      instance = data.instances[instance_name]
322 a8083063 Iustin Pop
      if instance.primary_node not in data.nodes:
323 8522ceeb Iustin Pop
        result.append("instance '%s' has invalid primary node '%s'" %
324 a8083063 Iustin Pop
                      (instance_name, instance.primary_node))
325 a8083063 Iustin Pop
      for snode in instance.secondary_nodes:
326 a8083063 Iustin Pop
        if snode not in data.nodes:
327 8522ceeb Iustin Pop
          result.append("instance '%s' has invalid secondary node '%s'" %
328 a8083063 Iustin Pop
                        (instance_name, snode))
329 a8083063 Iustin Pop
      for idx, nic in enumerate(instance.nics):
330 a8083063 Iustin Pop
        if nic.mac in seen_macs:
331 8522ceeb Iustin Pop
          result.append("instance '%s' has NIC %d mac %s duplicate" %
332 a8083063 Iustin Pop
                        (instance_name, idx, nic.mac))
333 a8083063 Iustin Pop
        else:
334 a8083063 Iustin Pop
          seen_macs.append(nic.mac)
335 48ce9fd9 Iustin Pop
336 48ce9fd9 Iustin Pop
      # gather the drbd ports for duplicate checks
337 48ce9fd9 Iustin Pop
      for dsk in instance.disks:
338 48ce9fd9 Iustin Pop
        if dsk.dev_type in constants.LDS_DRBD:
339 48ce9fd9 Iustin Pop
          tcp_port = dsk.logical_id[2]
340 48ce9fd9 Iustin Pop
          if tcp_port not in ports:
341 48ce9fd9 Iustin Pop
            ports[tcp_port] = []
342 48ce9fd9 Iustin Pop
          ports[tcp_port].append((instance.name, "drbd disk %s" % dsk.iv_name))
343 48ce9fd9 Iustin Pop
      # gather network port reservation
344 48ce9fd9 Iustin Pop
      net_port = getattr(instance, "network_port", None)
345 48ce9fd9 Iustin Pop
      if net_port is not None:
346 48ce9fd9 Iustin Pop
        if net_port not in ports:
347 48ce9fd9 Iustin Pop
          ports[net_port] = []
348 48ce9fd9 Iustin Pop
        ports[net_port].append((instance.name, "network port"))
349 48ce9fd9 Iustin Pop
350 332d0e37 Iustin Pop
      # instance disk verify
351 332d0e37 Iustin Pop
      for idx, disk in enumerate(instance.disks):
352 332d0e37 Iustin Pop
        result.extend(["instance '%s' disk %d error: %s" %
353 332d0e37 Iustin Pop
                       (instance.name, idx, msg) for msg in disk.Verify()])
354 4b98ac29 Iustin Pop
        result.extend(self._CheckDiskIDs(disk, seen_lids, seen_pids))
355 332d0e37 Iustin Pop
356 48ce9fd9 Iustin Pop
    # cluster-wide pool of free ports
357 a8efbb40 Iustin Pop
    for free_port in data.cluster.tcpudp_port_pool:
358 48ce9fd9 Iustin Pop
      if free_port not in ports:
359 48ce9fd9 Iustin Pop
        ports[free_port] = []
360 48ce9fd9 Iustin Pop
      ports[free_port].append(("cluster", "port marked as free"))
361 48ce9fd9 Iustin Pop
362 48ce9fd9 Iustin Pop
    # compute tcp/udp duplicate ports
363 48ce9fd9 Iustin Pop
    keys = ports.keys()
364 48ce9fd9 Iustin Pop
    keys.sort()
365 48ce9fd9 Iustin Pop
    for pnum in keys:
366 48ce9fd9 Iustin Pop
      pdata = ports[pnum]
367 48ce9fd9 Iustin Pop
      if len(pdata) > 1:
368 48ce9fd9 Iustin Pop
        txt = ", ".join(["%s/%s" % val for val in pdata])
369 48ce9fd9 Iustin Pop
        result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
370 48ce9fd9 Iustin Pop
371 48ce9fd9 Iustin Pop
    # highest used tcp port check
372 48ce9fd9 Iustin Pop
    if keys:
373 a8efbb40 Iustin Pop
      if keys[-1] > data.cluster.highest_used_port:
374 48ce9fd9 Iustin Pop
        result.append("Highest used port mismatch, saved %s, computed %s" %
375 a8efbb40 Iustin Pop
                      (data.cluster.highest_used_port, keys[-1]))
376 a8efbb40 Iustin Pop
377 3a26773f Iustin Pop
    if not data.nodes[data.cluster.master_node].master_candidate:
378 3a26773f Iustin Pop
      result.append("Master node is not a master candidate")
379 3a26773f Iustin Pop
380 4a89c54a Iustin Pop
    # master candidate checks
381 ec0292f1 Iustin Pop
    mc_now, mc_max = self._UnlockedGetMasterCandidateStats()
382 ec0292f1 Iustin Pop
    if mc_now < mc_max:
383 ec0292f1 Iustin Pop
      result.append("Not enough master candidates: actual %d, target %d" %
384 ec0292f1 Iustin Pop
                    (mc_now, mc_max))
385 48ce9fd9 Iustin Pop
386 5bf07049 Iustin Pop
    # node checks
387 5bf07049 Iustin Pop
    for node in data.nodes.values():
388 5bf07049 Iustin Pop
      if [node.master_candidate, node.drained, node.offline].count(True) > 1:
389 5bf07049 Iustin Pop
        result.append("Node %s state is invalid: master_candidate=%s,"
390 5bf07049 Iustin Pop
                      " drain=%s, offline=%s" %
391 5bf07049 Iustin Pop
                      (node.name, node.master_candidate, node.drain,
392 5bf07049 Iustin Pop
                       node.offline))
393 5bf07049 Iustin Pop
394 4a89c54a Iustin Pop
    # drbd minors check
395 4a89c54a Iustin Pop
    d_map, duplicates = self._UnlockedComputeDRBDMap()
396 4a89c54a Iustin Pop
    for node, minor, instance_a, instance_b in duplicates:
397 4a89c54a Iustin Pop
      result.append("DRBD minor %d on node %s is assigned twice to instances"
398 4a89c54a Iustin Pop
                    " %s and %s" % (minor, node, instance_a, instance_b))
399 4a89c54a Iustin Pop
400 a8083063 Iustin Pop
    return result
401 a8083063 Iustin Pop
402 4a89c54a Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
403 4a89c54a Iustin Pop
  def VerifyConfig(self):
404 4a89c54a Iustin Pop
    """Verify function.
405 4a89c54a Iustin Pop

406 4a89c54a Iustin Pop
    This is just a wrapper over L{_UnlockedVerifyConfig}.
407 4a89c54a Iustin Pop

408 4a89c54a Iustin Pop
    @rtype: list
409 4a89c54a Iustin Pop
    @return: a list of error messages; a non-empty list signifies
410 4a89c54a Iustin Pop
        configuration errors
411 4a89c54a Iustin Pop

412 4a89c54a Iustin Pop
    """
413 4a89c54a Iustin Pop
    return self._UnlockedVerifyConfig()
414 4a89c54a Iustin Pop
415 f78ede4e Guido Trotter
  def _UnlockedSetDiskID(self, disk, node_name):
416 a8083063 Iustin Pop
    """Convert the unique ID to the ID needed on the target nodes.
417 a8083063 Iustin Pop

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

420 a8083063 Iustin Pop
    The routine descends down and updates its children also, because
421 a8083063 Iustin Pop
    this helps when the only the top device is passed to the remote
422 a8083063 Iustin Pop
    node.
423 a8083063 Iustin Pop

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

426 a8083063 Iustin Pop
    """
427 a8083063 Iustin Pop
    if disk.children:
428 a8083063 Iustin Pop
      for child in disk.children:
429 f78ede4e Guido Trotter
        self._UnlockedSetDiskID(child, node_name)
430 a8083063 Iustin Pop
431 a8083063 Iustin Pop
    if disk.logical_id is None and disk.physical_id is not None:
432 a8083063 Iustin Pop
      return
433 ffa1c0dc Iustin Pop
    if disk.dev_type == constants.LD_DRBD8:
434 f9518d38 Iustin Pop
      pnode, snode, port, pminor, sminor, secret = disk.logical_id
435 a8083063 Iustin Pop
      if node_name not in (pnode, snode):
436 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
437 3ecf6786 Iustin Pop
                                        node_name)
438 f78ede4e Guido Trotter
      pnode_info = self._UnlockedGetNodeInfo(pnode)
439 f78ede4e Guido Trotter
      snode_info = self._UnlockedGetNodeInfo(snode)
440 a8083063 Iustin Pop
      if pnode_info is None or snode_info is None:
441 a8083063 Iustin Pop
        raise errors.ConfigurationError("Can't find primary or secondary node"
442 a8083063 Iustin Pop
                                        " for %s" % str(disk))
443 ffa1c0dc Iustin Pop
      p_data = (pnode_info.secondary_ip, port)
444 ffa1c0dc Iustin Pop
      s_data = (snode_info.secondary_ip, port)
445 a8083063 Iustin Pop
      if pnode == node_name:
446 f9518d38 Iustin Pop
        disk.physical_id = p_data + s_data + (pminor, secret)
447 a8083063 Iustin Pop
      else: # it must be secondary, we tested above
448 f9518d38 Iustin Pop
        disk.physical_id = s_data + p_data + (sminor, secret)
449 a8083063 Iustin Pop
    else:
450 a8083063 Iustin Pop
      disk.physical_id = disk.logical_id
451 a8083063 Iustin Pop
    return
452 a8083063 Iustin Pop
453 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
454 f78ede4e Guido Trotter
  def SetDiskID(self, disk, node_name):
455 f78ede4e Guido Trotter
    """Convert the unique ID to the ID needed on the target nodes.
456 f78ede4e Guido Trotter

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

459 f78ede4e Guido Trotter
    The routine descends down and updates its children also, because
460 f78ede4e Guido Trotter
    this helps when the only the top device is passed to the remote
461 f78ede4e Guido Trotter
    node.
462 f78ede4e Guido Trotter

463 f78ede4e Guido Trotter
    """
464 f78ede4e Guido Trotter
    return self._UnlockedSetDiskID(disk, node_name)
465 f78ede4e Guido Trotter
466 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
467 b2fddf63 Iustin Pop
  def AddTcpUdpPort(self, port):
468 b2fddf63 Iustin Pop
    """Adds a new port to the available port pool.
469 b2fddf63 Iustin Pop

470 b2fddf63 Iustin Pop
    """
471 264bb3c5 Michael Hanselmann
    if not isinstance(port, int):
472 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed for port")
473 264bb3c5 Michael Hanselmann
474 b2fddf63 Iustin Pop
    self._config_data.cluster.tcpudp_port_pool.add(port)
475 264bb3c5 Michael Hanselmann
    self._WriteConfig()
476 264bb3c5 Michael Hanselmann
477 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
478 b2fddf63 Iustin Pop
  def GetPortList(self):
479 264bb3c5 Michael Hanselmann
    """Returns a copy of the current port list.
480 264bb3c5 Michael Hanselmann

481 264bb3c5 Michael Hanselmann
    """
482 b2fddf63 Iustin Pop
    return self._config_data.cluster.tcpudp_port_pool.copy()
483 264bb3c5 Michael Hanselmann
484 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
485 a8083063 Iustin Pop
  def AllocatePort(self):
486 a8083063 Iustin Pop
    """Allocate a port.
487 a8083063 Iustin Pop

488 b2fddf63 Iustin Pop
    The port will be taken from the available port pool or from the
489 b2fddf63 Iustin Pop
    default port range (and in this case we increase
490 b2fddf63 Iustin Pop
    highest_used_port).
491 a8083063 Iustin Pop

492 a8083063 Iustin Pop
    """
493 264bb3c5 Michael Hanselmann
    # If there are TCP/IP ports configured, we use them first.
494 b2fddf63 Iustin Pop
    if self._config_data.cluster.tcpudp_port_pool:
495 b2fddf63 Iustin Pop
      port = self._config_data.cluster.tcpudp_port_pool.pop()
496 264bb3c5 Michael Hanselmann
    else:
497 264bb3c5 Michael Hanselmann
      port = self._config_data.cluster.highest_used_port + 1
498 264bb3c5 Michael Hanselmann
      if port >= constants.LAST_DRBD_PORT:
499 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("The highest used port is greater"
500 3ecf6786 Iustin Pop
                                        " than %s. Aborting." %
501 3ecf6786 Iustin Pop
                                        constants.LAST_DRBD_PORT)
502 264bb3c5 Michael Hanselmann
      self._config_data.cluster.highest_used_port = port
503 a8083063 Iustin Pop
504 a8083063 Iustin Pop
    self._WriteConfig()
505 a8083063 Iustin Pop
    return port
506 a8083063 Iustin Pop
507 6d2e83d5 Iustin Pop
  def _UnlockedComputeDRBDMap(self):
508 a81c53c9 Iustin Pop
    """Compute the used DRBD minor/nodes.
509 a81c53c9 Iustin Pop

510 4a89c54a Iustin Pop
    @rtype: (dict, list)
511 c41eea6e Iustin Pop
    @return: dictionary of node_name: dict of minor: instance_name;
512 c41eea6e Iustin Pop
        the returned dict will have all the nodes in it (even if with
513 4a89c54a Iustin Pop
        an empty list), and a list of duplicates; if the duplicates
514 4a89c54a Iustin Pop
        list is not empty, the configuration is corrupted and its caller
515 4a89c54a Iustin Pop
        should raise an exception
516 a81c53c9 Iustin Pop

517 a81c53c9 Iustin Pop
    """
518 a81c53c9 Iustin Pop
    def _AppendUsedPorts(instance_name, disk, used):
519 4a89c54a Iustin Pop
      duplicates = []
520 f9518d38 Iustin Pop
      if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
521 7c4d6c7b Michael Hanselmann
        node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
522 7c4d6c7b Michael Hanselmann
        for node, port in ((node_a, minor_a), (node_b, minor_b)):
523 4a89c54a Iustin Pop
          assert node in used, ("Node '%s' of instance '%s' not found"
524 4a89c54a Iustin Pop
                                " in node list" % (node, instance_name))
525 a81c53c9 Iustin Pop
          if port in used[node]:
526 4a89c54a Iustin Pop
            duplicates.append((node, port, instance_name, used[node][port]))
527 4a89c54a Iustin Pop
          else:
528 4a89c54a Iustin Pop
            used[node][port] = instance_name
529 a81c53c9 Iustin Pop
      if disk.children:
530 a81c53c9 Iustin Pop
        for child in disk.children:
531 4a89c54a Iustin Pop
          duplicates.extend(_AppendUsedPorts(instance_name, child, used))
532 4a89c54a Iustin Pop
      return duplicates
533 a81c53c9 Iustin Pop
534 4a89c54a Iustin Pop
    duplicates = []
535 a81c53c9 Iustin Pop
    my_dict = dict((node, {}) for node in self._config_data.nodes)
536 79b26a7a Iustin Pop
    for instance in self._config_data.instances.itervalues():
537 79b26a7a Iustin Pop
      for disk in instance.disks:
538 79b26a7a Iustin Pop
        duplicates.extend(_AppendUsedPorts(instance.name, disk, my_dict))
539 a81c53c9 Iustin Pop
    for (node, minor), instance in self._temporary_drbds.iteritems():
540 79b26a7a Iustin Pop
      if minor in my_dict[node] and my_dict[node][minor] != instance:
541 4a89c54a Iustin Pop
        duplicates.append((node, minor, instance, my_dict[node][minor]))
542 4a89c54a Iustin Pop
      else:
543 4a89c54a Iustin Pop
        my_dict[node][minor] = instance
544 4a89c54a Iustin Pop
    return my_dict, duplicates
545 a81c53c9 Iustin Pop
546 a81c53c9 Iustin Pop
  @locking.ssynchronized(_config_lock)
547 6d2e83d5 Iustin Pop
  def ComputeDRBDMap(self):
548 6d2e83d5 Iustin Pop
    """Compute the used DRBD minor/nodes.
549 6d2e83d5 Iustin Pop

550 6d2e83d5 Iustin Pop
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
551 6d2e83d5 Iustin Pop

552 6d2e83d5 Iustin Pop
    @return: dictionary of node_name: dict of minor: instance_name;
553 6d2e83d5 Iustin Pop
        the returned dict will have all the nodes in it (even if with
554 6d2e83d5 Iustin Pop
        an empty list).
555 6d2e83d5 Iustin Pop

556 6d2e83d5 Iustin Pop
    """
557 4a89c54a Iustin Pop
    d_map, duplicates = self._UnlockedComputeDRBDMap()
558 4a89c54a Iustin Pop
    if duplicates:
559 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
560 4a89c54a Iustin Pop
                                      str(duplicates))
561 4a89c54a Iustin Pop
    return d_map
562 6d2e83d5 Iustin Pop
563 6d2e83d5 Iustin Pop
  @locking.ssynchronized(_config_lock)
564 a81c53c9 Iustin Pop
  def AllocateDRBDMinor(self, nodes, instance):
565 a81c53c9 Iustin Pop
    """Allocate a drbd minor.
566 a81c53c9 Iustin Pop

567 a81c53c9 Iustin Pop
    The free minor will be automatically computed from the existing
568 a81c53c9 Iustin Pop
    devices. A node can be given multiple times in order to allocate
569 a81c53c9 Iustin Pop
    multiple minors. The result is the list of minors, in the same
570 a81c53c9 Iustin Pop
    order as the passed nodes.
571 a81c53c9 Iustin Pop

572 32388e6d Iustin Pop
    @type instance: string
573 32388e6d Iustin Pop
    @param instance: the instance for which we allocate minors
574 32388e6d Iustin Pop

575 a81c53c9 Iustin Pop
    """
576 32388e6d Iustin Pop
    assert isinstance(instance, basestring), \
577 4a89c54a Iustin Pop
           "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
578 32388e6d Iustin Pop
579 4a89c54a Iustin Pop
    d_map, duplicates = self._UnlockedComputeDRBDMap()
580 4a89c54a Iustin Pop
    if duplicates:
581 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
582 4a89c54a Iustin Pop
                                      str(duplicates))
583 a81c53c9 Iustin Pop
    result = []
584 a81c53c9 Iustin Pop
    for nname in nodes:
585 a81c53c9 Iustin Pop
      ndata = d_map[nname]
586 a81c53c9 Iustin Pop
      if not ndata:
587 a81c53c9 Iustin Pop
        # no minors used, we can start at 0
588 a81c53c9 Iustin Pop
        result.append(0)
589 a81c53c9 Iustin Pop
        ndata[0] = instance
590 d48663e4 Iustin Pop
        self._temporary_drbds[(nname, 0)] = instance
591 a81c53c9 Iustin Pop
        continue
592 a81c53c9 Iustin Pop
      keys = ndata.keys()
593 a81c53c9 Iustin Pop
      keys.sort()
594 a81c53c9 Iustin Pop
      ffree = utils.FirstFree(keys)
595 a81c53c9 Iustin Pop
      if ffree is None:
596 a81c53c9 Iustin Pop
        # return the next minor
597 a81c53c9 Iustin Pop
        # TODO: implement high-limit check
598 a81c53c9 Iustin Pop
        minor = keys[-1] + 1
599 a81c53c9 Iustin Pop
      else:
600 a81c53c9 Iustin Pop
        minor = ffree
601 4a89c54a Iustin Pop
      # double-check minor against current instances
602 4a89c54a Iustin Pop
      assert minor not in d_map[nname], \
603 4a89c54a Iustin Pop
             ("Attempt to reuse allocated DRBD minor %d on node %s,"
604 4a89c54a Iustin Pop
              " already allocated to instance %s" %
605 4a89c54a Iustin Pop
              (minor, nname, d_map[nname][minor]))
606 a81c53c9 Iustin Pop
      ndata[minor] = instance
607 4a89c54a Iustin Pop
      # double-check minor against reservation
608 4a89c54a Iustin Pop
      r_key = (nname, minor)
609 4a89c54a Iustin Pop
      assert r_key not in self._temporary_drbds, \
610 4a89c54a Iustin Pop
             ("Attempt to reuse reserved DRBD minor %d on node %s,"
611 4a89c54a Iustin Pop
              " reserved for instance %s" %
612 4a89c54a Iustin Pop
              (minor, nname, self._temporary_drbds[r_key]))
613 4a89c54a Iustin Pop
      self._temporary_drbds[r_key] = instance
614 4a89c54a Iustin Pop
      result.append(minor)
615 a81c53c9 Iustin Pop
    logging.debug("Request to allocate drbd minors, input: %s, returning %s",
616 a81c53c9 Iustin Pop
                  nodes, result)
617 a81c53c9 Iustin Pop
    return result
618 a81c53c9 Iustin Pop
619 61cf6b5e Iustin Pop
  def _UnlockedReleaseDRBDMinors(self, instance):
620 a81c53c9 Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
621 a81c53c9 Iustin Pop

622 a81c53c9 Iustin Pop
    @type instance: string
623 a81c53c9 Iustin Pop
    @param instance: the instance for which temporary minors should be
624 a81c53c9 Iustin Pop
                     released
625 a81c53c9 Iustin Pop

626 a81c53c9 Iustin Pop
    """
627 32388e6d Iustin Pop
    assert isinstance(instance, basestring), \
628 32388e6d Iustin Pop
           "Invalid argument passed to ReleaseDRBDMinors"
629 a81c53c9 Iustin Pop
    for key, name in self._temporary_drbds.items():
630 a81c53c9 Iustin Pop
      if name == instance:
631 a81c53c9 Iustin Pop
        del self._temporary_drbds[key]
632 a81c53c9 Iustin Pop
633 61cf6b5e Iustin Pop
  @locking.ssynchronized(_config_lock)
634 61cf6b5e Iustin Pop
  def ReleaseDRBDMinors(self, instance):
635 61cf6b5e Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
636 61cf6b5e Iustin Pop

637 61cf6b5e Iustin Pop
    This should be called on the error paths, on the success paths
638 61cf6b5e Iustin Pop
    it's automatically called by the ConfigWriter add and update
639 61cf6b5e Iustin Pop
    functions.
640 61cf6b5e Iustin Pop

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

643 61cf6b5e Iustin Pop
    @type instance: string
644 61cf6b5e Iustin Pop
    @param instance: the instance for which temporary minors should be
645 61cf6b5e Iustin Pop
                     released
646 61cf6b5e Iustin Pop

647 61cf6b5e Iustin Pop
    """
648 61cf6b5e Iustin Pop
    self._UnlockedReleaseDRBDMinors(instance)
649 61cf6b5e Iustin Pop
650 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
651 4a8b186a Michael Hanselmann
  def GetConfigVersion(self):
652 4a8b186a Michael Hanselmann
    """Get the configuration version.
653 4a8b186a Michael Hanselmann

654 4a8b186a Michael Hanselmann
    @return: Config version
655 4a8b186a Michael Hanselmann

656 4a8b186a Michael Hanselmann
    """
657 4a8b186a Michael Hanselmann
    return self._config_data.version
658 4a8b186a Michael Hanselmann
659 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
660 4a8b186a Michael Hanselmann
  def GetClusterName(self):
661 4a8b186a Michael Hanselmann
    """Get cluster name.
662 4a8b186a Michael Hanselmann

663 4a8b186a Michael Hanselmann
    @return: Cluster name
664 4a8b186a Michael Hanselmann

665 4a8b186a Michael Hanselmann
    """
666 4a8b186a Michael Hanselmann
    return self._config_data.cluster.cluster_name
667 4a8b186a Michael Hanselmann
668 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
669 4a8b186a Michael Hanselmann
  def GetMasterNode(self):
670 4a8b186a Michael Hanselmann
    """Get the hostname of the master node for this cluster.
671 4a8b186a Michael Hanselmann

672 4a8b186a Michael Hanselmann
    @return: Master hostname
673 4a8b186a Michael Hanselmann

674 4a8b186a Michael Hanselmann
    """
675 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_node
676 4a8b186a Michael Hanselmann
677 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
678 4a8b186a Michael Hanselmann
  def GetMasterIP(self):
679 4a8b186a Michael Hanselmann
    """Get the IP of the master node for this cluster.
680 4a8b186a Michael Hanselmann

681 4a8b186a Michael Hanselmann
    @return: Master IP
682 4a8b186a Michael Hanselmann

683 4a8b186a Michael Hanselmann
    """
684 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_ip
685 4a8b186a Michael Hanselmann
686 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
687 4a8b186a Michael Hanselmann
  def GetMasterNetdev(self):
688 4a8b186a Michael Hanselmann
    """Get the master network device for this cluster.
689 4a8b186a Michael Hanselmann

690 4a8b186a Michael Hanselmann
    """
691 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_netdev
692 4a8b186a Michael Hanselmann
693 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
694 4a8b186a Michael Hanselmann
  def GetFileStorageDir(self):
695 4a8b186a Michael Hanselmann
    """Get the file storage dir for this cluster.
696 4a8b186a Michael Hanselmann

697 4a8b186a Michael Hanselmann
    """
698 4a8b186a Michael Hanselmann
    return self._config_data.cluster.file_storage_dir
699 4a8b186a Michael Hanselmann
700 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
701 4a8b186a Michael Hanselmann
  def GetHypervisorType(self):
702 4a8b186a Michael Hanselmann
    """Get the hypervisor type for this cluster.
703 4a8b186a Michael Hanselmann

704 4a8b186a Michael Hanselmann
    """
705 066f465d Guido Trotter
    return self._config_data.cluster.enabled_hypervisors[0]
706 4a8b186a Michael Hanselmann
707 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
708 a8083063 Iustin Pop
  def GetHostKey(self):
709 a8083063 Iustin Pop
    """Return the rsa hostkey from the config.
710 a8083063 Iustin Pop

711 c41eea6e Iustin Pop
    @rtype: string
712 c41eea6e Iustin Pop
    @return: the rsa hostkey
713 a8083063 Iustin Pop

714 a8083063 Iustin Pop
    """
715 a8083063 Iustin Pop
    return self._config_data.cluster.rsahostkeypub
716 a8083063 Iustin Pop
717 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
718 a8083063 Iustin Pop
  def AddInstance(self, instance):
719 a8083063 Iustin Pop
    """Add an instance to the config.
720 a8083063 Iustin Pop

721 a8083063 Iustin Pop
    This should be used after creating a new instance.
722 a8083063 Iustin Pop

723 c41eea6e Iustin Pop
    @type instance: L{objects.Instance}
724 c41eea6e Iustin Pop
    @param instance: the instance object
725 c41eea6e Iustin Pop

726 a8083063 Iustin Pop
    """
727 a8083063 Iustin Pop
    if not isinstance(instance, objects.Instance):
728 a8083063 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
729 a8083063 Iustin Pop
730 e00fb268 Iustin Pop
    if instance.disk_template != constants.DT_DISKLESS:
731 e00fb268 Iustin Pop
      all_lvs = instance.MapLVsByNode()
732 74a48621 Iustin Pop
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
733 923b1523 Iustin Pop
734 e4640214 Guido Trotter
    all_macs = self._AllMACs()
735 e4640214 Guido Trotter
    for nic in instance.nics:
736 e4640214 Guido Trotter
      if nic.mac in all_macs:
737 e4640214 Guido Trotter
        raise errors.ConfigurationError("Cannot add instance %s:"
738 430b923c Iustin Pop
                                        " MAC address '%s' already in use." %
739 430b923c Iustin Pop
                                        (instance.name, nic.mac))
740 430b923c Iustin Pop
741 430b923c Iustin Pop
    self._EnsureUUID(instance)
742 e4640214 Guido Trotter
743 b989e85d Iustin Pop
    instance.serial_no = 1
744 d693c864 Iustin Pop
    instance.ctime = instance.mtime = time.time()
745 a8083063 Iustin Pop
    self._config_data.instances[instance.name] = instance
746 81a49123 Iustin Pop
    self._config_data.cluster.serial_no += 1
747 61cf6b5e Iustin Pop
    self._UnlockedReleaseDRBDMinors(instance.name)
748 e7d81ba0 Iustin Pop
    for nic in instance.nics:
749 e7d81ba0 Iustin Pop
      self._temporary_macs.discard(nic.mac)
750 a8083063 Iustin Pop
    self._WriteConfig()
751 a8083063 Iustin Pop
752 430b923c Iustin Pop
  def _EnsureUUID(self, item):
753 430b923c Iustin Pop
    """Ensures a given object has a valid UUID.
754 430b923c Iustin Pop

755 430b923c Iustin Pop
    @param item: the instance or node to be checked
756 430b923c Iustin Pop

757 430b923c Iustin Pop
    """
758 430b923c Iustin Pop
    if not item.uuid:
759 430b923c Iustin Pop
      item.uuid = self._GenerateUniqueID()
760 430b923c Iustin Pop
    elif item.uuid in self._AllIDs(temporary=True):
761 430b923c Iustin Pop
      raise errors.ConfigurationError("Cannot add '%s': UUID already in use" %
762 430b923c Iustin Pop
                                      (item.name, item.uuid))
763 430b923c Iustin Pop
764 6a408fb2 Iustin Pop
  def _SetInstanceStatus(self, instance_name, status):
765 6a408fb2 Iustin Pop
    """Set the instance's status to a given value.
766 a8083063 Iustin Pop

767 a8083063 Iustin Pop
    """
768 0d68c45d Iustin Pop
    assert isinstance(status, bool), \
769 0d68c45d Iustin Pop
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
770 a8083063 Iustin Pop
771 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
772 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" %
773 3ecf6786 Iustin Pop
                                      instance_name)
774 a8083063 Iustin Pop
    instance = self._config_data.instances[instance_name]
775 0d68c45d Iustin Pop
    if instance.admin_up != status:
776 0d68c45d Iustin Pop
      instance.admin_up = status
777 b989e85d Iustin Pop
      instance.serial_no += 1
778 d693c864 Iustin Pop
      instance.mtime = time.time()
779 455a3445 Iustin Pop
      self._WriteConfig()
780 a8083063 Iustin Pop
781 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
782 6a408fb2 Iustin Pop
  def MarkInstanceUp(self, instance_name):
783 6a408fb2 Iustin Pop
    """Mark the instance status to up in the config.
784 6a408fb2 Iustin Pop

785 6a408fb2 Iustin Pop
    """
786 0d68c45d Iustin Pop
    self._SetInstanceStatus(instance_name, True)
787 6a408fb2 Iustin Pop
788 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
789 a8083063 Iustin Pop
  def RemoveInstance(self, instance_name):
790 a8083063 Iustin Pop
    """Remove the instance from the configuration.
791 a8083063 Iustin Pop

792 a8083063 Iustin Pop
    """
793 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
794 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
795 a8083063 Iustin Pop
    del self._config_data.instances[instance_name]
796 81a49123 Iustin Pop
    self._config_data.cluster.serial_no += 1
797 a8083063 Iustin Pop
    self._WriteConfig()
798 a8083063 Iustin Pop
799 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
800 fc95f88f Iustin Pop
  def RenameInstance(self, old_name, new_name):
801 fc95f88f Iustin Pop
    """Rename an instance.
802 fc95f88f Iustin Pop

803 fc95f88f Iustin Pop
    This needs to be done in ConfigWriter and not by RemoveInstance
804 fc95f88f Iustin Pop
    combined with AddInstance as only we can guarantee an atomic
805 fc95f88f Iustin Pop
    rename.
806 fc95f88f Iustin Pop

807 fc95f88f Iustin Pop
    """
808 fc95f88f Iustin Pop
    if old_name not in self._config_data.instances:
809 fc95f88f Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
810 fc95f88f Iustin Pop
    inst = self._config_data.instances[old_name]
811 fc95f88f Iustin Pop
    del self._config_data.instances[old_name]
812 fc95f88f Iustin Pop
    inst.name = new_name
813 b23c4333 Manuel Franceschini
814 b23c4333 Manuel Franceschini
    for disk in inst.disks:
815 b23c4333 Manuel Franceschini
      if disk.dev_type == constants.LD_FILE:
816 b23c4333 Manuel Franceschini
        # rename the file paths in logical and physical id
817 b23c4333 Manuel Franceschini
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
818 b23c4333 Manuel Franceschini
        disk.physical_id = disk.logical_id = (disk.logical_id[0],
819 b23c4333 Manuel Franceschini
                                              os.path.join(file_storage_dir,
820 b23c4333 Manuel Franceschini
                                                           inst.name,
821 b23c4333 Manuel Franceschini
                                                           disk.iv_name))
822 b23c4333 Manuel Franceschini
823 fc95f88f Iustin Pop
    self._config_data.instances[inst.name] = inst
824 fc95f88f Iustin Pop
    self._WriteConfig()
825 fc95f88f Iustin Pop
826 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
827 a8083063 Iustin Pop
  def MarkInstanceDown(self, instance_name):
828 a8083063 Iustin Pop
    """Mark the status of an instance to down in the configuration.
829 a8083063 Iustin Pop

830 a8083063 Iustin Pop
    """
831 0d68c45d Iustin Pop
    self._SetInstanceStatus(instance_name, False)
832 a8083063 Iustin Pop
833 94bbfece Iustin Pop
  def _UnlockedGetInstanceList(self):
834 94bbfece Iustin Pop
    """Get the list of instances.
835 94bbfece Iustin Pop

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

838 94bbfece Iustin Pop
    """
839 94bbfece Iustin Pop
    return self._config_data.instances.keys()
840 94bbfece Iustin Pop
841 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
842 a8083063 Iustin Pop
  def GetInstanceList(self):
843 a8083063 Iustin Pop
    """Get the list of instances.
844 a8083063 Iustin Pop

845 c41eea6e Iustin Pop
    @return: array of instances, ex. ['instance2.example.com',
846 c41eea6e Iustin Pop
        'instance1.example.com']
847 a8083063 Iustin Pop

848 a8083063 Iustin Pop
    """
849 94bbfece Iustin Pop
    return self._UnlockedGetInstanceList()
850 a8083063 Iustin Pop
851 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
852 a8083063 Iustin Pop
  def ExpandInstanceName(self, short_name):
853 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
854 a8083063 Iustin Pop

855 a8083063 Iustin Pop
    """
856 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
857 a8083063 Iustin Pop
                                    self._config_data.instances.keys())
858 a8083063 Iustin Pop
859 94bbfece Iustin Pop
  def _UnlockedGetInstanceInfo(self, instance_name):
860 5bbd3f7f Michael Hanselmann
    """Returns information about an instance.
861 94bbfece Iustin Pop

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

864 94bbfece Iustin Pop
    """
865 94bbfece Iustin Pop
    if instance_name not in self._config_data.instances:
866 94bbfece Iustin Pop
      return None
867 94bbfece Iustin Pop
868 94bbfece Iustin Pop
    return self._config_data.instances[instance_name]
869 94bbfece Iustin Pop
870 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
871 a8083063 Iustin Pop
  def GetInstanceInfo(self, instance_name):
872 5bbd3f7f Michael Hanselmann
    """Returns information about an instance.
873 a8083063 Iustin Pop

874 5bbd3f7f Michael Hanselmann
    It takes the information from the configuration file. Other information of
875 a8083063 Iustin Pop
    an instance are taken from the live systems.
876 a8083063 Iustin Pop

877 c41eea6e Iustin Pop
    @param instance_name: name of the instance, e.g.
878 c41eea6e Iustin Pop
        I{instance1.example.com}
879 a8083063 Iustin Pop

880 c41eea6e Iustin Pop
    @rtype: L{objects.Instance}
881 c41eea6e Iustin Pop
    @return: the instance object
882 a8083063 Iustin Pop

883 a8083063 Iustin Pop
    """
884 94bbfece Iustin Pop
    return self._UnlockedGetInstanceInfo(instance_name)
885 a8083063 Iustin Pop
886 0b2de758 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
887 0b2de758 Iustin Pop
  def GetAllInstancesInfo(self):
888 0b2de758 Iustin Pop
    """Get the configuration of all instances.
889 0b2de758 Iustin Pop

890 0b2de758 Iustin Pop
    @rtype: dict
891 5fcc718f Iustin Pop
    @return: dict of (instance, instance_info), where instance_info is what
892 0b2de758 Iustin Pop
              would GetInstanceInfo return for the node
893 0b2de758 Iustin Pop

894 0b2de758 Iustin Pop
    """
895 64d3bd52 Guido Trotter
    my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
896 64d3bd52 Guido Trotter
                    for instance in self._UnlockedGetInstanceList()])
897 0b2de758 Iustin Pop
    return my_dict
898 0b2de758 Iustin Pop
899 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
900 a8083063 Iustin Pop
  def AddNode(self, node):
901 a8083063 Iustin Pop
    """Add a node to the configuration.
902 a8083063 Iustin Pop

903 c41eea6e Iustin Pop
    @type node: L{objects.Node}
904 c41eea6e Iustin Pop
    @param node: a Node instance
905 a8083063 Iustin Pop

906 a8083063 Iustin Pop
    """
907 d8470559 Michael Hanselmann
    logging.info("Adding node %s to configuration" % node.name)
908 d8470559 Michael Hanselmann
909 430b923c Iustin Pop
    self._EnsureUUID(node)
910 430b923c Iustin Pop
911 b989e85d Iustin Pop
    node.serial_no = 1
912 d693c864 Iustin Pop
    node.ctime = node.mtime = time.time()
913 a8083063 Iustin Pop
    self._config_data.nodes[node.name] = node
914 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
915 a8083063 Iustin Pop
    self._WriteConfig()
916 a8083063 Iustin Pop
917 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
918 a8083063 Iustin Pop
  def RemoveNode(self, node_name):
919 a8083063 Iustin Pop
    """Remove a node from the configuration.
920 a8083063 Iustin Pop

921 a8083063 Iustin Pop
    """
922 d8470559 Michael Hanselmann
    logging.info("Removing node %s from configuration" % node_name)
923 d8470559 Michael Hanselmann
924 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
925 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
926 a8083063 Iustin Pop
927 a8083063 Iustin Pop
    del self._config_data.nodes[node_name]
928 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
929 a8083063 Iustin Pop
    self._WriteConfig()
930 a8083063 Iustin Pop
931 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
932 a8083063 Iustin Pop
  def ExpandNodeName(self, short_name):
933 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
934 a8083063 Iustin Pop

935 a8083063 Iustin Pop
    """
936 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
937 a8083063 Iustin Pop
                                    self._config_data.nodes.keys())
938 a8083063 Iustin Pop
939 f78ede4e Guido Trotter
  def _UnlockedGetNodeInfo(self, node_name):
940 a8083063 Iustin Pop
    """Get the configuration of a node, as stored in the config.
941 a8083063 Iustin Pop

942 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
943 c41eea6e Iustin Pop
    held.
944 f78ede4e Guido Trotter

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

947 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
948 c41eea6e Iustin Pop
    @return: the node object
949 a8083063 Iustin Pop

950 a8083063 Iustin Pop
    """
951 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
952 a8083063 Iustin Pop
      return None
953 a8083063 Iustin Pop
954 a8083063 Iustin Pop
    return self._config_data.nodes[node_name]
955 a8083063 Iustin Pop
956 f78ede4e Guido Trotter
957 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
958 f78ede4e Guido Trotter
  def GetNodeInfo(self, node_name):
959 f78ede4e Guido Trotter
    """Get the configuration of a node, as stored in the config.
960 f78ede4e Guido Trotter

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

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

965 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
966 c41eea6e Iustin Pop
    @return: the node object
967 f78ede4e Guido Trotter

968 f78ede4e Guido Trotter
    """
969 f78ede4e Guido Trotter
    return self._UnlockedGetNodeInfo(node_name)
970 f78ede4e Guido Trotter
971 f78ede4e Guido Trotter
  def _UnlockedGetNodeList(self):
972 a8083063 Iustin Pop
    """Return the list of nodes which are in the configuration.
973 a8083063 Iustin Pop

974 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
975 c41eea6e Iustin Pop
    held.
976 c41eea6e Iustin Pop

977 c41eea6e Iustin Pop
    @rtype: list
978 f78ede4e Guido Trotter

979 a8083063 Iustin Pop
    """
980 a8083063 Iustin Pop
    return self._config_data.nodes.keys()
981 a8083063 Iustin Pop
982 f78ede4e Guido Trotter
983 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
984 f78ede4e Guido Trotter
  def GetNodeList(self):
985 f78ede4e Guido Trotter
    """Return the list of nodes which are in the configuration.
986 f78ede4e Guido Trotter

987 f78ede4e Guido Trotter
    """
988 f78ede4e Guido Trotter
    return self._UnlockedGetNodeList()
989 f78ede4e Guido Trotter
990 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
991 94a02bb5 Iustin Pop
  def GetOnlineNodeList(self):
992 94a02bb5 Iustin Pop
    """Return the list of nodes which are online.
993 94a02bb5 Iustin Pop

994 94a02bb5 Iustin Pop
    """
995 94a02bb5 Iustin Pop
    all_nodes = [self._UnlockedGetNodeInfo(node)
996 94a02bb5 Iustin Pop
                 for node in self._UnlockedGetNodeList()]
997 94a02bb5 Iustin Pop
    return [node.name for node in all_nodes if not node.offline]
998 94a02bb5 Iustin Pop
999 94a02bb5 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
1000 d65e5776 Iustin Pop
  def GetAllNodesInfo(self):
1001 d65e5776 Iustin Pop
    """Get the configuration of all nodes.
1002 d65e5776 Iustin Pop

1003 d65e5776 Iustin Pop
    @rtype: dict
1004 ec0292f1 Iustin Pop
    @return: dict of (node, node_info), where node_info is what
1005 d65e5776 Iustin Pop
              would GetNodeInfo return for the node
1006 d65e5776 Iustin Pop

1007 d65e5776 Iustin Pop
    """
1008 d65e5776 Iustin Pop
    my_dict = dict([(node, self._UnlockedGetNodeInfo(node))
1009 d65e5776 Iustin Pop
                    for node in self._UnlockedGetNodeList()])
1010 d65e5776 Iustin Pop
    return my_dict
1011 d65e5776 Iustin Pop
1012 23f06b2b Iustin Pop
  def _UnlockedGetMasterCandidateStats(self, exceptions=None):
1013 ec0292f1 Iustin Pop
    """Get the number of current and maximum desired and possible candidates.
1014 ec0292f1 Iustin Pop

1015 23f06b2b Iustin Pop
    @type exceptions: list
1016 23f06b2b Iustin Pop
    @param exceptions: if passed, list of nodes that should be ignored
1017 ec0292f1 Iustin Pop
    @rtype: tuple
1018 ec0292f1 Iustin Pop
    @return: tuple of (current, desired and possible)
1019 ec0292f1 Iustin Pop

1020 ec0292f1 Iustin Pop
    """
1021 ec0292f1 Iustin Pop
    mc_now = mc_max = 0
1022 23f06b2b Iustin Pop
    for node in self._config_data.nodes.values():
1023 23f06b2b Iustin Pop
      if exceptions and node.name in exceptions:
1024 23f06b2b Iustin Pop
        continue
1025 5bf07049 Iustin Pop
      if not (node.offline or node.drained):
1026 ec0292f1 Iustin Pop
        mc_max += 1
1027 ec0292f1 Iustin Pop
      if node.master_candidate:
1028 ec0292f1 Iustin Pop
        mc_now += 1
1029 ec0292f1 Iustin Pop
    mc_max = min(mc_max, self._config_data.cluster.candidate_pool_size)
1030 ec0292f1 Iustin Pop
    return (mc_now, mc_max)
1031 ec0292f1 Iustin Pop
1032 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
1033 23f06b2b Iustin Pop
  def GetMasterCandidateStats(self, exceptions=None):
1034 ec0292f1 Iustin Pop
    """Get the number of current and maximum possible candidates.
1035 ec0292f1 Iustin Pop

1036 ec0292f1 Iustin Pop
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
1037 ec0292f1 Iustin Pop

1038 23f06b2b Iustin Pop
    @type exceptions: list
1039 23f06b2b Iustin Pop
    @param exceptions: if passed, list of nodes that should be ignored
1040 ec0292f1 Iustin Pop
    @rtype: tuple
1041 ec0292f1 Iustin Pop
    @return: tuple of (current, max)
1042 ec0292f1 Iustin Pop

1043 ec0292f1 Iustin Pop
    """
1044 23f06b2b Iustin Pop
    return self._UnlockedGetMasterCandidateStats(exceptions)
1045 ec0292f1 Iustin Pop
1046 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock)
1047 ec0292f1 Iustin Pop
  def MaintainCandidatePool(self):
1048 ec0292f1 Iustin Pop
    """Try to grow the candidate pool to the desired size.
1049 ec0292f1 Iustin Pop

1050 ec0292f1 Iustin Pop
    @rtype: list
1051 ee513a66 Iustin Pop
    @return: list with the adjusted nodes (L{objects.Node} instances)
1052 ec0292f1 Iustin Pop

1053 ec0292f1 Iustin Pop
    """
1054 ec0292f1 Iustin Pop
    mc_now, mc_max = self._UnlockedGetMasterCandidateStats()
1055 ec0292f1 Iustin Pop
    mod_list = []
1056 ec0292f1 Iustin Pop
    if mc_now < mc_max:
1057 ec0292f1 Iustin Pop
      node_list = self._config_data.nodes.keys()
1058 ec0292f1 Iustin Pop
      random.shuffle(node_list)
1059 ec0292f1 Iustin Pop
      for name in node_list:
1060 ec0292f1 Iustin Pop
        if mc_now >= mc_max:
1061 ec0292f1 Iustin Pop
          break
1062 ec0292f1 Iustin Pop
        node = self._config_data.nodes[name]
1063 5bf07049 Iustin Pop
        if node.master_candidate or node.offline or node.drained:
1064 ec0292f1 Iustin Pop
          continue
1065 ee513a66 Iustin Pop
        mod_list.append(node)
1066 ec0292f1 Iustin Pop
        node.master_candidate = True
1067 ec0292f1 Iustin Pop
        node.serial_no += 1
1068 ec0292f1 Iustin Pop
        mc_now += 1
1069 ec0292f1 Iustin Pop
      if mc_now != mc_max:
1070 ec0292f1 Iustin Pop
        # this should not happen
1071 ec0292f1 Iustin Pop
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
1072 ec0292f1 Iustin Pop
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
1073 ec0292f1 Iustin Pop
      if mod_list:
1074 ec0292f1 Iustin Pop
        self._config_data.cluster.serial_no += 1
1075 ec0292f1 Iustin Pop
        self._WriteConfig()
1076 ec0292f1 Iustin Pop
1077 ec0292f1 Iustin Pop
    return mod_list
1078 ec0292f1 Iustin Pop
1079 a8083063 Iustin Pop
  def _BumpSerialNo(self):
1080 a8083063 Iustin Pop
    """Bump up the serial number of the config.
1081 a8083063 Iustin Pop

1082 a8083063 Iustin Pop
    """
1083 9d38c6e1 Iustin Pop
    self._config_data.serial_no += 1
1084 d693c864 Iustin Pop
    self._config_data.mtime = time.time()
1085 a8083063 Iustin Pop
1086 76d5d3a3 Iustin Pop
  def _AllUUIDObjects(self):
1087 76d5d3a3 Iustin Pop
    """Returns all objects with uuid attributes.
1088 76d5d3a3 Iustin Pop

1089 76d5d3a3 Iustin Pop
    """
1090 76d5d3a3 Iustin Pop
    return (self._config_data.instances.values() +
1091 76d5d3a3 Iustin Pop
            self._config_data.nodes.values() +
1092 76d5d3a3 Iustin Pop
            [self._config_data.cluster])
1093 76d5d3a3 Iustin Pop
1094 a8083063 Iustin Pop
  def _OpenConfig(self):
1095 a8083063 Iustin Pop
    """Read the config data from disk.
1096 a8083063 Iustin Pop

1097 a8083063 Iustin Pop
    """
1098 13998ef2 Michael Hanselmann
    raw_data = utils.ReadFile(self._cfg_file)
1099 13998ef2 Michael Hanselmann
1100 a8083063 Iustin Pop
    try:
1101 13998ef2 Michael Hanselmann
      data = objects.ConfigData.FromDict(serializer.Load(raw_data))
1102 13998ef2 Michael Hanselmann
    except Exception, err:
1103 13998ef2 Michael Hanselmann
      raise errors.ConfigurationError(err)
1104 5b263ed7 Michael Hanselmann
1105 5b263ed7 Michael Hanselmann
    # Make sure the configuration has the right version
1106 5b263ed7 Michael Hanselmann
    _ValidateConfig(data)
1107 5b263ed7 Michael Hanselmann
1108 a8083063 Iustin Pop
    if (not hasattr(data, 'cluster') or
1109 243cdbcc Michael Hanselmann
        not hasattr(data.cluster, 'rsahostkeypub')):
1110 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Incomplete configuration"
1111 243cdbcc Michael Hanselmann
                                      " (missing cluster.rsahostkeypub)")
1112 90d726a8 Iustin Pop
1113 90d726a8 Iustin Pop
    # Upgrade configuration if needed
1114 90d726a8 Iustin Pop
    data.UpgradeConfig()
1115 90d726a8 Iustin Pop
1116 a8083063 Iustin Pop
    self._config_data = data
1117 3c7f6c44 Iustin Pop
    # reset the last serial as -1 so that the next write will cause
1118 0779e3aa Iustin Pop
    # ssconf update
1119 0779e3aa Iustin Pop
    self._last_cluster_serial = -1
1120 a8083063 Iustin Pop
1121 76d5d3a3 Iustin Pop
    # And finally run our (custom) config upgrade sequence
1122 76d5d3a3 Iustin Pop
    self._UpgradeConfig()
1123 76d5d3a3 Iustin Pop
1124 76d5d3a3 Iustin Pop
  def _UpgradeConfig(self):
1125 76d5d3a3 Iustin Pop
    """Run upgrade steps that cannot be done purely in the objects.
1126 76d5d3a3 Iustin Pop

1127 76d5d3a3 Iustin Pop
    This is because some data elements need uniqueness across the
1128 76d5d3a3 Iustin Pop
    whole configuration, etc.
1129 76d5d3a3 Iustin Pop

1130 76d5d3a3 Iustin Pop
    @warning: this function will call L{_WriteConfig()}, so it needs
1131 76d5d3a3 Iustin Pop
        to either be called with the lock held or from a safe place
1132 76d5d3a3 Iustin Pop
        (the constructor)
1133 76d5d3a3 Iustin Pop

1134 76d5d3a3 Iustin Pop
    """
1135 76d5d3a3 Iustin Pop
    modified = False
1136 76d5d3a3 Iustin Pop
    for item in self._AllUUIDObjects():
1137 76d5d3a3 Iustin Pop
      if item.uuid is None:
1138 430b923c Iustin Pop
        item.uuid = self._GenerateUniqueID()
1139 76d5d3a3 Iustin Pop
        modified = True
1140 76d5d3a3 Iustin Pop
    if modified:
1141 76d5d3a3 Iustin Pop
      self._WriteConfig()
1142 76d5d3a3 Iustin Pop
1143 a8083063 Iustin Pop
  def _DistributeConfig(self):
1144 a8083063 Iustin Pop
    """Distribute the configuration to the other nodes.
1145 a8083063 Iustin Pop

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

1149 a8083063 Iustin Pop
    """
1150 a8083063 Iustin Pop
    if self._offline:
1151 a8083063 Iustin Pop
      return True
1152 a8083063 Iustin Pop
    bad = False
1153 a8083063 Iustin Pop
1154 6a5b8b4b Iustin Pop
    node_list = []
1155 6a5b8b4b Iustin Pop
    addr_list = []
1156 6a5b8b4b Iustin Pop
    myhostname = self._my_hostname
1157 6b294c53 Iustin Pop
    # we can skip checking whether _UnlockedGetNodeInfo returns None
1158 6b294c53 Iustin Pop
    # since the node list comes from _UnlocketGetNodeList, and we are
1159 6b294c53 Iustin Pop
    # called with the lock held, so no modifications should take place
1160 6b294c53 Iustin Pop
    # in between
1161 6a5b8b4b Iustin Pop
    for node_name in self._UnlockedGetNodeList():
1162 6a5b8b4b Iustin Pop
      if node_name == myhostname:
1163 6a5b8b4b Iustin Pop
        continue
1164 6a5b8b4b Iustin Pop
      node_info = self._UnlockedGetNodeInfo(node_name)
1165 6a5b8b4b Iustin Pop
      if not node_info.master_candidate:
1166 6a5b8b4b Iustin Pop
        continue
1167 6a5b8b4b Iustin Pop
      node_list.append(node_info.name)
1168 6a5b8b4b Iustin Pop
      addr_list.append(node_info.primary_ip)
1169 6b294c53 Iustin Pop
1170 6a5b8b4b Iustin Pop
    result = rpc.RpcRunner.call_upload_file(node_list, self._cfg_file,
1171 6a5b8b4b Iustin Pop
                                            address_list=addr_list)
1172 1b54fc6c Guido Trotter
    for to_node, to_result in result.items():
1173 3cebe102 Michael Hanselmann
      msg = to_result.fail_msg
1174 1b54fc6c Guido Trotter
      if msg:
1175 1b54fc6c Guido Trotter
        msg = ("Copy of file %s to node %s failed: %s" %
1176 dd7db360 Iustin Pop
               (self._cfg_file, to_node, msg))
1177 1b54fc6c Guido Trotter
        logging.error(msg)
1178 a8083063 Iustin Pop
        bad = True
1179 a8083063 Iustin Pop
    return not bad
1180 a8083063 Iustin Pop
1181 a8083063 Iustin Pop
  def _WriteConfig(self, destination=None):
1182 a8083063 Iustin Pop
    """Write the configuration data to persistent storage.
1183 a8083063 Iustin Pop

1184 a8083063 Iustin Pop
    """
1185 34d657ba Iustin Pop
    # first, cleanup the _temporary_ids set, if an ID is now in the
1186 34d657ba Iustin Pop
    # other objects it should be discarded to prevent unbounded growth
1187 34d657ba Iustin Pop
    # of that structure
1188 34d657ba Iustin Pop
    self._CleanupTemporaryIDs()
1189 4a89c54a Iustin Pop
    config_errors = self._UnlockedVerifyConfig()
1190 4a89c54a Iustin Pop
    if config_errors:
1191 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Configuration data is not"
1192 4a89c54a Iustin Pop
                                      " consistent: %s" %
1193 4a89c54a Iustin Pop
                                      (", ".join(config_errors)))
1194 a8083063 Iustin Pop
    if destination is None:
1195 a8083063 Iustin Pop
      destination = self._cfg_file
1196 a8083063 Iustin Pop
    self._BumpSerialNo()
1197 8d14b30d Iustin Pop
    txt = serializer.Dump(self._config_data.ToDict())
1198 13998ef2 Michael Hanselmann
1199 13998ef2 Michael Hanselmann
    utils.WriteFile(destination, data=txt)
1200 13998ef2 Michael Hanselmann
1201 14e15659 Iustin Pop
    self.write_count += 1
1202 3d3a04bc Iustin Pop
1203 f56618e0 Iustin Pop
    # and redistribute the config file to master candidates
1204 a8083063 Iustin Pop
    self._DistributeConfig()
1205 a8083063 Iustin Pop
1206 54d1a06e Michael Hanselmann
    # Write ssconf files on all nodes (including locally)
1207 0779e3aa Iustin Pop
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
1208 d9a855f1 Michael Hanselmann
      if not self._offline:
1209 e1e75d00 Iustin Pop
        result = rpc.RpcRunner.call_write_ssconf_files(\
1210 e1e75d00 Iustin Pop
          self._UnlockedGetNodeList(),
1211 e1e75d00 Iustin Pop
          self._UnlockedGetSsconfValues())
1212 e1e75d00 Iustin Pop
        for nname, nresu in result.items():
1213 3cebe102 Michael Hanselmann
          msg = nresu.fail_msg
1214 e1e75d00 Iustin Pop
          if msg:
1215 e1e75d00 Iustin Pop
            logging.warning("Error while uploading ssconf files to"
1216 e1e75d00 Iustin Pop
                            " node %s: %s", nname, msg)
1217 0779e3aa Iustin Pop
      self._last_cluster_serial = self._config_data.cluster.serial_no
1218 54d1a06e Michael Hanselmann
1219 03d1dba2 Michael Hanselmann
  def _UnlockedGetSsconfValues(self):
1220 054596f0 Iustin Pop
    """Return the values needed by ssconf.
1221 054596f0 Iustin Pop

1222 054596f0 Iustin Pop
    @rtype: dict
1223 054596f0 Iustin Pop
    @return: a dictionary with keys the ssconf names and values their
1224 054596f0 Iustin Pop
        associated value
1225 054596f0 Iustin Pop

1226 054596f0 Iustin Pop
    """
1227 a3316e4a Iustin Pop
    fn = "\n".join
1228 81a49123 Iustin Pop
    instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
1229 a3316e4a Iustin Pop
    node_names = utils.NiceSort(self._UnlockedGetNodeList())
1230 a3316e4a Iustin Pop
    node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
1231 c3029d0a Luca Bigliardi
    node_pri_ips = ["%s %s" % (ninfo.name, ninfo.primary_ip)
1232 5909fb97 Luca Bigliardi
                    for ninfo in node_info]
1233 c3029d0a Luca Bigliardi
    node_snd_ips = ["%s %s" % (ninfo.name, ninfo.secondary_ip)
1234 5909fb97 Luca Bigliardi
                    for ninfo in node_info]
1235 a3316e4a Iustin Pop
1236 81a49123 Iustin Pop
    instance_data = fn(instance_names)
1237 a3316e4a Iustin Pop
    off_data = fn(node.name for node in node_info if node.offline)
1238 81a49123 Iustin Pop
    on_data = fn(node.name for node in node_info if not node.offline)
1239 a3316e4a Iustin Pop
    mc_data = fn(node.name for node in node_info if node.master_candidate)
1240 8113a52e Luca Bigliardi
    mc_ips_data = fn(node.primary_ip for node in node_info
1241 8113a52e Luca Bigliardi
                     if node.master_candidate)
1242 a3316e4a Iustin Pop
    node_data = fn(node_names)
1243 f9780ccd Luca Bigliardi
    node_pri_ips_data = fn(node_pri_ips)
1244 f9780ccd Luca Bigliardi
    node_snd_ips_data = fn(node_snd_ips)
1245 f56618e0 Iustin Pop
1246 054596f0 Iustin Pop
    cluster = self._config_data.cluster
1247 5d60b3bd Iustin Pop
    cluster_tags = fn(cluster.GetTags())
1248 03d1dba2 Michael Hanselmann
    return {
1249 054596f0 Iustin Pop
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
1250 5d60b3bd Iustin Pop
      constants.SS_CLUSTER_TAGS: cluster_tags,
1251 054596f0 Iustin Pop
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
1252 a3316e4a Iustin Pop
      constants.SS_MASTER_CANDIDATES: mc_data,
1253 8113a52e Luca Bigliardi
      constants.SS_MASTER_CANDIDATES_IPS: mc_ips_data,
1254 054596f0 Iustin Pop
      constants.SS_MASTER_IP: cluster.master_ip,
1255 054596f0 Iustin Pop
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
1256 054596f0 Iustin Pop
      constants.SS_MASTER_NODE: cluster.master_node,
1257 a3316e4a Iustin Pop
      constants.SS_NODE_LIST: node_data,
1258 f9780ccd Luca Bigliardi
      constants.SS_NODE_PRIMARY_IPS: node_pri_ips_data,
1259 f9780ccd Luca Bigliardi
      constants.SS_NODE_SECONDARY_IPS: node_snd_ips_data,
1260 a3316e4a Iustin Pop
      constants.SS_OFFLINE_NODES: off_data,
1261 81a49123 Iustin Pop
      constants.SS_ONLINE_NODES: on_data,
1262 81a49123 Iustin Pop
      constants.SS_INSTANCE_LIST: instance_data,
1263 8a113c7a Iustin Pop
      constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
1264 03d1dba2 Michael Hanselmann
      }
1265 03d1dba2 Michael Hanselmann
1266 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1267 a8083063 Iustin Pop
  def GetVGName(self):
1268 a8083063 Iustin Pop
    """Return the volume group name.
1269 a8083063 Iustin Pop

1270 a8083063 Iustin Pop
    """
1271 a8083063 Iustin Pop
    return self._config_data.cluster.volume_group_name
1272 a8083063 Iustin Pop
1273 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1274 89ff8e15 Manuel Franceschini
  def SetVGName(self, vg_name):
1275 89ff8e15 Manuel Franceschini
    """Set the volume group name.
1276 89ff8e15 Manuel Franceschini

1277 89ff8e15 Manuel Franceschini
    """
1278 2d4011cd Manuel Franceschini
    self._config_data.cluster.volume_group_name = vg_name
1279 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
1280 89ff8e15 Manuel Franceschini
    self._WriteConfig()
1281 89ff8e15 Manuel Franceschini
1282 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1283 a8083063 Iustin Pop
  def GetMACPrefix(self):
1284 a8083063 Iustin Pop
    """Return the mac prefix.
1285 a8083063 Iustin Pop

1286 a8083063 Iustin Pop
    """
1287 a8083063 Iustin Pop
    return self._config_data.cluster.mac_prefix
1288 62779dd0 Iustin Pop
1289 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1290 62779dd0 Iustin Pop
  def GetClusterInfo(self):
1291 5bbd3f7f Michael Hanselmann
    """Returns information about the cluster
1292 62779dd0 Iustin Pop

1293 c41eea6e Iustin Pop
    @rtype: L{objects.Cluster}
1294 c41eea6e Iustin Pop
    @return: the cluster object
1295 62779dd0 Iustin Pop

1296 62779dd0 Iustin Pop
    """
1297 62779dd0 Iustin Pop
    return self._config_data.cluster
1298 e00fb268 Iustin Pop
1299 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1300 e00fb268 Iustin Pop
  def Update(self, target):
1301 e00fb268 Iustin Pop
    """Notify function to be called after updates.
1302 e00fb268 Iustin Pop

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

1309 c41eea6e Iustin Pop
    @param target: an instance of either L{objects.Cluster},
1310 c41eea6e Iustin Pop
        L{objects.Node} or L{objects.Instance} which is existing in
1311 c41eea6e Iustin Pop
        the cluster
1312 c41eea6e Iustin Pop

1313 e00fb268 Iustin Pop
    """
1314 e00fb268 Iustin Pop
    if self._config_data is None:
1315 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Configuration file not read,"
1316 3ecf6786 Iustin Pop
                                   " cannot save.")
1317 f34901f8 Iustin Pop
    update_serial = False
1318 e00fb268 Iustin Pop
    if isinstance(target, objects.Cluster):
1319 e00fb268 Iustin Pop
      test = target == self._config_data.cluster
1320 e00fb268 Iustin Pop
    elif isinstance(target, objects.Node):
1321 e00fb268 Iustin Pop
      test = target in self._config_data.nodes.values()
1322 f34901f8 Iustin Pop
      update_serial = True
1323 e00fb268 Iustin Pop
    elif isinstance(target, objects.Instance):
1324 e00fb268 Iustin Pop
      test = target in self._config_data.instances.values()
1325 e00fb268 Iustin Pop
    else:
1326 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
1327 3ecf6786 Iustin Pop
                                   " ConfigWriter.Update" % type(target))
1328 e00fb268 Iustin Pop
    if not test:
1329 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Configuration updated since object"
1330 3ecf6786 Iustin Pop
                                      " has been read or unknown object")
1331 f34901f8 Iustin Pop
    target.serial_no += 1
1332 d693c864 Iustin Pop
    target.mtime = now = time.time()
1333 f34901f8 Iustin Pop
1334 cff4c037 Iustin Pop
    if update_serial:
1335 f34901f8 Iustin Pop
      # for node updates, we need to increase the cluster serial too
1336 f34901f8 Iustin Pop
      self._config_data.cluster.serial_no += 1
1337 d693c864 Iustin Pop
      self._config_data.cluster.mtime = now
1338 b989e85d Iustin Pop
1339 61cf6b5e Iustin Pop
    if isinstance(target, objects.Instance):
1340 61cf6b5e Iustin Pop
      self._UnlockedReleaseDRBDMinors(target.name)
1341 e7d81ba0 Iustin Pop
      for nic in target.nics:
1342 e7d81ba0 Iustin Pop
        self._temporary_macs.discard(nic.mac)
1343 61cf6b5e Iustin Pop
1344 e00fb268 Iustin Pop
    self._WriteConfig()