Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ f95c81bf

History | View | Annotate | Download (40.9 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 a8083063 Iustin Pop
39 a8083063 Iustin Pop
from ganeti import errors
40 f78ede4e Guido Trotter
from ganeti import locking
41 a8083063 Iustin Pop
from ganeti import utils
42 a8083063 Iustin Pop
from ganeti import constants
43 a8083063 Iustin Pop
from ganeti import rpc
44 a8083063 Iustin Pop
from ganeti import objects
45 8d14b30d Iustin Pop
from ganeti import serializer
46 243cdbcc Michael Hanselmann
47 243cdbcc Michael Hanselmann
48 f78ede4e Guido Trotter
_config_lock = locking.SharedLock()
49 f78ede4e Guido Trotter
50 f78ede4e Guido Trotter
51 5b263ed7 Michael Hanselmann
def _ValidateConfig(data):
52 c41eea6e Iustin Pop
  """Verifies that a configuration objects looks valid.
53 c41eea6e Iustin Pop

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

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

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

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

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

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

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

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

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

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

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

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

165 26b316d0 Iustin Pop
    @type include_temporary: boolean
166 26b316d0 Iustin Pop
    @param include_temporary: whether to include the _temporary_ids set
167 26b316d0 Iustin Pop
    @rtype: set
168 26b316d0 Iustin Pop
    @return: a set of IDs
169 26b316d0 Iustin Pop

170 26b316d0 Iustin Pop
    """
171 26b316d0 Iustin Pop
    existing = set()
172 26b316d0 Iustin Pop
    if include_temporary:
173 26b316d0 Iustin Pop
      existing.update(self._temporary_ids)
174 26b316d0 Iustin Pop
    existing.update(self._AllLVs())
175 26b316d0 Iustin Pop
    existing.update(self._config_data.instances.keys())
176 26b316d0 Iustin Pop
    existing.update(self._config_data.nodes.keys())
177 26b316d0 Iustin Pop
    return existing
178 26b316d0 Iustin Pop
179 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
180 923b1523 Iustin Pop
  def GenerateUniqueID(self, exceptions=None):
181 923b1523 Iustin Pop
    """Generate an unique disk name.
182 923b1523 Iustin Pop

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

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

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

194 923b1523 Iustin Pop
    """
195 26b316d0 Iustin Pop
    existing = self._AllIDs(include_temporary=True)
196 923b1523 Iustin Pop
    if exceptions is not None:
197 923b1523 Iustin Pop
      existing.update(exceptions)
198 923b1523 Iustin Pop
    retries = 64
199 923b1523 Iustin Pop
    while retries > 0:
200 24818e8f Michael Hanselmann
      unique_id = utils.NewUUID()
201 923b1523 Iustin Pop
      if unique_id not in existing and unique_id is not None:
202 923b1523 Iustin Pop
        break
203 923b1523 Iustin Pop
    else:
204 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Not able generate an unique ID"
205 3ecf6786 Iustin Pop
                                      " (last tried ID: %s" % unique_id)
206 923b1523 Iustin Pop
    self._temporary_ids.add(unique_id)
207 923b1523 Iustin Pop
    return unique_id
208 923b1523 Iustin Pop
209 8db31d6b Iustin Pop
  def _CleanupTemporaryIDs(self):
210 8db31d6b Iustin Pop
    """Cleanups the _temporary_ids structure.
211 8db31d6b Iustin Pop

212 8db31d6b Iustin Pop
    """
213 8db31d6b Iustin Pop
    existing = self._AllIDs(include_temporary=False)
214 8db31d6b Iustin Pop
    self._temporary_ids = self._temporary_ids - existing
215 8db31d6b Iustin Pop
216 a8083063 Iustin Pop
  def _AllMACs(self):
217 a8083063 Iustin Pop
    """Return all MACs present in the config.
218 a8083063 Iustin Pop

219 c41eea6e Iustin Pop
    @rtype: list
220 c41eea6e Iustin Pop
    @return: the list of all MACs
221 c41eea6e Iustin Pop

222 a8083063 Iustin Pop
    """
223 a8083063 Iustin Pop
    result = []
224 a8083063 Iustin Pop
    for instance in self._config_data.instances.values():
225 a8083063 Iustin Pop
      for nic in instance.nics:
226 a8083063 Iustin Pop
        result.append(nic.mac)
227 a8083063 Iustin Pop
228 a8083063 Iustin Pop
    return result
229 a8083063 Iustin Pop
230 f9518d38 Iustin Pop
  def _AllDRBDSecrets(self):
231 f9518d38 Iustin Pop
    """Return all DRBD secrets present in the config.
232 f9518d38 Iustin Pop

233 c41eea6e Iustin Pop
    @rtype: list
234 c41eea6e Iustin Pop
    @return: the list of all DRBD secrets
235 c41eea6e Iustin Pop

236 f9518d38 Iustin Pop
    """
237 f9518d38 Iustin Pop
    def helper(disk, result):
238 f9518d38 Iustin Pop
      """Recursively gather secrets from this disk."""
239 f9518d38 Iustin Pop
      if disk.dev_type == constants.DT_DRBD8:
240 f9518d38 Iustin Pop
        result.append(disk.logical_id[5])
241 f9518d38 Iustin Pop
      if disk.children:
242 f9518d38 Iustin Pop
        for child in disk.children:
243 f9518d38 Iustin Pop
          helper(child, result)
244 f9518d38 Iustin Pop
245 f9518d38 Iustin Pop
    result = []
246 f9518d38 Iustin Pop
    for instance in self._config_data.instances.values():
247 f9518d38 Iustin Pop
      for disk in instance.disks:
248 f9518d38 Iustin Pop
        helper(disk, result)
249 f9518d38 Iustin Pop
250 f9518d38 Iustin Pop
    return result
251 f9518d38 Iustin Pop
252 4b98ac29 Iustin Pop
  def _CheckDiskIDs(self, disk, l_ids, p_ids):
253 4b98ac29 Iustin Pop
    """Compute duplicate disk IDs
254 4b98ac29 Iustin Pop

255 4b98ac29 Iustin Pop
    @type disk: L{objects.Disk}
256 4b98ac29 Iustin Pop
    @param disk: the disk at which to start searching
257 4b98ac29 Iustin Pop
    @type l_ids: list
258 4b98ac29 Iustin Pop
    @param l_ids: list of current logical ids
259 4b98ac29 Iustin Pop
    @type p_ids: list
260 4b98ac29 Iustin Pop
    @param p_ids: list of current physical ids
261 4b98ac29 Iustin Pop
    @rtype: list
262 4b98ac29 Iustin Pop
    @return: a list of error messages
263 4b98ac29 Iustin Pop

264 4b98ac29 Iustin Pop
    """
265 4b98ac29 Iustin Pop
    result = []
266 25ae22e4 Iustin Pop
    if disk.logical_id is not None:
267 25ae22e4 Iustin Pop
      if disk.logical_id in l_ids:
268 25ae22e4 Iustin Pop
        result.append("duplicate logical id %s" % str(disk.logical_id))
269 25ae22e4 Iustin Pop
      else:
270 25ae22e4 Iustin Pop
        l_ids.append(disk.logical_id)
271 25ae22e4 Iustin Pop
    if disk.physical_id is not None:
272 25ae22e4 Iustin Pop
      if disk.physical_id in p_ids:
273 25ae22e4 Iustin Pop
        result.append("duplicate physical id %s" % str(disk.physical_id))
274 25ae22e4 Iustin Pop
      else:
275 25ae22e4 Iustin Pop
        p_ids.append(disk.physical_id)
276 4b98ac29 Iustin Pop
277 4b98ac29 Iustin Pop
    if disk.children:
278 4b98ac29 Iustin Pop
      for child in disk.children:
279 4b98ac29 Iustin Pop
        result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
280 4b98ac29 Iustin Pop
    return result
281 4b98ac29 Iustin Pop
282 4a89c54a Iustin Pop
  def _UnlockedVerifyConfig(self):
283 a8efbb40 Iustin Pop
    """Verify function.
284 a8efbb40 Iustin Pop

285 4a89c54a Iustin Pop
    @rtype: list
286 4a89c54a Iustin Pop
    @return: a list of error messages; a non-empty list signifies
287 4a89c54a Iustin Pop
        configuration errors
288 4a89c54a Iustin Pop

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

396 4a89c54a Iustin Pop
    This is just a wrapper over L{_UnlockedVerifyConfig}.
397 4a89c54a Iustin Pop

398 4a89c54a Iustin Pop
    @rtype: list
399 4a89c54a Iustin Pop
    @return: a list of error messages; a non-empty list signifies
400 4a89c54a Iustin Pop
        configuration errors
401 4a89c54a Iustin Pop

402 4a89c54a Iustin Pop
    """
403 4a89c54a Iustin Pop
    return self._UnlockedVerifyConfig()
404 4a89c54a Iustin Pop
405 f78ede4e Guido Trotter
  def _UnlockedSetDiskID(self, disk, node_name):
406 a8083063 Iustin Pop
    """Convert the unique ID to the ID needed on the target nodes.
407 a8083063 Iustin Pop

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

410 a8083063 Iustin Pop
    The routine descends down and updates its children also, because
411 a8083063 Iustin Pop
    this helps when the only the top device is passed to the remote
412 a8083063 Iustin Pop
    node.
413 a8083063 Iustin Pop

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

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

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

449 f78ede4e Guido Trotter
    The routine descends down and updates its children also, because
450 f78ede4e Guido Trotter
    this helps when the only the top device is passed to the remote
451 f78ede4e Guido Trotter
    node.
452 f78ede4e Guido Trotter

453 f78ede4e Guido Trotter
    """
454 f78ede4e Guido Trotter
    return self._UnlockedSetDiskID(disk, node_name)
455 f78ede4e Guido Trotter
456 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
457 b2fddf63 Iustin Pop
  def AddTcpUdpPort(self, port):
458 b2fddf63 Iustin Pop
    """Adds a new port to the available port pool.
459 b2fddf63 Iustin Pop

460 b2fddf63 Iustin Pop
    """
461 264bb3c5 Michael Hanselmann
    if not isinstance(port, int):
462 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed for port")
463 264bb3c5 Michael Hanselmann
464 b2fddf63 Iustin Pop
    self._config_data.cluster.tcpudp_port_pool.add(port)
465 264bb3c5 Michael Hanselmann
    self._WriteConfig()
466 264bb3c5 Michael Hanselmann
467 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
468 b2fddf63 Iustin Pop
  def GetPortList(self):
469 264bb3c5 Michael Hanselmann
    """Returns a copy of the current port list.
470 264bb3c5 Michael Hanselmann

471 264bb3c5 Michael Hanselmann
    """
472 b2fddf63 Iustin Pop
    return self._config_data.cluster.tcpudp_port_pool.copy()
473 264bb3c5 Michael Hanselmann
474 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
475 a8083063 Iustin Pop
  def AllocatePort(self):
476 a8083063 Iustin Pop
    """Allocate a port.
477 a8083063 Iustin Pop

478 b2fddf63 Iustin Pop
    The port will be taken from the available port pool or from the
479 b2fddf63 Iustin Pop
    default port range (and in this case we increase
480 b2fddf63 Iustin Pop
    highest_used_port).
481 a8083063 Iustin Pop

482 a8083063 Iustin Pop
    """
483 264bb3c5 Michael Hanselmann
    # If there are TCP/IP ports configured, we use them first.
484 b2fddf63 Iustin Pop
    if self._config_data.cluster.tcpudp_port_pool:
485 b2fddf63 Iustin Pop
      port = self._config_data.cluster.tcpudp_port_pool.pop()
486 264bb3c5 Michael Hanselmann
    else:
487 264bb3c5 Michael Hanselmann
      port = self._config_data.cluster.highest_used_port + 1
488 264bb3c5 Michael Hanselmann
      if port >= constants.LAST_DRBD_PORT:
489 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("The highest used port is greater"
490 3ecf6786 Iustin Pop
                                        " than %s. Aborting." %
491 3ecf6786 Iustin Pop
                                        constants.LAST_DRBD_PORT)
492 264bb3c5 Michael Hanselmann
      self._config_data.cluster.highest_used_port = port
493 a8083063 Iustin Pop
494 a8083063 Iustin Pop
    self._WriteConfig()
495 a8083063 Iustin Pop
    return port
496 a8083063 Iustin Pop
497 6d2e83d5 Iustin Pop
  def _UnlockedComputeDRBDMap(self):
498 a81c53c9 Iustin Pop
    """Compute the used DRBD minor/nodes.
499 a81c53c9 Iustin Pop

500 4a89c54a Iustin Pop
    @rtype: (dict, list)
501 c41eea6e Iustin Pop
    @return: dictionary of node_name: dict of minor: instance_name;
502 c41eea6e Iustin Pop
        the returned dict will have all the nodes in it (even if with
503 4a89c54a Iustin Pop
        an empty list), and a list of duplicates; if the duplicates
504 4a89c54a Iustin Pop
        list is not empty, the configuration is corrupted and its caller
505 4a89c54a Iustin Pop
        should raise an exception
506 a81c53c9 Iustin Pop

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

540 6d2e83d5 Iustin Pop
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
541 6d2e83d5 Iustin Pop

542 6d2e83d5 Iustin Pop
    @return: dictionary of node_name: dict of minor: instance_name;
543 6d2e83d5 Iustin Pop
        the returned dict will have all the nodes in it (even if with
544 6d2e83d5 Iustin Pop
        an empty list).
545 6d2e83d5 Iustin Pop

546 6d2e83d5 Iustin Pop
    """
547 4a89c54a Iustin Pop
    d_map, duplicates = self._UnlockedComputeDRBDMap()
548 4a89c54a Iustin Pop
    if duplicates:
549 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
550 4a89c54a Iustin Pop
                                      str(duplicates))
551 4a89c54a Iustin Pop
    return d_map
552 6d2e83d5 Iustin Pop
553 6d2e83d5 Iustin Pop
  @locking.ssynchronized(_config_lock)
554 a81c53c9 Iustin Pop
  def AllocateDRBDMinor(self, nodes, instance):
555 a81c53c9 Iustin Pop
    """Allocate a drbd minor.
556 a81c53c9 Iustin Pop

557 a81c53c9 Iustin Pop
    The free minor will be automatically computed from the existing
558 a81c53c9 Iustin Pop
    devices. A node can be given multiple times in order to allocate
559 a81c53c9 Iustin Pop
    multiple minors. The result is the list of minors, in the same
560 a81c53c9 Iustin Pop
    order as the passed nodes.
561 a81c53c9 Iustin Pop

562 32388e6d Iustin Pop
    @type instance: string
563 32388e6d Iustin Pop
    @param instance: the instance for which we allocate minors
564 32388e6d Iustin Pop

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

612 a81c53c9 Iustin Pop
    @type instance: string
613 a81c53c9 Iustin Pop
    @param instance: the instance for which temporary minors should be
614 a81c53c9 Iustin Pop
                     released
615 a81c53c9 Iustin Pop

616 a81c53c9 Iustin Pop
    """
617 32388e6d Iustin Pop
    assert isinstance(instance, basestring), \
618 32388e6d Iustin Pop
           "Invalid argument passed to ReleaseDRBDMinors"
619 a81c53c9 Iustin Pop
    for key, name in self._temporary_drbds.items():
620 a81c53c9 Iustin Pop
      if name == instance:
621 a81c53c9 Iustin Pop
        del self._temporary_drbds[key]
622 a81c53c9 Iustin Pop
623 61cf6b5e Iustin Pop
  @locking.ssynchronized(_config_lock)
624 61cf6b5e Iustin Pop
  def ReleaseDRBDMinors(self, instance):
625 61cf6b5e Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
626 61cf6b5e Iustin Pop

627 61cf6b5e Iustin Pop
    This should be called on the error paths, on the success paths
628 61cf6b5e Iustin Pop
    it's automatically called by the ConfigWriter add and update
629 61cf6b5e Iustin Pop
    functions.
630 61cf6b5e Iustin Pop

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

633 61cf6b5e Iustin Pop
    @type instance: string
634 61cf6b5e Iustin Pop
    @param instance: the instance for which temporary minors should be
635 61cf6b5e Iustin Pop
                     released
636 61cf6b5e Iustin Pop

637 61cf6b5e Iustin Pop
    """
638 61cf6b5e Iustin Pop
    self._UnlockedReleaseDRBDMinors(instance)
639 61cf6b5e Iustin Pop
640 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
641 4a8b186a Michael Hanselmann
  def GetConfigVersion(self):
642 4a8b186a Michael Hanselmann
    """Get the configuration version.
643 4a8b186a Michael Hanselmann

644 4a8b186a Michael Hanselmann
    @return: Config version
645 4a8b186a Michael Hanselmann

646 4a8b186a Michael Hanselmann
    """
647 4a8b186a Michael Hanselmann
    return self._config_data.version
648 4a8b186a Michael Hanselmann
649 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
650 4a8b186a Michael Hanselmann
  def GetClusterName(self):
651 4a8b186a Michael Hanselmann
    """Get cluster name.
652 4a8b186a Michael Hanselmann

653 4a8b186a Michael Hanselmann
    @return: Cluster name
654 4a8b186a Michael Hanselmann

655 4a8b186a Michael Hanselmann
    """
656 4a8b186a Michael Hanselmann
    return self._config_data.cluster.cluster_name
657 4a8b186a Michael Hanselmann
658 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
659 4a8b186a Michael Hanselmann
  def GetMasterNode(self):
660 4a8b186a Michael Hanselmann
    """Get the hostname of the master node for this cluster.
661 4a8b186a Michael Hanselmann

662 4a8b186a Michael Hanselmann
    @return: Master hostname
663 4a8b186a Michael Hanselmann

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

671 4a8b186a Michael Hanselmann
    @return: Master IP
672 4a8b186a Michael Hanselmann

673 4a8b186a Michael Hanselmann
    """
674 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_ip
675 4a8b186a Michael Hanselmann
676 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
677 4a8b186a Michael Hanselmann
  def GetMasterNetdev(self):
678 4a8b186a Michael Hanselmann
    """Get the master network device for this cluster.
679 4a8b186a Michael Hanselmann

680 4a8b186a Michael Hanselmann
    """
681 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_netdev
682 4a8b186a Michael Hanselmann
683 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
684 4a8b186a Michael Hanselmann
  def GetFileStorageDir(self):
685 4a8b186a Michael Hanselmann
    """Get the file storage dir for this cluster.
686 4a8b186a Michael Hanselmann

687 4a8b186a Michael Hanselmann
    """
688 4a8b186a Michael Hanselmann
    return self._config_data.cluster.file_storage_dir
689 4a8b186a Michael Hanselmann
690 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
691 4a8b186a Michael Hanselmann
  def GetHypervisorType(self):
692 4a8b186a Michael Hanselmann
    """Get the hypervisor type for this cluster.
693 4a8b186a Michael Hanselmann

694 4a8b186a Michael Hanselmann
    """
695 64272529 Iustin Pop
    return self._config_data.cluster.default_hypervisor
696 4a8b186a Michael Hanselmann
697 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
698 a8083063 Iustin Pop
  def GetHostKey(self):
699 a8083063 Iustin Pop
    """Return the rsa hostkey from the config.
700 a8083063 Iustin Pop

701 c41eea6e Iustin Pop
    @rtype: string
702 c41eea6e Iustin Pop
    @return: the rsa hostkey
703 a8083063 Iustin Pop

704 a8083063 Iustin Pop
    """
705 a8083063 Iustin Pop
    return self._config_data.cluster.rsahostkeypub
706 a8083063 Iustin Pop
707 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
708 a8083063 Iustin Pop
  def AddInstance(self, instance):
709 a8083063 Iustin Pop
    """Add an instance to the config.
710 a8083063 Iustin Pop

711 a8083063 Iustin Pop
    This should be used after creating a new instance.
712 a8083063 Iustin Pop

713 c41eea6e Iustin Pop
    @type instance: L{objects.Instance}
714 c41eea6e Iustin Pop
    @param instance: the instance object
715 c41eea6e Iustin Pop

716 a8083063 Iustin Pop
    """
717 a8083063 Iustin Pop
    if not isinstance(instance, objects.Instance):
718 a8083063 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
719 a8083063 Iustin Pop
720 e00fb268 Iustin Pop
    if instance.disk_template != constants.DT_DISKLESS:
721 e00fb268 Iustin Pop
      all_lvs = instance.MapLVsByNode()
722 74a48621 Iustin Pop
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
723 923b1523 Iustin Pop
724 e4640214 Guido Trotter
    all_macs = self._AllMACs()
725 e4640214 Guido Trotter
    for nic in instance.nics:
726 e4640214 Guido Trotter
      if nic.mac in all_macs:
727 e4640214 Guido Trotter
        raise errors.ConfigurationError("Cannot add instance %s:"
728 e4640214 Guido Trotter
          " MAC address '%s' already in use." % (instance.name, nic.mac))
729 e4640214 Guido Trotter
730 b989e85d Iustin Pop
    instance.serial_no = 1
731 a8083063 Iustin Pop
    self._config_data.instances[instance.name] = instance
732 81a49123 Iustin Pop
    self._config_data.cluster.serial_no += 1
733 61cf6b5e Iustin Pop
    self._UnlockedReleaseDRBDMinors(instance.name)
734 e7d81ba0 Iustin Pop
    for nic in instance.nics:
735 e7d81ba0 Iustin Pop
      self._temporary_macs.discard(nic.mac)
736 a8083063 Iustin Pop
    self._WriteConfig()
737 a8083063 Iustin Pop
738 6a408fb2 Iustin Pop
  def _SetInstanceStatus(self, instance_name, status):
739 6a408fb2 Iustin Pop
    """Set the instance's status to a given value.
740 a8083063 Iustin Pop

741 a8083063 Iustin Pop
    """
742 0d68c45d Iustin Pop
    assert isinstance(status, bool), \
743 0d68c45d Iustin Pop
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
744 a8083063 Iustin Pop
745 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
746 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" %
747 3ecf6786 Iustin Pop
                                      instance_name)
748 a8083063 Iustin Pop
    instance = self._config_data.instances[instance_name]
749 0d68c45d Iustin Pop
    if instance.admin_up != status:
750 0d68c45d Iustin Pop
      instance.admin_up = status
751 b989e85d Iustin Pop
      instance.serial_no += 1
752 455a3445 Iustin Pop
      self._WriteConfig()
753 a8083063 Iustin Pop
754 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
755 6a408fb2 Iustin Pop
  def MarkInstanceUp(self, instance_name):
756 6a408fb2 Iustin Pop
    """Mark the instance status to up in the config.
757 6a408fb2 Iustin Pop

758 6a408fb2 Iustin Pop
    """
759 0d68c45d Iustin Pop
    self._SetInstanceStatus(instance_name, True)
760 6a408fb2 Iustin Pop
761 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
762 a8083063 Iustin Pop
  def RemoveInstance(self, instance_name):
763 a8083063 Iustin Pop
    """Remove the instance from the configuration.
764 a8083063 Iustin Pop

765 a8083063 Iustin Pop
    """
766 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
767 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
768 a8083063 Iustin Pop
    del self._config_data.instances[instance_name]
769 81a49123 Iustin Pop
    self._config_data.cluster.serial_no += 1
770 a8083063 Iustin Pop
    self._WriteConfig()
771 a8083063 Iustin Pop
772 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
773 fc95f88f Iustin Pop
  def RenameInstance(self, old_name, new_name):
774 fc95f88f Iustin Pop
    """Rename an instance.
775 fc95f88f Iustin Pop

776 fc95f88f Iustin Pop
    This needs to be done in ConfigWriter and not by RemoveInstance
777 fc95f88f Iustin Pop
    combined with AddInstance as only we can guarantee an atomic
778 fc95f88f Iustin Pop
    rename.
779 fc95f88f Iustin Pop

780 fc95f88f Iustin Pop
    """
781 fc95f88f Iustin Pop
    if old_name not in self._config_data.instances:
782 fc95f88f Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
783 fc95f88f Iustin Pop
    inst = self._config_data.instances[old_name]
784 fc95f88f Iustin Pop
    del self._config_data.instances[old_name]
785 fc95f88f Iustin Pop
    inst.name = new_name
786 b23c4333 Manuel Franceschini
787 b23c4333 Manuel Franceschini
    for disk in inst.disks:
788 b23c4333 Manuel Franceschini
      if disk.dev_type == constants.LD_FILE:
789 b23c4333 Manuel Franceschini
        # rename the file paths in logical and physical id
790 b23c4333 Manuel Franceschini
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
791 b23c4333 Manuel Franceschini
        disk.physical_id = disk.logical_id = (disk.logical_id[0],
792 b23c4333 Manuel Franceschini
                                              os.path.join(file_storage_dir,
793 b23c4333 Manuel Franceschini
                                                           inst.name,
794 b23c4333 Manuel Franceschini
                                                           disk.iv_name))
795 b23c4333 Manuel Franceschini
796 fc95f88f Iustin Pop
    self._config_data.instances[inst.name] = inst
797 fc95f88f Iustin Pop
    self._WriteConfig()
798 fc95f88f Iustin Pop
799 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
800 a8083063 Iustin Pop
  def MarkInstanceDown(self, instance_name):
801 a8083063 Iustin Pop
    """Mark the status of an instance to down in the configuration.
802 a8083063 Iustin Pop

803 a8083063 Iustin Pop
    """
804 0d68c45d Iustin Pop
    self._SetInstanceStatus(instance_name, False)
805 a8083063 Iustin Pop
806 94bbfece Iustin Pop
  def _UnlockedGetInstanceList(self):
807 94bbfece Iustin Pop
    """Get the list of instances.
808 94bbfece Iustin Pop

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

811 94bbfece Iustin Pop
    """
812 94bbfece Iustin Pop
    return self._config_data.instances.keys()
813 94bbfece Iustin Pop
814 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
815 a8083063 Iustin Pop
  def GetInstanceList(self):
816 a8083063 Iustin Pop
    """Get the list of instances.
817 a8083063 Iustin Pop

818 c41eea6e Iustin Pop
    @return: array of instances, ex. ['instance2.example.com',
819 c41eea6e Iustin Pop
        'instance1.example.com']
820 a8083063 Iustin Pop

821 a8083063 Iustin Pop
    """
822 94bbfece Iustin Pop
    return self._UnlockedGetInstanceList()
823 a8083063 Iustin Pop
824 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
825 a8083063 Iustin Pop
  def ExpandInstanceName(self, short_name):
826 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
827 a8083063 Iustin Pop

828 a8083063 Iustin Pop
    """
829 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
830 a8083063 Iustin Pop
                                    self._config_data.instances.keys())
831 a8083063 Iustin Pop
832 94bbfece Iustin Pop
  def _UnlockedGetInstanceInfo(self, instance_name):
833 5bbd3f7f Michael Hanselmann
    """Returns information about an instance.
834 94bbfece Iustin Pop

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

837 94bbfece Iustin Pop
    """
838 94bbfece Iustin Pop
    if instance_name not in self._config_data.instances:
839 94bbfece Iustin Pop
      return None
840 94bbfece Iustin Pop
841 94bbfece Iustin Pop
    return self._config_data.instances[instance_name]
842 94bbfece Iustin Pop
843 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
844 a8083063 Iustin Pop
  def GetInstanceInfo(self, instance_name):
845 5bbd3f7f Michael Hanselmann
    """Returns information about an instance.
846 a8083063 Iustin Pop

847 5bbd3f7f Michael Hanselmann
    It takes the information from the configuration file. Other information of
848 a8083063 Iustin Pop
    an instance are taken from the live systems.
849 a8083063 Iustin Pop

850 c41eea6e Iustin Pop
    @param instance_name: name of the instance, e.g.
851 c41eea6e Iustin Pop
        I{instance1.example.com}
852 a8083063 Iustin Pop

853 c41eea6e Iustin Pop
    @rtype: L{objects.Instance}
854 c41eea6e Iustin Pop
    @return: the instance object
855 a8083063 Iustin Pop

856 a8083063 Iustin Pop
    """
857 94bbfece Iustin Pop
    return self._UnlockedGetInstanceInfo(instance_name)
858 a8083063 Iustin Pop
859 0b2de758 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
860 0b2de758 Iustin Pop
  def GetAllInstancesInfo(self):
861 0b2de758 Iustin Pop
    """Get the configuration of all instances.
862 0b2de758 Iustin Pop

863 0b2de758 Iustin Pop
    @rtype: dict
864 5fcc718f Iustin Pop
    @return: dict of (instance, instance_info), where instance_info is what
865 0b2de758 Iustin Pop
              would GetInstanceInfo return for the node
866 0b2de758 Iustin Pop

867 0b2de758 Iustin Pop
    """
868 64d3bd52 Guido Trotter
    my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
869 64d3bd52 Guido Trotter
                    for instance in self._UnlockedGetInstanceList()])
870 0b2de758 Iustin Pop
    return my_dict
871 0b2de758 Iustin Pop
872 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
873 a8083063 Iustin Pop
  def AddNode(self, node):
874 a8083063 Iustin Pop
    """Add a node to the configuration.
875 a8083063 Iustin Pop

876 c41eea6e Iustin Pop
    @type node: L{objects.Node}
877 c41eea6e Iustin Pop
    @param node: a Node instance
878 a8083063 Iustin Pop

879 a8083063 Iustin Pop
    """
880 d8470559 Michael Hanselmann
    logging.info("Adding node %s to configuration" % node.name)
881 d8470559 Michael Hanselmann
882 b989e85d Iustin Pop
    node.serial_no = 1
883 a8083063 Iustin Pop
    self._config_data.nodes[node.name] = node
884 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
885 a8083063 Iustin Pop
    self._WriteConfig()
886 a8083063 Iustin Pop
887 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
888 a8083063 Iustin Pop
  def RemoveNode(self, node_name):
889 a8083063 Iustin Pop
    """Remove a node from the configuration.
890 a8083063 Iustin Pop

891 a8083063 Iustin Pop
    """
892 d8470559 Michael Hanselmann
    logging.info("Removing node %s from configuration" % node_name)
893 d8470559 Michael Hanselmann
894 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
895 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
896 a8083063 Iustin Pop
897 a8083063 Iustin Pop
    del self._config_data.nodes[node_name]
898 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
899 a8083063 Iustin Pop
    self._WriteConfig()
900 a8083063 Iustin Pop
901 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
902 a8083063 Iustin Pop
  def ExpandNodeName(self, short_name):
903 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
904 a8083063 Iustin Pop

905 a8083063 Iustin Pop
    """
906 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
907 a8083063 Iustin Pop
                                    self._config_data.nodes.keys())
908 a8083063 Iustin Pop
909 f78ede4e Guido Trotter
  def _UnlockedGetNodeInfo(self, node_name):
910 a8083063 Iustin Pop
    """Get the configuration of a node, as stored in the config.
911 a8083063 Iustin Pop

912 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
913 c41eea6e Iustin Pop
    held.
914 f78ede4e Guido Trotter

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

917 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
918 c41eea6e Iustin Pop
    @return: the node object
919 a8083063 Iustin Pop

920 a8083063 Iustin Pop
    """
921 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
922 a8083063 Iustin Pop
      return None
923 a8083063 Iustin Pop
924 a8083063 Iustin Pop
    return self._config_data.nodes[node_name]
925 a8083063 Iustin Pop
926 f78ede4e Guido Trotter
927 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
928 f78ede4e Guido Trotter
  def GetNodeInfo(self, node_name):
929 f78ede4e Guido Trotter
    """Get the configuration of a node, as stored in the config.
930 f78ede4e Guido Trotter

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

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

935 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
936 c41eea6e Iustin Pop
    @return: the node object
937 f78ede4e Guido Trotter

938 f78ede4e Guido Trotter
    """
939 f78ede4e Guido Trotter
    return self._UnlockedGetNodeInfo(node_name)
940 f78ede4e Guido Trotter
941 f78ede4e Guido Trotter
  def _UnlockedGetNodeList(self):
942 a8083063 Iustin Pop
    """Return the list of nodes which are in the configuration.
943 a8083063 Iustin Pop

944 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
945 c41eea6e Iustin Pop
    held.
946 c41eea6e Iustin Pop

947 c41eea6e Iustin Pop
    @rtype: list
948 f78ede4e Guido Trotter

949 a8083063 Iustin Pop
    """
950 a8083063 Iustin Pop
    return self._config_data.nodes.keys()
951 a8083063 Iustin Pop
952 f78ede4e Guido Trotter
953 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
954 f78ede4e Guido Trotter
  def GetNodeList(self):
955 f78ede4e Guido Trotter
    """Return the list of nodes which are in the configuration.
956 f78ede4e Guido Trotter

957 f78ede4e Guido Trotter
    """
958 f78ede4e Guido Trotter
    return self._UnlockedGetNodeList()
959 f78ede4e Guido Trotter
960 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
961 94a02bb5 Iustin Pop
  def GetOnlineNodeList(self):
962 94a02bb5 Iustin Pop
    """Return the list of nodes which are online.
963 94a02bb5 Iustin Pop

964 94a02bb5 Iustin Pop
    """
965 94a02bb5 Iustin Pop
    all_nodes = [self._UnlockedGetNodeInfo(node)
966 94a02bb5 Iustin Pop
                 for node in self._UnlockedGetNodeList()]
967 94a02bb5 Iustin Pop
    return [node.name for node in all_nodes if not node.offline]
968 94a02bb5 Iustin Pop
969 94a02bb5 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
970 d65e5776 Iustin Pop
  def GetAllNodesInfo(self):
971 d65e5776 Iustin Pop
    """Get the configuration of all nodes.
972 d65e5776 Iustin Pop

973 d65e5776 Iustin Pop
    @rtype: dict
974 ec0292f1 Iustin Pop
    @return: dict of (node, node_info), where node_info is what
975 d65e5776 Iustin Pop
              would GetNodeInfo return for the node
976 d65e5776 Iustin Pop

977 d65e5776 Iustin Pop
    """
978 d65e5776 Iustin Pop
    my_dict = dict([(node, self._UnlockedGetNodeInfo(node))
979 d65e5776 Iustin Pop
                    for node in self._UnlockedGetNodeList()])
980 d65e5776 Iustin Pop
    return my_dict
981 d65e5776 Iustin Pop
982 23f06b2b Iustin Pop
  def _UnlockedGetMasterCandidateStats(self, exceptions=None):
983 ec0292f1 Iustin Pop
    """Get the number of current and maximum desired and possible candidates.
984 ec0292f1 Iustin Pop

985 23f06b2b Iustin Pop
    @type exceptions: list
986 23f06b2b Iustin Pop
    @param exceptions: if passed, list of nodes that should be ignored
987 ec0292f1 Iustin Pop
    @rtype: tuple
988 ec0292f1 Iustin Pop
    @return: tuple of (current, desired and possible)
989 ec0292f1 Iustin Pop

990 ec0292f1 Iustin Pop
    """
991 ec0292f1 Iustin Pop
    mc_now = mc_max = 0
992 23f06b2b Iustin Pop
    for node in self._config_data.nodes.values():
993 23f06b2b Iustin Pop
      if exceptions and node.name in exceptions:
994 23f06b2b Iustin Pop
        continue
995 5bf07049 Iustin Pop
      if not (node.offline or node.drained):
996 ec0292f1 Iustin Pop
        mc_max += 1
997 ec0292f1 Iustin Pop
      if node.master_candidate:
998 ec0292f1 Iustin Pop
        mc_now += 1
999 ec0292f1 Iustin Pop
    mc_max = min(mc_max, self._config_data.cluster.candidate_pool_size)
1000 ec0292f1 Iustin Pop
    return (mc_now, mc_max)
1001 ec0292f1 Iustin Pop
1002 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
1003 23f06b2b Iustin Pop
  def GetMasterCandidateStats(self, exceptions=None):
1004 ec0292f1 Iustin Pop
    """Get the number of current and maximum possible candidates.
1005 ec0292f1 Iustin Pop

1006 ec0292f1 Iustin Pop
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
1007 ec0292f1 Iustin Pop

1008 23f06b2b Iustin Pop
    @type exceptions: list
1009 23f06b2b Iustin Pop
    @param exceptions: if passed, list of nodes that should be ignored
1010 ec0292f1 Iustin Pop
    @rtype: tuple
1011 ec0292f1 Iustin Pop
    @return: tuple of (current, max)
1012 ec0292f1 Iustin Pop

1013 ec0292f1 Iustin Pop
    """
1014 23f06b2b Iustin Pop
    return self._UnlockedGetMasterCandidateStats(exceptions)
1015 ec0292f1 Iustin Pop
1016 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock)
1017 ec0292f1 Iustin Pop
  def MaintainCandidatePool(self):
1018 ec0292f1 Iustin Pop
    """Try to grow the candidate pool to the desired size.
1019 ec0292f1 Iustin Pop

1020 ec0292f1 Iustin Pop
    @rtype: list
1021 ee513a66 Iustin Pop
    @return: list with the adjusted nodes (L{objects.Node} instances)
1022 ec0292f1 Iustin Pop

1023 ec0292f1 Iustin Pop
    """
1024 ec0292f1 Iustin Pop
    mc_now, mc_max = self._UnlockedGetMasterCandidateStats()
1025 ec0292f1 Iustin Pop
    mod_list = []
1026 ec0292f1 Iustin Pop
    if mc_now < mc_max:
1027 ec0292f1 Iustin Pop
      node_list = self._config_data.nodes.keys()
1028 ec0292f1 Iustin Pop
      random.shuffle(node_list)
1029 ec0292f1 Iustin Pop
      for name in node_list:
1030 ec0292f1 Iustin Pop
        if mc_now >= mc_max:
1031 ec0292f1 Iustin Pop
          break
1032 ec0292f1 Iustin Pop
        node = self._config_data.nodes[name]
1033 5bf07049 Iustin Pop
        if node.master_candidate or node.offline or node.drained:
1034 ec0292f1 Iustin Pop
          continue
1035 ee513a66 Iustin Pop
        mod_list.append(node)
1036 ec0292f1 Iustin Pop
        node.master_candidate = True
1037 ec0292f1 Iustin Pop
        node.serial_no += 1
1038 ec0292f1 Iustin Pop
        mc_now += 1
1039 ec0292f1 Iustin Pop
      if mc_now != mc_max:
1040 ec0292f1 Iustin Pop
        # this should not happen
1041 ec0292f1 Iustin Pop
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
1042 ec0292f1 Iustin Pop
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
1043 ec0292f1 Iustin Pop
      if mod_list:
1044 ec0292f1 Iustin Pop
        self._config_data.cluster.serial_no += 1
1045 ec0292f1 Iustin Pop
        self._WriteConfig()
1046 ec0292f1 Iustin Pop
1047 ec0292f1 Iustin Pop
    return mod_list
1048 ec0292f1 Iustin Pop
1049 a8083063 Iustin Pop
  def _BumpSerialNo(self):
1050 a8083063 Iustin Pop
    """Bump up the serial number of the config.
1051 a8083063 Iustin Pop

1052 a8083063 Iustin Pop
    """
1053 9d38c6e1 Iustin Pop
    self._config_data.serial_no += 1
1054 a8083063 Iustin Pop
1055 a8083063 Iustin Pop
  def _OpenConfig(self):
1056 a8083063 Iustin Pop
    """Read the config data from disk.
1057 a8083063 Iustin Pop

1058 a8083063 Iustin Pop
    """
1059 a8083063 Iustin Pop
    f = open(self._cfg_file, 'r')
1060 a8083063 Iustin Pop
    try:
1061 a8083063 Iustin Pop
      try:
1062 8d14b30d Iustin Pop
        data = objects.ConfigData.FromDict(serializer.Load(f.read()))
1063 a8083063 Iustin Pop
      except Exception, err:
1064 3ecf6786 Iustin Pop
        raise errors.ConfigurationError(err)
1065 a8083063 Iustin Pop
    finally:
1066 a8083063 Iustin Pop
      f.close()
1067 5b263ed7 Michael Hanselmann
1068 5b263ed7 Michael Hanselmann
    # Make sure the configuration has the right version
1069 5b263ed7 Michael Hanselmann
    _ValidateConfig(data)
1070 5b263ed7 Michael Hanselmann
1071 a8083063 Iustin Pop
    if (not hasattr(data, 'cluster') or
1072 243cdbcc Michael Hanselmann
        not hasattr(data.cluster, 'rsahostkeypub')):
1073 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Incomplete configuration"
1074 243cdbcc Michael Hanselmann
                                      " (missing cluster.rsahostkeypub)")
1075 2d90608c Iustin Pop
1076 2d90608c Iustin Pop
    # Upgrade configuration if needed
1077 2d90608c Iustin Pop
    data.UpgradeConfig()
1078 2d90608c Iustin Pop
1079 a8083063 Iustin Pop
    self._config_data = data
1080 3c7f6c44 Iustin Pop
    # reset the last serial as -1 so that the next write will cause
1081 0779e3aa Iustin Pop
    # ssconf update
1082 0779e3aa Iustin Pop
    self._last_cluster_serial = -1
1083 a8083063 Iustin Pop
1084 a8083063 Iustin Pop
  def _DistributeConfig(self):
1085 a8083063 Iustin Pop
    """Distribute the configuration to the other nodes.
1086 a8083063 Iustin Pop

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

1090 a8083063 Iustin Pop
    """
1091 a8083063 Iustin Pop
    if self._offline:
1092 a8083063 Iustin Pop
      return True
1093 a8083063 Iustin Pop
    bad = False
1094 a8083063 Iustin Pop
1095 6a5b8b4b Iustin Pop
    node_list = []
1096 6a5b8b4b Iustin Pop
    addr_list = []
1097 6a5b8b4b Iustin Pop
    myhostname = self._my_hostname
1098 6b294c53 Iustin Pop
    # we can skip checking whether _UnlockedGetNodeInfo returns None
1099 6b294c53 Iustin Pop
    # since the node list comes from _UnlocketGetNodeList, and we are
1100 6b294c53 Iustin Pop
    # called with the lock held, so no modifications should take place
1101 6b294c53 Iustin Pop
    # in between
1102 6a5b8b4b Iustin Pop
    for node_name in self._UnlockedGetNodeList():
1103 6a5b8b4b Iustin Pop
      if node_name == myhostname:
1104 6a5b8b4b Iustin Pop
        continue
1105 6a5b8b4b Iustin Pop
      node_info = self._UnlockedGetNodeInfo(node_name)
1106 6a5b8b4b Iustin Pop
      if not node_info.master_candidate:
1107 6a5b8b4b Iustin Pop
        continue
1108 6a5b8b4b Iustin Pop
      node_list.append(node_info.name)
1109 6a5b8b4b Iustin Pop
      addr_list.append(node_info.primary_ip)
1110 6b294c53 Iustin Pop
1111 6a5b8b4b Iustin Pop
    result = rpc.RpcRunner.call_upload_file(node_list, self._cfg_file,
1112 6a5b8b4b Iustin Pop
                                            address_list=addr_list)
1113 6a5b8b4b Iustin Pop
    for node in node_list:
1114 a8083063 Iustin Pop
      if not result[node]:
1115 74a48621 Iustin Pop
        logging.error("copy of file %s to node %s failed",
1116 74a48621 Iustin Pop
                      self._cfg_file, node)
1117 a8083063 Iustin Pop
        bad = True
1118 a8083063 Iustin Pop
    return not bad
1119 a8083063 Iustin Pop
1120 a8083063 Iustin Pop
  def _WriteConfig(self, destination=None):
1121 a8083063 Iustin Pop
    """Write the configuration data to persistent storage.
1122 a8083063 Iustin Pop

1123 a8083063 Iustin Pop
    """
1124 8db31d6b Iustin Pop
    # first, cleanup the _temporary_ids set, if an ID is now in the
1125 8db31d6b Iustin Pop
    # other objects it should be discarded to prevent unbounded growth
1126 8db31d6b Iustin Pop
    # of that structure
1127 8db31d6b Iustin Pop
    self._CleanupTemporaryIDs()
1128 4a89c54a Iustin Pop
    config_errors = self._UnlockedVerifyConfig()
1129 4a89c54a Iustin Pop
    if config_errors:
1130 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Configuration data is not"
1131 4a89c54a Iustin Pop
                                      " consistent: %s" %
1132 4a89c54a Iustin Pop
                                      (", ".join(config_errors)))
1133 a8083063 Iustin Pop
    if destination is None:
1134 a8083063 Iustin Pop
      destination = self._cfg_file
1135 a8083063 Iustin Pop
    self._BumpSerialNo()
1136 8d14b30d Iustin Pop
    txt = serializer.Dump(self._config_data.ToDict())
1137 a8083063 Iustin Pop
    dir_name, file_name = os.path.split(destination)
1138 a8083063 Iustin Pop
    fd, name = tempfile.mkstemp('.newconfig', file_name, dir_name)
1139 a8083063 Iustin Pop
    f = os.fdopen(fd, 'w')
1140 a8083063 Iustin Pop
    try:
1141 8d14b30d Iustin Pop
      f.write(txt)
1142 a8083063 Iustin Pop
      os.fsync(f.fileno())
1143 a8083063 Iustin Pop
    finally:
1144 a8083063 Iustin Pop
      f.close()
1145 a8083063 Iustin Pop
    # we don't need to do os.close(fd) as f.close() did it
1146 a8083063 Iustin Pop
    os.rename(name, destination)
1147 14e15659 Iustin Pop
    self.write_count += 1
1148 3d3a04bc Iustin Pop
1149 f56618e0 Iustin Pop
    # and redistribute the config file to master candidates
1150 a8083063 Iustin Pop
    self._DistributeConfig()
1151 a8083063 Iustin Pop
1152 54d1a06e Michael Hanselmann
    # Write ssconf files on all nodes (including locally)
1153 0779e3aa Iustin Pop
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
1154 d9a855f1 Michael Hanselmann
      if not self._offline:
1155 03d1dba2 Michael Hanselmann
        rpc.RpcRunner.call_write_ssconf_files(self._UnlockedGetNodeList(),
1156 03d1dba2 Michael Hanselmann
                                              self._UnlockedGetSsconfValues())
1157 0779e3aa Iustin Pop
      self._last_cluster_serial = self._config_data.cluster.serial_no
1158 54d1a06e Michael Hanselmann
1159 03d1dba2 Michael Hanselmann
  def _UnlockedGetSsconfValues(self):
1160 054596f0 Iustin Pop
    """Return the values needed by ssconf.
1161 054596f0 Iustin Pop

1162 054596f0 Iustin Pop
    @rtype: dict
1163 054596f0 Iustin Pop
    @return: a dictionary with keys the ssconf names and values their
1164 054596f0 Iustin Pop
        associated value
1165 054596f0 Iustin Pop

1166 054596f0 Iustin Pop
    """
1167 a3316e4a Iustin Pop
    fn = "\n".join
1168 81a49123 Iustin Pop
    instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
1169 a3316e4a Iustin Pop
    node_names = utils.NiceSort(self._UnlockedGetNodeList())
1170 a3316e4a Iustin Pop
    node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
1171 a3316e4a Iustin Pop
1172 81a49123 Iustin Pop
    instance_data = fn(instance_names)
1173 a3316e4a Iustin Pop
    off_data = fn(node.name for node in node_info if node.offline)
1174 81a49123 Iustin Pop
    on_data = fn(node.name for node in node_info if not node.offline)
1175 a3316e4a Iustin Pop
    mc_data = fn(node.name for node in node_info if node.master_candidate)
1176 a3316e4a Iustin Pop
    node_data = fn(node_names)
1177 f56618e0 Iustin Pop
1178 054596f0 Iustin Pop
    cluster = self._config_data.cluster
1179 5d60b3bd Iustin Pop
    cluster_tags = fn(cluster.GetTags())
1180 03d1dba2 Michael Hanselmann
    return {
1181 054596f0 Iustin Pop
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
1182 5d60b3bd Iustin Pop
      constants.SS_CLUSTER_TAGS: cluster_tags,
1183 054596f0 Iustin Pop
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
1184 a3316e4a Iustin Pop
      constants.SS_MASTER_CANDIDATES: mc_data,
1185 054596f0 Iustin Pop
      constants.SS_MASTER_IP: cluster.master_ip,
1186 054596f0 Iustin Pop
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
1187 054596f0 Iustin Pop
      constants.SS_MASTER_NODE: cluster.master_node,
1188 a3316e4a Iustin Pop
      constants.SS_NODE_LIST: node_data,
1189 a3316e4a Iustin Pop
      constants.SS_OFFLINE_NODES: off_data,
1190 81a49123 Iustin Pop
      constants.SS_ONLINE_NODES: on_data,
1191 81a49123 Iustin Pop
      constants.SS_INSTANCE_LIST: instance_data,
1192 8a113c7a Iustin Pop
      constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
1193 03d1dba2 Michael Hanselmann
      }
1194 03d1dba2 Michael Hanselmann
1195 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1196 a8083063 Iustin Pop
  def GetVGName(self):
1197 a8083063 Iustin Pop
    """Return the volume group name.
1198 a8083063 Iustin Pop

1199 a8083063 Iustin Pop
    """
1200 a8083063 Iustin Pop
    return self._config_data.cluster.volume_group_name
1201 a8083063 Iustin Pop
1202 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1203 89ff8e15 Manuel Franceschini
  def SetVGName(self, vg_name):
1204 89ff8e15 Manuel Franceschini
    """Set the volume group name.
1205 89ff8e15 Manuel Franceschini

1206 89ff8e15 Manuel Franceschini
    """
1207 2d4011cd Manuel Franceschini
    self._config_data.cluster.volume_group_name = vg_name
1208 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
1209 89ff8e15 Manuel Franceschini
    self._WriteConfig()
1210 89ff8e15 Manuel Franceschini
1211 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1212 a8083063 Iustin Pop
  def GetDefBridge(self):
1213 a8083063 Iustin Pop
    """Return the default bridge.
1214 a8083063 Iustin Pop

1215 a8083063 Iustin Pop
    """
1216 a8083063 Iustin Pop
    return self._config_data.cluster.default_bridge
1217 a8083063 Iustin Pop
1218 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1219 a8083063 Iustin Pop
  def GetMACPrefix(self):
1220 a8083063 Iustin Pop
    """Return the mac prefix.
1221 a8083063 Iustin Pop

1222 a8083063 Iustin Pop
    """
1223 a8083063 Iustin Pop
    return self._config_data.cluster.mac_prefix
1224 62779dd0 Iustin Pop
1225 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1226 62779dd0 Iustin Pop
  def GetClusterInfo(self):
1227 5bbd3f7f Michael Hanselmann
    """Returns information about the cluster
1228 62779dd0 Iustin Pop

1229 c41eea6e Iustin Pop
    @rtype: L{objects.Cluster}
1230 c41eea6e Iustin Pop
    @return: the cluster object
1231 62779dd0 Iustin Pop

1232 62779dd0 Iustin Pop
    """
1233 62779dd0 Iustin Pop
    return self._config_data.cluster
1234 e00fb268 Iustin Pop
1235 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1236 e00fb268 Iustin Pop
  def Update(self, target):
1237 e00fb268 Iustin Pop
    """Notify function to be called after updates.
1238 e00fb268 Iustin Pop

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

1245 c41eea6e Iustin Pop
    @param target: an instance of either L{objects.Cluster},
1246 c41eea6e Iustin Pop
        L{objects.Node} or L{objects.Instance} which is existing in
1247 c41eea6e Iustin Pop
        the cluster
1248 c41eea6e Iustin Pop

1249 e00fb268 Iustin Pop
    """
1250 e00fb268 Iustin Pop
    if self._config_data is None:
1251 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Configuration file not read,"
1252 3ecf6786 Iustin Pop
                                   " cannot save.")
1253 f34901f8 Iustin Pop
    update_serial = False
1254 e00fb268 Iustin Pop
    if isinstance(target, objects.Cluster):
1255 e00fb268 Iustin Pop
      test = target == self._config_data.cluster
1256 e00fb268 Iustin Pop
    elif isinstance(target, objects.Node):
1257 e00fb268 Iustin Pop
      test = target in self._config_data.nodes.values()
1258 f34901f8 Iustin Pop
      update_serial = True
1259 e00fb268 Iustin Pop
    elif isinstance(target, objects.Instance):
1260 e00fb268 Iustin Pop
      test = target in self._config_data.instances.values()
1261 e00fb268 Iustin Pop
    else:
1262 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
1263 3ecf6786 Iustin Pop
                                   " ConfigWriter.Update" % type(target))
1264 e00fb268 Iustin Pop
    if not test:
1265 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Configuration updated since object"
1266 3ecf6786 Iustin Pop
                                      " has been read or unknown object")
1267 f34901f8 Iustin Pop
    target.serial_no += 1
1268 f34901f8 Iustin Pop
1269 cff4c037 Iustin Pop
    if update_serial:
1270 f34901f8 Iustin Pop
      # for node updates, we need to increase the cluster serial too
1271 f34901f8 Iustin Pop
      self._config_data.cluster.serial_no += 1
1272 b989e85d Iustin Pop
1273 61cf6b5e Iustin Pop
    if isinstance(target, objects.Instance):
1274 61cf6b5e Iustin Pop
      self._UnlockedReleaseDRBDMinors(target.name)
1275 e7d81ba0 Iustin Pop
      for nic in target.nics:
1276 e7d81ba0 Iustin Pop
        self._temporary_macs.discard(nic.mac)
1277 61cf6b5e Iustin Pop
1278 e00fb268 Iustin Pop
    self._WriteConfig()