Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 9a5fba23

History | View | Annotate | Download (40.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 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 923b1523 Iustin Pop
  def _ComputeAllLVs(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 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
163 923b1523 Iustin Pop
  def GenerateUniqueID(self, exceptions=None):
164 923b1523 Iustin Pop
    """Generate an unique disk name.
165 923b1523 Iustin Pop

166 923b1523 Iustin Pop
    This checks the current node, instances and disk names for
167 923b1523 Iustin Pop
    duplicates.
168 923b1523 Iustin Pop

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

174 c41eea6e Iustin Pop
    @rtype: string
175 c41eea6e Iustin Pop
    @return: the unique id
176 923b1523 Iustin Pop

177 923b1523 Iustin Pop
    """
178 923b1523 Iustin Pop
    existing = set()
179 923b1523 Iustin Pop
    existing.update(self._temporary_ids)
180 923b1523 Iustin Pop
    existing.update(self._ComputeAllLVs())
181 923b1523 Iustin Pop
    existing.update(self._config_data.instances.keys())
182 923b1523 Iustin Pop
    existing.update(self._config_data.nodes.keys())
183 923b1523 Iustin Pop
    if exceptions is not None:
184 923b1523 Iustin Pop
      existing.update(exceptions)
185 923b1523 Iustin Pop
    retries = 64
186 923b1523 Iustin Pop
    while retries > 0:
187 24818e8f Michael Hanselmann
      unique_id = utils.NewUUID()
188 923b1523 Iustin Pop
      if unique_id not in existing and unique_id is not None:
189 923b1523 Iustin Pop
        break
190 923b1523 Iustin Pop
    else:
191 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Not able generate an unique ID"
192 3ecf6786 Iustin Pop
                                      " (last tried ID: %s" % unique_id)
193 923b1523 Iustin Pop
    self._temporary_ids.add(unique_id)
194 923b1523 Iustin Pop
    return unique_id
195 923b1523 Iustin Pop
196 a8083063 Iustin Pop
  def _AllMACs(self):
197 a8083063 Iustin Pop
    """Return all MACs present in the config.
198 a8083063 Iustin Pop

199 c41eea6e Iustin Pop
    @rtype: list
200 c41eea6e Iustin Pop
    @return: the list of all MACs
201 c41eea6e Iustin Pop

202 a8083063 Iustin Pop
    """
203 a8083063 Iustin Pop
    result = []
204 a8083063 Iustin Pop
    for instance in self._config_data.instances.values():
205 a8083063 Iustin Pop
      for nic in instance.nics:
206 a8083063 Iustin Pop
        result.append(nic.mac)
207 a8083063 Iustin Pop
208 a8083063 Iustin Pop
    return result
209 a8083063 Iustin Pop
210 f9518d38 Iustin Pop
  def _AllDRBDSecrets(self):
211 f9518d38 Iustin Pop
    """Return all DRBD secrets present in the config.
212 f9518d38 Iustin Pop

213 c41eea6e Iustin Pop
    @rtype: list
214 c41eea6e Iustin Pop
    @return: the list of all DRBD secrets
215 c41eea6e Iustin Pop

216 f9518d38 Iustin Pop
    """
217 f9518d38 Iustin Pop
    def helper(disk, result):
218 f9518d38 Iustin Pop
      """Recursively gather secrets from this disk."""
219 f9518d38 Iustin Pop
      if disk.dev_type == constants.DT_DRBD8:
220 f9518d38 Iustin Pop
        result.append(disk.logical_id[5])
221 f9518d38 Iustin Pop
      if disk.children:
222 f9518d38 Iustin Pop
        for child in disk.children:
223 f9518d38 Iustin Pop
          helper(child, result)
224 f9518d38 Iustin Pop
225 f9518d38 Iustin Pop
    result = []
226 f9518d38 Iustin Pop
    for instance in self._config_data.instances.values():
227 f9518d38 Iustin Pop
      for disk in instance.disks:
228 f9518d38 Iustin Pop
        helper(disk, result)
229 f9518d38 Iustin Pop
230 f9518d38 Iustin Pop
    return result
231 f9518d38 Iustin Pop
232 4b98ac29 Iustin Pop
  def _CheckDiskIDs(self, disk, l_ids, p_ids):
233 4b98ac29 Iustin Pop
    """Compute duplicate disk IDs
234 4b98ac29 Iustin Pop

235 4b98ac29 Iustin Pop
    @type disk: L{objects.Disk}
236 4b98ac29 Iustin Pop
    @param disk: the disk at which to start searching
237 4b98ac29 Iustin Pop
    @type l_ids: list
238 4b98ac29 Iustin Pop
    @param l_ids: list of current logical ids
239 4b98ac29 Iustin Pop
    @type p_ids: list
240 4b98ac29 Iustin Pop
    @param p_ids: list of current physical ids
241 4b98ac29 Iustin Pop
    @rtype: list
242 4b98ac29 Iustin Pop
    @return: a list of error messages
243 4b98ac29 Iustin Pop

244 4b98ac29 Iustin Pop
    """
245 4b98ac29 Iustin Pop
    result = []
246 25ae22e4 Iustin Pop
    if disk.logical_id is not None:
247 25ae22e4 Iustin Pop
      if disk.logical_id in l_ids:
248 25ae22e4 Iustin Pop
        result.append("duplicate logical id %s" % str(disk.logical_id))
249 25ae22e4 Iustin Pop
      else:
250 25ae22e4 Iustin Pop
        l_ids.append(disk.logical_id)
251 25ae22e4 Iustin Pop
    if disk.physical_id is not None:
252 25ae22e4 Iustin Pop
      if disk.physical_id in p_ids:
253 25ae22e4 Iustin Pop
        result.append("duplicate physical id %s" % str(disk.physical_id))
254 25ae22e4 Iustin Pop
      else:
255 25ae22e4 Iustin Pop
        p_ids.append(disk.physical_id)
256 4b98ac29 Iustin Pop
257 4b98ac29 Iustin Pop
    if disk.children:
258 4b98ac29 Iustin Pop
      for child in disk.children:
259 4b98ac29 Iustin Pop
        result.extend(self._CheckDiskIDs(child, l_ids, p_ids))
260 4b98ac29 Iustin Pop
    return result
261 4b98ac29 Iustin Pop
262 4a89c54a Iustin Pop
  def _UnlockedVerifyConfig(self):
263 a8efbb40 Iustin Pop
    """Verify function.
264 a8efbb40 Iustin Pop

265 4a89c54a Iustin Pop
    @rtype: list
266 4a89c54a Iustin Pop
    @return: a list of error messages; a non-empty list signifies
267 4a89c54a Iustin Pop
        configuration errors
268 4a89c54a Iustin Pop

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

376 4a89c54a Iustin Pop
    This is just a wrapper over L{_UnlockedVerifyConfig}.
377 4a89c54a Iustin Pop

378 4a89c54a Iustin Pop
    @rtype: list
379 4a89c54a Iustin Pop
    @return: a list of error messages; a non-empty list signifies
380 4a89c54a Iustin Pop
        configuration errors
381 4a89c54a Iustin Pop

382 4a89c54a Iustin Pop
    """
383 4a89c54a Iustin Pop
    return self._UnlockedVerifyConfig()
384 4a89c54a Iustin Pop
385 f78ede4e Guido Trotter
  def _UnlockedSetDiskID(self, disk, node_name):
386 a8083063 Iustin Pop
    """Convert the unique ID to the ID needed on the target nodes.
387 a8083063 Iustin Pop

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

390 a8083063 Iustin Pop
    The routine descends down and updates its children also, because
391 a8083063 Iustin Pop
    this helps when the only the top device is passed to the remote
392 a8083063 Iustin Pop
    node.
393 a8083063 Iustin Pop

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

396 a8083063 Iustin Pop
    """
397 a8083063 Iustin Pop
    if disk.children:
398 a8083063 Iustin Pop
      for child in disk.children:
399 f78ede4e Guido Trotter
        self._UnlockedSetDiskID(child, node_name)
400 a8083063 Iustin Pop
401 a8083063 Iustin Pop
    if disk.logical_id is None and disk.physical_id is not None:
402 a8083063 Iustin Pop
      return
403 ffa1c0dc Iustin Pop
    if disk.dev_type == constants.LD_DRBD8:
404 f9518d38 Iustin Pop
      pnode, snode, port, pminor, sminor, secret = disk.logical_id
405 a8083063 Iustin Pop
      if node_name not in (pnode, snode):
406 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
407 3ecf6786 Iustin Pop
                                        node_name)
408 f78ede4e Guido Trotter
      pnode_info = self._UnlockedGetNodeInfo(pnode)
409 f78ede4e Guido Trotter
      snode_info = self._UnlockedGetNodeInfo(snode)
410 a8083063 Iustin Pop
      if pnode_info is None or snode_info is None:
411 a8083063 Iustin Pop
        raise errors.ConfigurationError("Can't find primary or secondary node"
412 a8083063 Iustin Pop
                                        " for %s" % str(disk))
413 ffa1c0dc Iustin Pop
      p_data = (pnode_info.secondary_ip, port)
414 ffa1c0dc Iustin Pop
      s_data = (snode_info.secondary_ip, port)
415 a8083063 Iustin Pop
      if pnode == node_name:
416 f9518d38 Iustin Pop
        disk.physical_id = p_data + s_data + (pminor, secret)
417 a8083063 Iustin Pop
      else: # it must be secondary, we tested above
418 f9518d38 Iustin Pop
        disk.physical_id = s_data + p_data + (sminor, secret)
419 a8083063 Iustin Pop
    else:
420 a8083063 Iustin Pop
      disk.physical_id = disk.logical_id
421 a8083063 Iustin Pop
    return
422 a8083063 Iustin Pop
423 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
424 f78ede4e Guido Trotter
  def SetDiskID(self, disk, node_name):
425 f78ede4e Guido Trotter
    """Convert the unique ID to the ID needed on the target nodes.
426 f78ede4e Guido Trotter

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

429 f78ede4e Guido Trotter
    The routine descends down and updates its children also, because
430 f78ede4e Guido Trotter
    this helps when the only the top device is passed to the remote
431 f78ede4e Guido Trotter
    node.
432 f78ede4e Guido Trotter

433 f78ede4e Guido Trotter
    """
434 f78ede4e Guido Trotter
    return self._UnlockedSetDiskID(disk, node_name)
435 f78ede4e Guido Trotter
436 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
437 b2fddf63 Iustin Pop
  def AddTcpUdpPort(self, port):
438 b2fddf63 Iustin Pop
    """Adds a new port to the available port pool.
439 b2fddf63 Iustin Pop

440 b2fddf63 Iustin Pop
    """
441 264bb3c5 Michael Hanselmann
    if not isinstance(port, int):
442 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed for port")
443 264bb3c5 Michael Hanselmann
444 b2fddf63 Iustin Pop
    self._config_data.cluster.tcpudp_port_pool.add(port)
445 264bb3c5 Michael Hanselmann
    self._WriteConfig()
446 264bb3c5 Michael Hanselmann
447 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
448 b2fddf63 Iustin Pop
  def GetPortList(self):
449 264bb3c5 Michael Hanselmann
    """Returns a copy of the current port list.
450 264bb3c5 Michael Hanselmann

451 264bb3c5 Michael Hanselmann
    """
452 b2fddf63 Iustin Pop
    return self._config_data.cluster.tcpudp_port_pool.copy()
453 264bb3c5 Michael Hanselmann
454 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
455 a8083063 Iustin Pop
  def AllocatePort(self):
456 a8083063 Iustin Pop
    """Allocate a port.
457 a8083063 Iustin Pop

458 b2fddf63 Iustin Pop
    The port will be taken from the available port pool or from the
459 b2fddf63 Iustin Pop
    default port range (and in this case we increase
460 b2fddf63 Iustin Pop
    highest_used_port).
461 a8083063 Iustin Pop

462 a8083063 Iustin Pop
    """
463 264bb3c5 Michael Hanselmann
    # If there are TCP/IP ports configured, we use them first.
464 b2fddf63 Iustin Pop
    if self._config_data.cluster.tcpudp_port_pool:
465 b2fddf63 Iustin Pop
      port = self._config_data.cluster.tcpudp_port_pool.pop()
466 264bb3c5 Michael Hanselmann
    else:
467 264bb3c5 Michael Hanselmann
      port = self._config_data.cluster.highest_used_port + 1
468 264bb3c5 Michael Hanselmann
      if port >= constants.LAST_DRBD_PORT:
469 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("The highest used port is greater"
470 3ecf6786 Iustin Pop
                                        " than %s. Aborting." %
471 3ecf6786 Iustin Pop
                                        constants.LAST_DRBD_PORT)
472 264bb3c5 Michael Hanselmann
      self._config_data.cluster.highest_used_port = port
473 a8083063 Iustin Pop
474 a8083063 Iustin Pop
    self._WriteConfig()
475 a8083063 Iustin Pop
    return port
476 a8083063 Iustin Pop
477 6d2e83d5 Iustin Pop
  def _UnlockedComputeDRBDMap(self):
478 a81c53c9 Iustin Pop
    """Compute the used DRBD minor/nodes.
479 a81c53c9 Iustin Pop

480 4a89c54a Iustin Pop
    @rtype: (dict, list)
481 c41eea6e Iustin Pop
    @return: dictionary of node_name: dict of minor: instance_name;
482 c41eea6e Iustin Pop
        the returned dict will have all the nodes in it (even if with
483 4a89c54a Iustin Pop
        an empty list), and a list of duplicates; if the duplicates
484 4a89c54a Iustin Pop
        list is not empty, the configuration is corrupted and its caller
485 4a89c54a Iustin Pop
        should raise an exception
486 a81c53c9 Iustin Pop

487 a81c53c9 Iustin Pop
    """
488 a81c53c9 Iustin Pop
    def _AppendUsedPorts(instance_name, disk, used):
489 4a89c54a Iustin Pop
      duplicates = []
490 f9518d38 Iustin Pop
      if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
491 7c4d6c7b Michael Hanselmann
        node_a, node_b, _, minor_a, minor_b = disk.logical_id[:5]
492 7c4d6c7b Michael Hanselmann
        for node, port in ((node_a, minor_a), (node_b, minor_b)):
493 4a89c54a Iustin Pop
          assert node in used, ("Node '%s' of instance '%s' not found"
494 4a89c54a Iustin Pop
                                " in node list" % (node, instance_name))
495 a81c53c9 Iustin Pop
          if port in used[node]:
496 4a89c54a Iustin Pop
            duplicates.append((node, port, instance_name, used[node][port]))
497 4a89c54a Iustin Pop
          else:
498 4a89c54a Iustin Pop
            used[node][port] = instance_name
499 a81c53c9 Iustin Pop
      if disk.children:
500 a81c53c9 Iustin Pop
        for child in disk.children:
501 4a89c54a Iustin Pop
          duplicates.extend(_AppendUsedPorts(instance_name, child, used))
502 4a89c54a Iustin Pop
      return duplicates
503 a81c53c9 Iustin Pop
504 4a89c54a Iustin Pop
    duplicates = []
505 a81c53c9 Iustin Pop
    my_dict = dict((node, {}) for node in self._config_data.nodes)
506 79b26a7a Iustin Pop
    for instance in self._config_data.instances.itervalues():
507 79b26a7a Iustin Pop
      for disk in instance.disks:
508 79b26a7a Iustin Pop
        duplicates.extend(_AppendUsedPorts(instance.name, disk, my_dict))
509 a81c53c9 Iustin Pop
    for (node, minor), instance in self._temporary_drbds.iteritems():
510 79b26a7a Iustin Pop
      if minor in my_dict[node] and my_dict[node][minor] != instance:
511 4a89c54a Iustin Pop
        duplicates.append((node, minor, instance, my_dict[node][minor]))
512 4a89c54a Iustin Pop
      else:
513 4a89c54a Iustin Pop
        my_dict[node][minor] = instance
514 4a89c54a Iustin Pop
    return my_dict, duplicates
515 a81c53c9 Iustin Pop
516 a81c53c9 Iustin Pop
  @locking.ssynchronized(_config_lock)
517 6d2e83d5 Iustin Pop
  def ComputeDRBDMap(self):
518 6d2e83d5 Iustin Pop
    """Compute the used DRBD minor/nodes.
519 6d2e83d5 Iustin Pop

520 6d2e83d5 Iustin Pop
    This is just a wrapper over L{_UnlockedComputeDRBDMap}.
521 6d2e83d5 Iustin Pop

522 6d2e83d5 Iustin Pop
    @return: dictionary of node_name: dict of minor: instance_name;
523 6d2e83d5 Iustin Pop
        the returned dict will have all the nodes in it (even if with
524 6d2e83d5 Iustin Pop
        an empty list).
525 6d2e83d5 Iustin Pop

526 6d2e83d5 Iustin Pop
    """
527 4a89c54a Iustin Pop
    d_map, duplicates = self._UnlockedComputeDRBDMap()
528 4a89c54a Iustin Pop
    if duplicates:
529 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
530 4a89c54a Iustin Pop
                                      str(duplicates))
531 4a89c54a Iustin Pop
    return d_map
532 6d2e83d5 Iustin Pop
533 6d2e83d5 Iustin Pop
  @locking.ssynchronized(_config_lock)
534 a81c53c9 Iustin Pop
  def AllocateDRBDMinor(self, nodes, instance):
535 a81c53c9 Iustin Pop
    """Allocate a drbd minor.
536 a81c53c9 Iustin Pop

537 a81c53c9 Iustin Pop
    The free minor will be automatically computed from the existing
538 a81c53c9 Iustin Pop
    devices. A node can be given multiple times in order to allocate
539 a81c53c9 Iustin Pop
    multiple minors. The result is the list of minors, in the same
540 a81c53c9 Iustin Pop
    order as the passed nodes.
541 a81c53c9 Iustin Pop

542 32388e6d Iustin Pop
    @type instance: string
543 32388e6d Iustin Pop
    @param instance: the instance for which we allocate minors
544 32388e6d Iustin Pop

545 a81c53c9 Iustin Pop
    """
546 32388e6d Iustin Pop
    assert isinstance(instance, basestring), \
547 4a89c54a Iustin Pop
           "Invalid argument '%s' passed to AllocateDRBDMinor" % instance
548 32388e6d Iustin Pop
549 4a89c54a Iustin Pop
    d_map, duplicates = self._UnlockedComputeDRBDMap()
550 4a89c54a Iustin Pop
    if duplicates:
551 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Duplicate DRBD ports detected: %s" %
552 4a89c54a Iustin Pop
                                      str(duplicates))
553 a81c53c9 Iustin Pop
    result = []
554 a81c53c9 Iustin Pop
    for nname in nodes:
555 a81c53c9 Iustin Pop
      ndata = d_map[nname]
556 a81c53c9 Iustin Pop
      if not ndata:
557 a81c53c9 Iustin Pop
        # no minors used, we can start at 0
558 a81c53c9 Iustin Pop
        result.append(0)
559 a81c53c9 Iustin Pop
        ndata[0] = instance
560 d48663e4 Iustin Pop
        self._temporary_drbds[(nname, 0)] = instance
561 a81c53c9 Iustin Pop
        continue
562 a81c53c9 Iustin Pop
      keys = ndata.keys()
563 a81c53c9 Iustin Pop
      keys.sort()
564 a81c53c9 Iustin Pop
      ffree = utils.FirstFree(keys)
565 a81c53c9 Iustin Pop
      if ffree is None:
566 a81c53c9 Iustin Pop
        # return the next minor
567 a81c53c9 Iustin Pop
        # TODO: implement high-limit check
568 a81c53c9 Iustin Pop
        minor = keys[-1] + 1
569 a81c53c9 Iustin Pop
      else:
570 a81c53c9 Iustin Pop
        minor = ffree
571 4a89c54a Iustin Pop
      # double-check minor against current instances
572 4a89c54a Iustin Pop
      assert minor not in d_map[nname], \
573 4a89c54a Iustin Pop
             ("Attempt to reuse allocated DRBD minor %d on node %s,"
574 4a89c54a Iustin Pop
              " already allocated to instance %s" %
575 4a89c54a Iustin Pop
              (minor, nname, d_map[nname][minor]))
576 a81c53c9 Iustin Pop
      ndata[minor] = instance
577 4a89c54a Iustin Pop
      # double-check minor against reservation
578 4a89c54a Iustin Pop
      r_key = (nname, minor)
579 4a89c54a Iustin Pop
      assert r_key not in self._temporary_drbds, \
580 4a89c54a Iustin Pop
             ("Attempt to reuse reserved DRBD minor %d on node %s,"
581 4a89c54a Iustin Pop
              " reserved for instance %s" %
582 4a89c54a Iustin Pop
              (minor, nname, self._temporary_drbds[r_key]))
583 4a89c54a Iustin Pop
      self._temporary_drbds[r_key] = instance
584 4a89c54a Iustin Pop
      result.append(minor)
585 a81c53c9 Iustin Pop
    logging.debug("Request to allocate drbd minors, input: %s, returning %s",
586 a81c53c9 Iustin Pop
                  nodes, result)
587 a81c53c9 Iustin Pop
    return result
588 a81c53c9 Iustin Pop
589 61cf6b5e Iustin Pop
  def _UnlockedReleaseDRBDMinors(self, instance):
590 a81c53c9 Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
591 a81c53c9 Iustin Pop

592 a81c53c9 Iustin Pop
    @type instance: string
593 a81c53c9 Iustin Pop
    @param instance: the instance for which temporary minors should be
594 a81c53c9 Iustin Pop
                     released
595 a81c53c9 Iustin Pop

596 a81c53c9 Iustin Pop
    """
597 32388e6d Iustin Pop
    assert isinstance(instance, basestring), \
598 32388e6d Iustin Pop
           "Invalid argument passed to ReleaseDRBDMinors"
599 a81c53c9 Iustin Pop
    for key, name in self._temporary_drbds.items():
600 a81c53c9 Iustin Pop
      if name == instance:
601 a81c53c9 Iustin Pop
        del self._temporary_drbds[key]
602 a81c53c9 Iustin Pop
603 61cf6b5e Iustin Pop
  @locking.ssynchronized(_config_lock)
604 61cf6b5e Iustin Pop
  def ReleaseDRBDMinors(self, instance):
605 61cf6b5e Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
606 61cf6b5e Iustin Pop

607 61cf6b5e Iustin Pop
    This should be called on the error paths, on the success paths
608 61cf6b5e Iustin Pop
    it's automatically called by the ConfigWriter add and update
609 61cf6b5e Iustin Pop
    functions.
610 61cf6b5e Iustin Pop

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

613 61cf6b5e Iustin Pop
    @type instance: string
614 61cf6b5e Iustin Pop
    @param instance: the instance for which temporary minors should be
615 61cf6b5e Iustin Pop
                     released
616 61cf6b5e Iustin Pop

617 61cf6b5e Iustin Pop
    """
618 61cf6b5e Iustin Pop
    self._UnlockedReleaseDRBDMinors(instance)
619 61cf6b5e Iustin Pop
620 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
621 4a8b186a Michael Hanselmann
  def GetConfigVersion(self):
622 4a8b186a Michael Hanselmann
    """Get the configuration version.
623 4a8b186a Michael Hanselmann

624 4a8b186a Michael Hanselmann
    @return: Config version
625 4a8b186a Michael Hanselmann

626 4a8b186a Michael Hanselmann
    """
627 4a8b186a Michael Hanselmann
    return self._config_data.version
628 4a8b186a Michael Hanselmann
629 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
630 4a8b186a Michael Hanselmann
  def GetClusterName(self):
631 4a8b186a Michael Hanselmann
    """Get cluster name.
632 4a8b186a Michael Hanselmann

633 4a8b186a Michael Hanselmann
    @return: Cluster name
634 4a8b186a Michael Hanselmann

635 4a8b186a Michael Hanselmann
    """
636 4a8b186a Michael Hanselmann
    return self._config_data.cluster.cluster_name
637 4a8b186a Michael Hanselmann
638 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
639 4a8b186a Michael Hanselmann
  def GetMasterNode(self):
640 4a8b186a Michael Hanselmann
    """Get the hostname of the master node for this cluster.
641 4a8b186a Michael Hanselmann

642 4a8b186a Michael Hanselmann
    @return: Master hostname
643 4a8b186a Michael Hanselmann

644 4a8b186a Michael Hanselmann
    """
645 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_node
646 4a8b186a Michael Hanselmann
647 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
648 4a8b186a Michael Hanselmann
  def GetMasterIP(self):
649 4a8b186a Michael Hanselmann
    """Get the IP of the master node for this cluster.
650 4a8b186a Michael Hanselmann

651 4a8b186a Michael Hanselmann
    @return: Master IP
652 4a8b186a Michael Hanselmann

653 4a8b186a Michael Hanselmann
    """
654 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_ip
655 4a8b186a Michael Hanselmann
656 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
657 4a8b186a Michael Hanselmann
  def GetMasterNetdev(self):
658 4a8b186a Michael Hanselmann
    """Get the master network device for this cluster.
659 4a8b186a Michael Hanselmann

660 4a8b186a Michael Hanselmann
    """
661 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_netdev
662 4a8b186a Michael Hanselmann
663 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
664 4a8b186a Michael Hanselmann
  def GetFileStorageDir(self):
665 4a8b186a Michael Hanselmann
    """Get the file storage dir for this cluster.
666 4a8b186a Michael Hanselmann

667 4a8b186a Michael Hanselmann
    """
668 4a8b186a Michael Hanselmann
    return self._config_data.cluster.file_storage_dir
669 4a8b186a Michael Hanselmann
670 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
671 4a8b186a Michael Hanselmann
  def GetHypervisorType(self):
672 4a8b186a Michael Hanselmann
    """Get the hypervisor type for this cluster.
673 4a8b186a Michael Hanselmann

674 4a8b186a Michael Hanselmann
    """
675 64272529 Iustin Pop
    return self._config_data.cluster.default_hypervisor
676 4a8b186a Michael Hanselmann
677 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
678 a8083063 Iustin Pop
  def GetHostKey(self):
679 a8083063 Iustin Pop
    """Return the rsa hostkey from the config.
680 a8083063 Iustin Pop

681 c41eea6e Iustin Pop
    @rtype: string
682 c41eea6e Iustin Pop
    @return: the rsa hostkey
683 a8083063 Iustin Pop

684 a8083063 Iustin Pop
    """
685 a8083063 Iustin Pop
    return self._config_data.cluster.rsahostkeypub
686 a8083063 Iustin Pop
687 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
688 a8083063 Iustin Pop
  def AddInstance(self, instance):
689 a8083063 Iustin Pop
    """Add an instance to the config.
690 a8083063 Iustin Pop

691 a8083063 Iustin Pop
    This should be used after creating a new instance.
692 a8083063 Iustin Pop

693 c41eea6e Iustin Pop
    @type instance: L{objects.Instance}
694 c41eea6e Iustin Pop
    @param instance: the instance object
695 c41eea6e Iustin Pop

696 a8083063 Iustin Pop
    """
697 a8083063 Iustin Pop
    if not isinstance(instance, objects.Instance):
698 a8083063 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
699 a8083063 Iustin Pop
700 e00fb268 Iustin Pop
    if instance.disk_template != constants.DT_DISKLESS:
701 e00fb268 Iustin Pop
      all_lvs = instance.MapLVsByNode()
702 74a48621 Iustin Pop
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
703 923b1523 Iustin Pop
704 e4640214 Guido Trotter
    all_macs = self._AllMACs()
705 e4640214 Guido Trotter
    for nic in instance.nics:
706 e4640214 Guido Trotter
      if nic.mac in all_macs:
707 e4640214 Guido Trotter
        raise errors.ConfigurationError("Cannot add instance %s:"
708 e4640214 Guido Trotter
          " MAC address '%s' already in use." % (instance.name, nic.mac))
709 e4640214 Guido Trotter
710 b989e85d Iustin Pop
    instance.serial_no = 1
711 a8083063 Iustin Pop
    self._config_data.instances[instance.name] = instance
712 81a49123 Iustin Pop
    self._config_data.cluster.serial_no += 1
713 61cf6b5e Iustin Pop
    self._UnlockedReleaseDRBDMinors(instance.name)
714 e7d81ba0 Iustin Pop
    for nic in instance.nics:
715 e7d81ba0 Iustin Pop
      self._temporary_macs.discard(nic.mac)
716 a8083063 Iustin Pop
    self._WriteConfig()
717 a8083063 Iustin Pop
718 6a408fb2 Iustin Pop
  def _SetInstanceStatus(self, instance_name, status):
719 6a408fb2 Iustin Pop
    """Set the instance's status to a given value.
720 a8083063 Iustin Pop

721 a8083063 Iustin Pop
    """
722 0d68c45d Iustin Pop
    assert isinstance(status, bool), \
723 0d68c45d Iustin Pop
           "Invalid status '%s' passed to SetInstanceStatus" % (status,)
724 a8083063 Iustin Pop
725 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
726 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" %
727 3ecf6786 Iustin Pop
                                      instance_name)
728 a8083063 Iustin Pop
    instance = self._config_data.instances[instance_name]
729 0d68c45d Iustin Pop
    if instance.admin_up != status:
730 0d68c45d Iustin Pop
      instance.admin_up = status
731 b989e85d Iustin Pop
      instance.serial_no += 1
732 455a3445 Iustin Pop
      self._WriteConfig()
733 a8083063 Iustin Pop
734 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
735 6a408fb2 Iustin Pop
  def MarkInstanceUp(self, instance_name):
736 6a408fb2 Iustin Pop
    """Mark the instance status to up in the config.
737 6a408fb2 Iustin Pop

738 6a408fb2 Iustin Pop
    """
739 0d68c45d Iustin Pop
    self._SetInstanceStatus(instance_name, True)
740 6a408fb2 Iustin Pop
741 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
742 a8083063 Iustin Pop
  def RemoveInstance(self, instance_name):
743 a8083063 Iustin Pop
    """Remove the instance from the configuration.
744 a8083063 Iustin Pop

745 a8083063 Iustin Pop
    """
746 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
747 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
748 a8083063 Iustin Pop
    del self._config_data.instances[instance_name]
749 81a49123 Iustin Pop
    self._config_data.cluster.serial_no += 1
750 a8083063 Iustin Pop
    self._WriteConfig()
751 a8083063 Iustin Pop
752 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
753 fc95f88f Iustin Pop
  def RenameInstance(self, old_name, new_name):
754 fc95f88f Iustin Pop
    """Rename an instance.
755 fc95f88f Iustin Pop

756 fc95f88f Iustin Pop
    This needs to be done in ConfigWriter and not by RemoveInstance
757 fc95f88f Iustin Pop
    combined with AddInstance as only we can guarantee an atomic
758 fc95f88f Iustin Pop
    rename.
759 fc95f88f Iustin Pop

760 fc95f88f Iustin Pop
    """
761 fc95f88f Iustin Pop
    if old_name not in self._config_data.instances:
762 fc95f88f Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
763 fc95f88f Iustin Pop
    inst = self._config_data.instances[old_name]
764 fc95f88f Iustin Pop
    del self._config_data.instances[old_name]
765 fc95f88f Iustin Pop
    inst.name = new_name
766 b23c4333 Manuel Franceschini
767 b23c4333 Manuel Franceschini
    for disk in inst.disks:
768 b23c4333 Manuel Franceschini
      if disk.dev_type == constants.LD_FILE:
769 b23c4333 Manuel Franceschini
        # rename the file paths in logical and physical id
770 b23c4333 Manuel Franceschini
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
771 b23c4333 Manuel Franceschini
        disk.physical_id = disk.logical_id = (disk.logical_id[0],
772 b23c4333 Manuel Franceschini
                                              os.path.join(file_storage_dir,
773 b23c4333 Manuel Franceschini
                                                           inst.name,
774 b23c4333 Manuel Franceschini
                                                           disk.iv_name))
775 b23c4333 Manuel Franceschini
776 fc95f88f Iustin Pop
    self._config_data.instances[inst.name] = inst
777 fc95f88f Iustin Pop
    self._WriteConfig()
778 fc95f88f Iustin Pop
779 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
780 a8083063 Iustin Pop
  def MarkInstanceDown(self, instance_name):
781 a8083063 Iustin Pop
    """Mark the status of an instance to down in the configuration.
782 a8083063 Iustin Pop

783 a8083063 Iustin Pop
    """
784 0d68c45d Iustin Pop
    self._SetInstanceStatus(instance_name, False)
785 a8083063 Iustin Pop
786 94bbfece Iustin Pop
  def _UnlockedGetInstanceList(self):
787 94bbfece Iustin Pop
    """Get the list of instances.
788 94bbfece Iustin Pop

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

791 94bbfece Iustin Pop
    """
792 94bbfece Iustin Pop
    return self._config_data.instances.keys()
793 94bbfece Iustin Pop
794 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
795 a8083063 Iustin Pop
  def GetInstanceList(self):
796 a8083063 Iustin Pop
    """Get the list of instances.
797 a8083063 Iustin Pop

798 c41eea6e Iustin Pop
    @return: array of instances, ex. ['instance2.example.com',
799 c41eea6e Iustin Pop
        'instance1.example.com']
800 a8083063 Iustin Pop

801 a8083063 Iustin Pop
    """
802 94bbfece Iustin Pop
    return self._UnlockedGetInstanceList()
803 a8083063 Iustin Pop
804 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
805 a8083063 Iustin Pop
  def ExpandInstanceName(self, short_name):
806 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
807 a8083063 Iustin Pop

808 a8083063 Iustin Pop
    """
809 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
810 a8083063 Iustin Pop
                                    self._config_data.instances.keys())
811 a8083063 Iustin Pop
812 94bbfece Iustin Pop
  def _UnlockedGetInstanceInfo(self, instance_name):
813 5bbd3f7f Michael Hanselmann
    """Returns information about an instance.
814 94bbfece Iustin Pop

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

817 94bbfece Iustin Pop
    """
818 94bbfece Iustin Pop
    if instance_name not in self._config_data.instances:
819 94bbfece Iustin Pop
      return None
820 94bbfece Iustin Pop
821 94bbfece Iustin Pop
    return self._config_data.instances[instance_name]
822 94bbfece Iustin Pop
823 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
824 a8083063 Iustin Pop
  def GetInstanceInfo(self, instance_name):
825 5bbd3f7f Michael Hanselmann
    """Returns information about an instance.
826 a8083063 Iustin Pop

827 5bbd3f7f Michael Hanselmann
    It takes the information from the configuration file. Other information of
828 a8083063 Iustin Pop
    an instance are taken from the live systems.
829 a8083063 Iustin Pop

830 c41eea6e Iustin Pop
    @param instance_name: name of the instance, e.g.
831 c41eea6e Iustin Pop
        I{instance1.example.com}
832 a8083063 Iustin Pop

833 c41eea6e Iustin Pop
    @rtype: L{objects.Instance}
834 c41eea6e Iustin Pop
    @return: the instance object
835 a8083063 Iustin Pop

836 a8083063 Iustin Pop
    """
837 94bbfece Iustin Pop
    return self._UnlockedGetInstanceInfo(instance_name)
838 a8083063 Iustin Pop
839 0b2de758 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
840 0b2de758 Iustin Pop
  def GetAllInstancesInfo(self):
841 0b2de758 Iustin Pop
    """Get the configuration of all instances.
842 0b2de758 Iustin Pop

843 0b2de758 Iustin Pop
    @rtype: dict
844 5fcc718f Iustin Pop
    @return: dict of (instance, instance_info), where instance_info is what
845 0b2de758 Iustin Pop
              would GetInstanceInfo return for the node
846 0b2de758 Iustin Pop

847 0b2de758 Iustin Pop
    """
848 64d3bd52 Guido Trotter
    my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
849 64d3bd52 Guido Trotter
                    for instance in self._UnlockedGetInstanceList()])
850 0b2de758 Iustin Pop
    return my_dict
851 0b2de758 Iustin Pop
852 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
853 a8083063 Iustin Pop
  def AddNode(self, node):
854 a8083063 Iustin Pop
    """Add a node to the configuration.
855 a8083063 Iustin Pop

856 c41eea6e Iustin Pop
    @type node: L{objects.Node}
857 c41eea6e Iustin Pop
    @param node: a Node instance
858 a8083063 Iustin Pop

859 a8083063 Iustin Pop
    """
860 d8470559 Michael Hanselmann
    logging.info("Adding node %s to configuration" % node.name)
861 d8470559 Michael Hanselmann
862 b989e85d Iustin Pop
    node.serial_no = 1
863 a8083063 Iustin Pop
    self._config_data.nodes[node.name] = node
864 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
865 a8083063 Iustin Pop
    self._WriteConfig()
866 a8083063 Iustin Pop
867 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
868 a8083063 Iustin Pop
  def RemoveNode(self, node_name):
869 a8083063 Iustin Pop
    """Remove a node from the configuration.
870 a8083063 Iustin Pop

871 a8083063 Iustin Pop
    """
872 d8470559 Michael Hanselmann
    logging.info("Removing node %s from configuration" % node_name)
873 d8470559 Michael Hanselmann
874 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
875 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
876 a8083063 Iustin Pop
877 a8083063 Iustin Pop
    del self._config_data.nodes[node_name]
878 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
879 a8083063 Iustin Pop
    self._WriteConfig()
880 a8083063 Iustin Pop
881 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
882 a8083063 Iustin Pop
  def ExpandNodeName(self, short_name):
883 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
884 a8083063 Iustin Pop

885 a8083063 Iustin Pop
    """
886 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
887 a8083063 Iustin Pop
                                    self._config_data.nodes.keys())
888 a8083063 Iustin Pop
889 f78ede4e Guido Trotter
  def _UnlockedGetNodeInfo(self, node_name):
890 a8083063 Iustin Pop
    """Get the configuration of a node, as stored in the config.
891 a8083063 Iustin Pop

892 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
893 c41eea6e Iustin Pop
    held.
894 f78ede4e Guido Trotter

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

897 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
898 c41eea6e Iustin Pop
    @return: the node object
899 a8083063 Iustin Pop

900 a8083063 Iustin Pop
    """
901 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
902 a8083063 Iustin Pop
      return None
903 a8083063 Iustin Pop
904 a8083063 Iustin Pop
    return self._config_data.nodes[node_name]
905 a8083063 Iustin Pop
906 f78ede4e Guido Trotter
907 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
908 f78ede4e Guido Trotter
  def GetNodeInfo(self, node_name):
909 f78ede4e Guido Trotter
    """Get the configuration of a node, as stored in the config.
910 f78ede4e Guido Trotter

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

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

915 c41eea6e Iustin Pop
    @rtype: L{objects.Node}
916 c41eea6e Iustin Pop
    @return: the node object
917 f78ede4e Guido Trotter

918 f78ede4e Guido Trotter
    """
919 f78ede4e Guido Trotter
    return self._UnlockedGetNodeInfo(node_name)
920 f78ede4e Guido Trotter
921 f78ede4e Guido Trotter
  def _UnlockedGetNodeList(self):
922 a8083063 Iustin Pop
    """Return the list of nodes which are in the configuration.
923 a8083063 Iustin Pop

924 c41eea6e Iustin Pop
    This function is for internal use, when the config lock is already
925 c41eea6e Iustin Pop
    held.
926 c41eea6e Iustin Pop

927 c41eea6e Iustin Pop
    @rtype: list
928 f78ede4e Guido Trotter

929 a8083063 Iustin Pop
    """
930 a8083063 Iustin Pop
    return self._config_data.nodes.keys()
931 a8083063 Iustin Pop
932 f78ede4e Guido Trotter
933 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
934 f78ede4e Guido Trotter
  def GetNodeList(self):
935 f78ede4e Guido Trotter
    """Return the list of nodes which are in the configuration.
936 f78ede4e Guido Trotter

937 f78ede4e Guido Trotter
    """
938 f78ede4e Guido Trotter
    return self._UnlockedGetNodeList()
939 f78ede4e Guido Trotter
940 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
941 94a02bb5 Iustin Pop
  def GetOnlineNodeList(self):
942 94a02bb5 Iustin Pop
    """Return the list of nodes which are online.
943 94a02bb5 Iustin Pop

944 94a02bb5 Iustin Pop
    """
945 94a02bb5 Iustin Pop
    all_nodes = [self._UnlockedGetNodeInfo(node)
946 94a02bb5 Iustin Pop
                 for node in self._UnlockedGetNodeList()]
947 94a02bb5 Iustin Pop
    return [node.name for node in all_nodes if not node.offline]
948 94a02bb5 Iustin Pop
949 94a02bb5 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
950 d65e5776 Iustin Pop
  def GetAllNodesInfo(self):
951 d65e5776 Iustin Pop
    """Get the configuration of all nodes.
952 d65e5776 Iustin Pop

953 d65e5776 Iustin Pop
    @rtype: dict
954 ec0292f1 Iustin Pop
    @return: dict of (node, node_info), where node_info is what
955 d65e5776 Iustin Pop
              would GetNodeInfo return for the node
956 d65e5776 Iustin Pop

957 d65e5776 Iustin Pop
    """
958 d65e5776 Iustin Pop
    my_dict = dict([(node, self._UnlockedGetNodeInfo(node))
959 d65e5776 Iustin Pop
                    for node in self._UnlockedGetNodeList()])
960 d65e5776 Iustin Pop
    return my_dict
961 d65e5776 Iustin Pop
962 23f06b2b Iustin Pop
  def _UnlockedGetMasterCandidateStats(self, exceptions=None):
963 ec0292f1 Iustin Pop
    """Get the number of current and maximum desired and possible candidates.
964 ec0292f1 Iustin Pop

965 23f06b2b Iustin Pop
    @type exceptions: list
966 23f06b2b Iustin Pop
    @param exceptions: if passed, list of nodes that should be ignored
967 ec0292f1 Iustin Pop
    @rtype: tuple
968 ec0292f1 Iustin Pop
    @return: tuple of (current, desired and possible)
969 ec0292f1 Iustin Pop

970 ec0292f1 Iustin Pop
    """
971 ec0292f1 Iustin Pop
    mc_now = mc_max = 0
972 23f06b2b Iustin Pop
    for node in self._config_data.nodes.values():
973 23f06b2b Iustin Pop
      if exceptions and node.name in exceptions:
974 23f06b2b Iustin Pop
        continue
975 5bf07049 Iustin Pop
      if not (node.offline or node.drained):
976 ec0292f1 Iustin Pop
        mc_max += 1
977 ec0292f1 Iustin Pop
      if node.master_candidate:
978 ec0292f1 Iustin Pop
        mc_now += 1
979 ec0292f1 Iustin Pop
    mc_max = min(mc_max, self._config_data.cluster.candidate_pool_size)
980 ec0292f1 Iustin Pop
    return (mc_now, mc_max)
981 ec0292f1 Iustin Pop
982 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
983 23f06b2b Iustin Pop
  def GetMasterCandidateStats(self, exceptions=None):
984 ec0292f1 Iustin Pop
    """Get the number of current and maximum possible candidates.
985 ec0292f1 Iustin Pop

986 ec0292f1 Iustin Pop
    This is just a wrapper over L{_UnlockedGetMasterCandidateStats}.
987 ec0292f1 Iustin Pop

988 23f06b2b Iustin Pop
    @type exceptions: list
989 23f06b2b Iustin Pop
    @param exceptions: if passed, list of nodes that should be ignored
990 ec0292f1 Iustin Pop
    @rtype: tuple
991 ec0292f1 Iustin Pop
    @return: tuple of (current, max)
992 ec0292f1 Iustin Pop

993 ec0292f1 Iustin Pop
    """
994 23f06b2b Iustin Pop
    return self._UnlockedGetMasterCandidateStats(exceptions)
995 ec0292f1 Iustin Pop
996 ec0292f1 Iustin Pop
  @locking.ssynchronized(_config_lock)
997 ec0292f1 Iustin Pop
  def MaintainCandidatePool(self):
998 ec0292f1 Iustin Pop
    """Try to grow the candidate pool to the desired size.
999 ec0292f1 Iustin Pop

1000 ec0292f1 Iustin Pop
    @rtype: list
1001 ee513a66 Iustin Pop
    @return: list with the adjusted nodes (L{objects.Node} instances)
1002 ec0292f1 Iustin Pop

1003 ec0292f1 Iustin Pop
    """
1004 ec0292f1 Iustin Pop
    mc_now, mc_max = self._UnlockedGetMasterCandidateStats()
1005 ec0292f1 Iustin Pop
    mod_list = []
1006 ec0292f1 Iustin Pop
    if mc_now < mc_max:
1007 ec0292f1 Iustin Pop
      node_list = self._config_data.nodes.keys()
1008 ec0292f1 Iustin Pop
      random.shuffle(node_list)
1009 ec0292f1 Iustin Pop
      for name in node_list:
1010 ec0292f1 Iustin Pop
        if mc_now >= mc_max:
1011 ec0292f1 Iustin Pop
          break
1012 ec0292f1 Iustin Pop
        node = self._config_data.nodes[name]
1013 5bf07049 Iustin Pop
        if node.master_candidate or node.offline or node.drained:
1014 ec0292f1 Iustin Pop
          continue
1015 ee513a66 Iustin Pop
        mod_list.append(node)
1016 ec0292f1 Iustin Pop
        node.master_candidate = True
1017 ec0292f1 Iustin Pop
        node.serial_no += 1
1018 ec0292f1 Iustin Pop
        mc_now += 1
1019 ec0292f1 Iustin Pop
      if mc_now != mc_max:
1020 ec0292f1 Iustin Pop
        # this should not happen
1021 ec0292f1 Iustin Pop
        logging.warning("Warning: MaintainCandidatePool didn't manage to"
1022 ec0292f1 Iustin Pop
                        " fill the candidate pool (%d/%d)", mc_now, mc_max)
1023 ec0292f1 Iustin Pop
      if mod_list:
1024 ec0292f1 Iustin Pop
        self._config_data.cluster.serial_no += 1
1025 ec0292f1 Iustin Pop
        self._WriteConfig()
1026 ec0292f1 Iustin Pop
1027 ec0292f1 Iustin Pop
    return mod_list
1028 ec0292f1 Iustin Pop
1029 a8083063 Iustin Pop
  def _BumpSerialNo(self):
1030 a8083063 Iustin Pop
    """Bump up the serial number of the config.
1031 a8083063 Iustin Pop

1032 a8083063 Iustin Pop
    """
1033 9d38c6e1 Iustin Pop
    self._config_data.serial_no += 1
1034 a8083063 Iustin Pop
1035 a8083063 Iustin Pop
  def _OpenConfig(self):
1036 a8083063 Iustin Pop
    """Read the config data from disk.
1037 a8083063 Iustin Pop

1038 a8083063 Iustin Pop
    """
1039 a8083063 Iustin Pop
    f = open(self._cfg_file, 'r')
1040 a8083063 Iustin Pop
    try:
1041 a8083063 Iustin Pop
      try:
1042 8d14b30d Iustin Pop
        data = objects.ConfigData.FromDict(serializer.Load(f.read()))
1043 a8083063 Iustin Pop
      except Exception, err:
1044 3ecf6786 Iustin Pop
        raise errors.ConfigurationError(err)
1045 a8083063 Iustin Pop
    finally:
1046 a8083063 Iustin Pop
      f.close()
1047 5b263ed7 Michael Hanselmann
1048 5b263ed7 Michael Hanselmann
    # Make sure the configuration has the right version
1049 5b263ed7 Michael Hanselmann
    _ValidateConfig(data)
1050 5b263ed7 Michael Hanselmann
1051 a8083063 Iustin Pop
    if (not hasattr(data, 'cluster') or
1052 243cdbcc Michael Hanselmann
        not hasattr(data.cluster, 'rsahostkeypub')):
1053 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Incomplete configuration"
1054 243cdbcc Michael Hanselmann
                                      " (missing cluster.rsahostkeypub)")
1055 a8083063 Iustin Pop
    self._config_data = data
1056 3c7f6c44 Iustin Pop
    # reset the last serial as -1 so that the next write will cause
1057 0779e3aa Iustin Pop
    # ssconf update
1058 0779e3aa Iustin Pop
    self._last_cluster_serial = -1
1059 a8083063 Iustin Pop
1060 a8083063 Iustin Pop
  def _DistributeConfig(self):
1061 a8083063 Iustin Pop
    """Distribute the configuration to the other nodes.
1062 a8083063 Iustin Pop

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

1066 a8083063 Iustin Pop
    """
1067 a8083063 Iustin Pop
    if self._offline:
1068 a8083063 Iustin Pop
      return True
1069 a8083063 Iustin Pop
    bad = False
1070 a8083063 Iustin Pop
1071 6a5b8b4b Iustin Pop
    node_list = []
1072 6a5b8b4b Iustin Pop
    addr_list = []
1073 6a5b8b4b Iustin Pop
    myhostname = self._my_hostname
1074 6b294c53 Iustin Pop
    # we can skip checking whether _UnlockedGetNodeInfo returns None
1075 6b294c53 Iustin Pop
    # since the node list comes from _UnlocketGetNodeList, and we are
1076 6b294c53 Iustin Pop
    # called with the lock held, so no modifications should take place
1077 6b294c53 Iustin Pop
    # in between
1078 6a5b8b4b Iustin Pop
    for node_name in self._UnlockedGetNodeList():
1079 6a5b8b4b Iustin Pop
      if node_name == myhostname:
1080 6a5b8b4b Iustin Pop
        continue
1081 6a5b8b4b Iustin Pop
      node_info = self._UnlockedGetNodeInfo(node_name)
1082 6a5b8b4b Iustin Pop
      if not node_info.master_candidate:
1083 6a5b8b4b Iustin Pop
        continue
1084 6a5b8b4b Iustin Pop
      node_list.append(node_info.name)
1085 6a5b8b4b Iustin Pop
      addr_list.append(node_info.primary_ip)
1086 6b294c53 Iustin Pop
1087 6a5b8b4b Iustin Pop
    result = rpc.RpcRunner.call_upload_file(node_list, self._cfg_file,
1088 6a5b8b4b Iustin Pop
                                            address_list=addr_list)
1089 6a5b8b4b Iustin Pop
    for node in node_list:
1090 a8083063 Iustin Pop
      if not result[node]:
1091 74a48621 Iustin Pop
        logging.error("copy of file %s to node %s failed",
1092 74a48621 Iustin Pop
                      self._cfg_file, node)
1093 a8083063 Iustin Pop
        bad = True
1094 a8083063 Iustin Pop
    return not bad
1095 a8083063 Iustin Pop
1096 a8083063 Iustin Pop
  def _WriteConfig(self, destination=None):
1097 a8083063 Iustin Pop
    """Write the configuration data to persistent storage.
1098 a8083063 Iustin Pop

1099 a8083063 Iustin Pop
    """
1100 4a89c54a Iustin Pop
    config_errors = self._UnlockedVerifyConfig()
1101 4a89c54a Iustin Pop
    if config_errors:
1102 4a89c54a Iustin Pop
      raise errors.ConfigurationError("Configuration data is not"
1103 4a89c54a Iustin Pop
                                      " consistent: %s" %
1104 4a89c54a Iustin Pop
                                      (", ".join(config_errors)))
1105 a8083063 Iustin Pop
    if destination is None:
1106 a8083063 Iustin Pop
      destination = self._cfg_file
1107 a8083063 Iustin Pop
    self._BumpSerialNo()
1108 8d14b30d Iustin Pop
    txt = serializer.Dump(self._config_data.ToDict())
1109 a8083063 Iustin Pop
    dir_name, file_name = os.path.split(destination)
1110 a8083063 Iustin Pop
    fd, name = tempfile.mkstemp('.newconfig', file_name, dir_name)
1111 a8083063 Iustin Pop
    f = os.fdopen(fd, 'w')
1112 a8083063 Iustin Pop
    try:
1113 8d14b30d Iustin Pop
      f.write(txt)
1114 a8083063 Iustin Pop
      os.fsync(f.fileno())
1115 a8083063 Iustin Pop
    finally:
1116 a8083063 Iustin Pop
      f.close()
1117 a8083063 Iustin Pop
    # we don't need to do os.close(fd) as f.close() did it
1118 a8083063 Iustin Pop
    os.rename(name, destination)
1119 14e15659 Iustin Pop
    self.write_count += 1
1120 3d3a04bc Iustin Pop
1121 f56618e0 Iustin Pop
    # and redistribute the config file to master candidates
1122 a8083063 Iustin Pop
    self._DistributeConfig()
1123 a8083063 Iustin Pop
1124 54d1a06e Michael Hanselmann
    # Write ssconf files on all nodes (including locally)
1125 0779e3aa Iustin Pop
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
1126 d9a855f1 Michael Hanselmann
      if not self._offline:
1127 03d1dba2 Michael Hanselmann
        rpc.RpcRunner.call_write_ssconf_files(self._UnlockedGetNodeList(),
1128 03d1dba2 Michael Hanselmann
                                              self._UnlockedGetSsconfValues())
1129 0779e3aa Iustin Pop
      self._last_cluster_serial = self._config_data.cluster.serial_no
1130 54d1a06e Michael Hanselmann
1131 03d1dba2 Michael Hanselmann
  def _UnlockedGetSsconfValues(self):
1132 054596f0 Iustin Pop
    """Return the values needed by ssconf.
1133 054596f0 Iustin Pop

1134 054596f0 Iustin Pop
    @rtype: dict
1135 054596f0 Iustin Pop
    @return: a dictionary with keys the ssconf names and values their
1136 054596f0 Iustin Pop
        associated value
1137 054596f0 Iustin Pop

1138 054596f0 Iustin Pop
    """
1139 a3316e4a Iustin Pop
    fn = "\n".join
1140 81a49123 Iustin Pop
    instance_names = utils.NiceSort(self._UnlockedGetInstanceList())
1141 a3316e4a Iustin Pop
    node_names = utils.NiceSort(self._UnlockedGetNodeList())
1142 a3316e4a Iustin Pop
    node_info = [self._UnlockedGetNodeInfo(name) for name in node_names]
1143 a3316e4a Iustin Pop
1144 81a49123 Iustin Pop
    instance_data = fn(instance_names)
1145 a3316e4a Iustin Pop
    off_data = fn(node.name for node in node_info if node.offline)
1146 81a49123 Iustin Pop
    on_data = fn(node.name for node in node_info if not node.offline)
1147 a3316e4a Iustin Pop
    mc_data = fn(node.name for node in node_info if node.master_candidate)
1148 a3316e4a Iustin Pop
    node_data = fn(node_names)
1149 f56618e0 Iustin Pop
1150 054596f0 Iustin Pop
    cluster = self._config_data.cluster
1151 5d60b3bd Iustin Pop
    cluster_tags = fn(cluster.GetTags())
1152 03d1dba2 Michael Hanselmann
    return {
1153 054596f0 Iustin Pop
      constants.SS_CLUSTER_NAME: cluster.cluster_name,
1154 5d60b3bd Iustin Pop
      constants.SS_CLUSTER_TAGS: cluster_tags,
1155 054596f0 Iustin Pop
      constants.SS_FILE_STORAGE_DIR: cluster.file_storage_dir,
1156 a3316e4a Iustin Pop
      constants.SS_MASTER_CANDIDATES: mc_data,
1157 054596f0 Iustin Pop
      constants.SS_MASTER_IP: cluster.master_ip,
1158 054596f0 Iustin Pop
      constants.SS_MASTER_NETDEV: cluster.master_netdev,
1159 054596f0 Iustin Pop
      constants.SS_MASTER_NODE: cluster.master_node,
1160 a3316e4a Iustin Pop
      constants.SS_NODE_LIST: node_data,
1161 a3316e4a Iustin Pop
      constants.SS_OFFLINE_NODES: off_data,
1162 81a49123 Iustin Pop
      constants.SS_ONLINE_NODES: on_data,
1163 81a49123 Iustin Pop
      constants.SS_INSTANCE_LIST: instance_data,
1164 8a113c7a Iustin Pop
      constants.SS_RELEASE_VERSION: constants.RELEASE_VERSION,
1165 03d1dba2 Michael Hanselmann
      }
1166 03d1dba2 Michael Hanselmann
1167 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1168 a8083063 Iustin Pop
  def GetVGName(self):
1169 a8083063 Iustin Pop
    """Return the volume group name.
1170 a8083063 Iustin Pop

1171 a8083063 Iustin Pop
    """
1172 a8083063 Iustin Pop
    return self._config_data.cluster.volume_group_name
1173 a8083063 Iustin Pop
1174 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1175 89ff8e15 Manuel Franceschini
  def SetVGName(self, vg_name):
1176 89ff8e15 Manuel Franceschini
    """Set the volume group name.
1177 89ff8e15 Manuel Franceschini

1178 89ff8e15 Manuel Franceschini
    """
1179 2d4011cd Manuel Franceschini
    self._config_data.cluster.volume_group_name = vg_name
1180 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
1181 89ff8e15 Manuel Franceschini
    self._WriteConfig()
1182 89ff8e15 Manuel Franceschini
1183 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1184 a8083063 Iustin Pop
  def GetDefBridge(self):
1185 a8083063 Iustin Pop
    """Return the default bridge.
1186 a8083063 Iustin Pop

1187 a8083063 Iustin Pop
    """
1188 a8083063 Iustin Pop
    return self._config_data.cluster.default_bridge
1189 a8083063 Iustin Pop
1190 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1191 a8083063 Iustin Pop
  def GetMACPrefix(self):
1192 a8083063 Iustin Pop
    """Return the mac prefix.
1193 a8083063 Iustin Pop

1194 a8083063 Iustin Pop
    """
1195 a8083063 Iustin Pop
    return self._config_data.cluster.mac_prefix
1196 62779dd0 Iustin Pop
1197 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
1198 62779dd0 Iustin Pop
  def GetClusterInfo(self):
1199 5bbd3f7f Michael Hanselmann
    """Returns information about the cluster
1200 62779dd0 Iustin Pop

1201 c41eea6e Iustin Pop
    @rtype: L{objects.Cluster}
1202 c41eea6e Iustin Pop
    @return: the cluster object
1203 62779dd0 Iustin Pop

1204 62779dd0 Iustin Pop
    """
1205 62779dd0 Iustin Pop
    return self._config_data.cluster
1206 e00fb268 Iustin Pop
1207 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
1208 e00fb268 Iustin Pop
  def Update(self, target):
1209 e00fb268 Iustin Pop
    """Notify function to be called after updates.
1210 e00fb268 Iustin Pop

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

1217 c41eea6e Iustin Pop
    @param target: an instance of either L{objects.Cluster},
1218 c41eea6e Iustin Pop
        L{objects.Node} or L{objects.Instance} which is existing in
1219 c41eea6e Iustin Pop
        the cluster
1220 c41eea6e Iustin Pop

1221 e00fb268 Iustin Pop
    """
1222 e00fb268 Iustin Pop
    if self._config_data is None:
1223 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Configuration file not read,"
1224 3ecf6786 Iustin Pop
                                   " cannot save.")
1225 f34901f8 Iustin Pop
    update_serial = False
1226 e00fb268 Iustin Pop
    if isinstance(target, objects.Cluster):
1227 e00fb268 Iustin Pop
      test = target == self._config_data.cluster
1228 e00fb268 Iustin Pop
    elif isinstance(target, objects.Node):
1229 e00fb268 Iustin Pop
      test = target in self._config_data.nodes.values()
1230 f34901f8 Iustin Pop
      update_serial = True
1231 e00fb268 Iustin Pop
    elif isinstance(target, objects.Instance):
1232 e00fb268 Iustin Pop
      test = target in self._config_data.instances.values()
1233 e00fb268 Iustin Pop
    else:
1234 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
1235 3ecf6786 Iustin Pop
                                   " ConfigWriter.Update" % type(target))
1236 e00fb268 Iustin Pop
    if not test:
1237 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Configuration updated since object"
1238 3ecf6786 Iustin Pop
                                      " has been read or unknown object")
1239 f34901f8 Iustin Pop
    target.serial_no += 1
1240 f34901f8 Iustin Pop
1241 cff4c037 Iustin Pop
    if update_serial:
1242 f34901f8 Iustin Pop
      # for node updates, we need to increase the cluster serial too
1243 f34901f8 Iustin Pop
      self._config_data.cluster.serial_no += 1
1244 b989e85d Iustin Pop
1245 61cf6b5e Iustin Pop
    if isinstance(target, objects.Instance):
1246 61cf6b5e Iustin Pop
      self._UnlockedReleaseDRBDMinors(target.name)
1247 e7d81ba0 Iustin Pop
      for nic in target.nics:
1248 e7d81ba0 Iustin Pop
        self._temporary_macs.discard(nic.mac)
1249 61cf6b5e Iustin Pop
1250 e00fb268 Iustin Pop
    self._WriteConfig()