Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 5b263ed7

History | View | Annotate | Download (31.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 5b263ed7 Michael Hanselmann
  if data.version != constants.CONFIG_VERSION:
53 243cdbcc Michael Hanselmann
    raise errors.ConfigurationError("Cluster configuration version"
54 243cdbcc Michael Hanselmann
                                    " mismatch, got %s instead of %s" %
55 5b263ed7 Michael Hanselmann
                                    (data.version,
56 243cdbcc Michael Hanselmann
                                     constants.CONFIG_VERSION))
57 a8083063 Iustin Pop
58 319856a9 Michael Hanselmann
59 a8083063 Iustin Pop
class ConfigWriter:
60 098c0958 Michael Hanselmann
  """The interface to the cluster configuration.
61 a8083063 Iustin Pop

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

88 a8083063 Iustin Pop
    """
89 a8083063 Iustin Pop
    return os.path.exists(constants.CLUSTER_CONF_FILE)
90 a8083063 Iustin Pop
91 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
92 a8083063 Iustin Pop
  def GenerateMAC(self):
93 a8083063 Iustin Pop
    """Generate a MAC for an instance.
94 a8083063 Iustin Pop

95 a8083063 Iustin Pop
    This should check the current instances for duplicates.
96 a8083063 Iustin Pop

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

118 1862d460 Alexander Schreiber
    This only checks instances managed by this cluster, it does not
119 1862d460 Alexander Schreiber
    check for potential collisions elsewhere.
120 1862d460 Alexander Schreiber

121 1862d460 Alexander Schreiber
    """
122 1862d460 Alexander Schreiber
    self._OpenConfig()
123 1862d460 Alexander Schreiber
    all_macs = self._AllMACs()
124 1862d460 Alexander Schreiber
    return mac in all_macs
125 1862d460 Alexander Schreiber
126 f9518d38 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
127 f9518d38 Iustin Pop
  def GenerateDRBDSecret(self):
128 f9518d38 Iustin Pop
    """Generate a DRBD secret.
129 f9518d38 Iustin Pop

130 f9518d38 Iustin Pop
    This checks the current disks for duplicates.
131 f9518d38 Iustin Pop

132 f9518d38 Iustin Pop
    """
133 f9518d38 Iustin Pop
    self._OpenConfig()
134 f9518d38 Iustin Pop
    all_secrets = self._AllDRBDSecrets()
135 f9518d38 Iustin Pop
    retries = 64
136 f9518d38 Iustin Pop
    while retries > 0:
137 f9518d38 Iustin Pop
      secret = utils.GenerateSecret()
138 f9518d38 Iustin Pop
      if secret not in all_secrets:
139 f9518d38 Iustin Pop
        break
140 f9518d38 Iustin Pop
      retries -= 1
141 f9518d38 Iustin Pop
    else:
142 f9518d38 Iustin Pop
      raise errors.ConfigurationError("Can't generate unique DRBD secret")
143 f9518d38 Iustin Pop
    return secret
144 f9518d38 Iustin Pop
145 923b1523 Iustin Pop
  def _ComputeAllLVs(self):
146 923b1523 Iustin Pop
    """Compute the list of all LVs.
147 923b1523 Iustin Pop

148 923b1523 Iustin Pop
    """
149 923b1523 Iustin Pop
    self._OpenConfig()
150 923b1523 Iustin Pop
    lvnames = set()
151 923b1523 Iustin Pop
    for instance in self._config_data.instances.values():
152 923b1523 Iustin Pop
      node_data = instance.MapLVsByNode()
153 923b1523 Iustin Pop
      for lv_list in node_data.values():
154 923b1523 Iustin Pop
        lvnames.update(lv_list)
155 923b1523 Iustin Pop
    return lvnames
156 923b1523 Iustin Pop
157 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
158 923b1523 Iustin Pop
  def GenerateUniqueID(self, exceptions=None):
159 923b1523 Iustin Pop
    """Generate an unique disk name.
160 923b1523 Iustin Pop

161 923b1523 Iustin Pop
    This checks the current node, instances and disk names for
162 923b1523 Iustin Pop
    duplicates.
163 923b1523 Iustin Pop

164 923b1523 Iustin Pop
    Args:
165 923b1523 Iustin Pop
      - exceptions: a list with some other names which should be checked
166 923b1523 Iustin Pop
                    for uniqueness (used for example when you want to get
167 923b1523 Iustin Pop
                    more than one id at one time without adding each one in
168 923b1523 Iustin Pop
                    turn to the config file
169 923b1523 Iustin Pop

170 923b1523 Iustin Pop
    Returns: the unique id as a string
171 923b1523 Iustin Pop

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

194 a8083063 Iustin Pop
    """
195 a8083063 Iustin Pop
    self._OpenConfig()
196 a8083063 Iustin Pop
197 a8083063 Iustin Pop
    result = []
198 a8083063 Iustin Pop
    for instance in self._config_data.instances.values():
199 a8083063 Iustin Pop
      for nic in instance.nics:
200 a8083063 Iustin Pop
        result.append(nic.mac)
201 a8083063 Iustin Pop
202 a8083063 Iustin Pop
    return result
203 a8083063 Iustin Pop
204 f9518d38 Iustin Pop
  def _AllDRBDSecrets(self):
205 f9518d38 Iustin Pop
    """Return all DRBD secrets present in the config.
206 f9518d38 Iustin Pop

207 f9518d38 Iustin Pop
    """
208 f9518d38 Iustin Pop
    def helper(disk, result):
209 f9518d38 Iustin Pop
      """Recursively gather secrets from this disk."""
210 f9518d38 Iustin Pop
      if disk.dev_type == constants.DT_DRBD8:
211 f9518d38 Iustin Pop
        result.append(disk.logical_id[5])
212 f9518d38 Iustin Pop
      if disk.children:
213 f9518d38 Iustin Pop
        for child in disk.children:
214 f9518d38 Iustin Pop
          helper(child, result)
215 f9518d38 Iustin Pop
216 f9518d38 Iustin Pop
    result = []
217 f9518d38 Iustin Pop
    for instance in self._config_data.instances.values():
218 f9518d38 Iustin Pop
      for disk in instance.disks:
219 f9518d38 Iustin Pop
        helper(disk, result)
220 f9518d38 Iustin Pop
221 f9518d38 Iustin Pop
    return result
222 f9518d38 Iustin Pop
223 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
224 a8083063 Iustin Pop
  def VerifyConfig(self):
225 a8083063 Iustin Pop
    """Stub verify function.
226 a8083063 Iustin Pop
    """
227 a8083063 Iustin Pop
    self._OpenConfig()
228 a8083063 Iustin Pop
229 a8083063 Iustin Pop
    result = []
230 a8083063 Iustin Pop
    seen_macs = []
231 48ce9fd9 Iustin Pop
    ports = {}
232 a8083063 Iustin Pop
    data = self._config_data
233 a8083063 Iustin Pop
    for instance_name in data.instances:
234 a8083063 Iustin Pop
      instance = data.instances[instance_name]
235 a8083063 Iustin Pop
      if instance.primary_node not in data.nodes:
236 8522ceeb Iustin Pop
        result.append("instance '%s' has invalid primary node '%s'" %
237 a8083063 Iustin Pop
                      (instance_name, instance.primary_node))
238 a8083063 Iustin Pop
      for snode in instance.secondary_nodes:
239 a8083063 Iustin Pop
        if snode not in data.nodes:
240 8522ceeb Iustin Pop
          result.append("instance '%s' has invalid secondary node '%s'" %
241 a8083063 Iustin Pop
                        (instance_name, snode))
242 a8083063 Iustin Pop
      for idx, nic in enumerate(instance.nics):
243 a8083063 Iustin Pop
        if nic.mac in seen_macs:
244 8522ceeb Iustin Pop
          result.append("instance '%s' has NIC %d mac %s duplicate" %
245 a8083063 Iustin Pop
                        (instance_name, idx, nic.mac))
246 a8083063 Iustin Pop
        else:
247 a8083063 Iustin Pop
          seen_macs.append(nic.mac)
248 48ce9fd9 Iustin Pop
249 48ce9fd9 Iustin Pop
      # gather the drbd ports for duplicate checks
250 48ce9fd9 Iustin Pop
      for dsk in instance.disks:
251 48ce9fd9 Iustin Pop
        if dsk.dev_type in constants.LDS_DRBD:
252 48ce9fd9 Iustin Pop
          tcp_port = dsk.logical_id[2]
253 48ce9fd9 Iustin Pop
          if tcp_port not in ports:
254 48ce9fd9 Iustin Pop
            ports[tcp_port] = []
255 48ce9fd9 Iustin Pop
          ports[tcp_port].append((instance.name, "drbd disk %s" % dsk.iv_name))
256 48ce9fd9 Iustin Pop
      # gather network port reservation
257 48ce9fd9 Iustin Pop
      net_port = getattr(instance, "network_port", None)
258 48ce9fd9 Iustin Pop
      if net_port is not None:
259 48ce9fd9 Iustin Pop
        if net_port not in ports:
260 48ce9fd9 Iustin Pop
          ports[net_port] = []
261 48ce9fd9 Iustin Pop
        ports[net_port].append((instance.name, "network port"))
262 48ce9fd9 Iustin Pop
263 48ce9fd9 Iustin Pop
    # cluster-wide pool of free ports
264 48ce9fd9 Iustin Pop
    for free_port in self._config_data.cluster.tcpudp_port_pool:
265 48ce9fd9 Iustin Pop
      if free_port not in ports:
266 48ce9fd9 Iustin Pop
        ports[free_port] = []
267 48ce9fd9 Iustin Pop
      ports[free_port].append(("cluster", "port marked as free"))
268 48ce9fd9 Iustin Pop
269 48ce9fd9 Iustin Pop
    # compute tcp/udp duplicate ports
270 48ce9fd9 Iustin Pop
    keys = ports.keys()
271 48ce9fd9 Iustin Pop
    keys.sort()
272 48ce9fd9 Iustin Pop
    for pnum in keys:
273 48ce9fd9 Iustin Pop
      pdata = ports[pnum]
274 48ce9fd9 Iustin Pop
      if len(pdata) > 1:
275 48ce9fd9 Iustin Pop
        txt = ", ".join(["%s/%s" % val for val in pdata])
276 48ce9fd9 Iustin Pop
        result.append("tcp/udp port %s has duplicates: %s" % (pnum, txt))
277 48ce9fd9 Iustin Pop
278 48ce9fd9 Iustin Pop
    # highest used tcp port check
279 48ce9fd9 Iustin Pop
    if keys:
280 48ce9fd9 Iustin Pop
      if keys[-1] > self._config_data.cluster.highest_used_port:
281 48ce9fd9 Iustin Pop
        result.append("Highest used port mismatch, saved %s, computed %s" %
282 48ce9fd9 Iustin Pop
                      (self._config_data.cluster.highest_used_port,
283 48ce9fd9 Iustin Pop
                       keys[-1]))
284 48ce9fd9 Iustin Pop
285 a8083063 Iustin Pop
    return result
286 a8083063 Iustin Pop
287 f78ede4e Guido Trotter
  def _UnlockedSetDiskID(self, disk, node_name):
288 a8083063 Iustin Pop
    """Convert the unique ID to the ID needed on the target nodes.
289 a8083063 Iustin Pop

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

292 a8083063 Iustin Pop
    The routine descends down and updates its children also, because
293 a8083063 Iustin Pop
    this helps when the only the top device is passed to the remote
294 a8083063 Iustin Pop
    node.
295 a8083063 Iustin Pop

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

298 a8083063 Iustin Pop
    """
299 a8083063 Iustin Pop
    if disk.children:
300 a8083063 Iustin Pop
      for child in disk.children:
301 f78ede4e Guido Trotter
        self._UnlockedSetDiskID(child, node_name)
302 a8083063 Iustin Pop
303 a8083063 Iustin Pop
    if disk.logical_id is None and disk.physical_id is not None:
304 a8083063 Iustin Pop
      return
305 ffa1c0dc Iustin Pop
    if disk.dev_type == constants.LD_DRBD8:
306 f9518d38 Iustin Pop
      pnode, snode, port, pminor, sminor, secret = disk.logical_id
307 a8083063 Iustin Pop
      if node_name not in (pnode, snode):
308 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
309 3ecf6786 Iustin Pop
                                        node_name)
310 f78ede4e Guido Trotter
      pnode_info = self._UnlockedGetNodeInfo(pnode)
311 f78ede4e Guido Trotter
      snode_info = self._UnlockedGetNodeInfo(snode)
312 a8083063 Iustin Pop
      if pnode_info is None or snode_info is None:
313 a8083063 Iustin Pop
        raise errors.ConfigurationError("Can't find primary or secondary node"
314 a8083063 Iustin Pop
                                        " for %s" % str(disk))
315 ffa1c0dc Iustin Pop
      p_data = (pnode_info.secondary_ip, port)
316 ffa1c0dc Iustin Pop
      s_data = (snode_info.secondary_ip, port)
317 a8083063 Iustin Pop
      if pnode == node_name:
318 f9518d38 Iustin Pop
        disk.physical_id = p_data + s_data + (pminor, secret)
319 a8083063 Iustin Pop
      else: # it must be secondary, we tested above
320 f9518d38 Iustin Pop
        disk.physical_id = s_data + p_data + (sminor, secret)
321 a8083063 Iustin Pop
    else:
322 a8083063 Iustin Pop
      disk.physical_id = disk.logical_id
323 a8083063 Iustin Pop
    return
324 a8083063 Iustin Pop
325 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
326 f78ede4e Guido Trotter
  def SetDiskID(self, disk, node_name):
327 f78ede4e Guido Trotter
    """Convert the unique ID to the ID needed on the target nodes.
328 f78ede4e Guido Trotter

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

331 f78ede4e Guido Trotter
    The routine descends down and updates its children also, because
332 f78ede4e Guido Trotter
    this helps when the only the top device is passed to the remote
333 f78ede4e Guido Trotter
    node.
334 f78ede4e Guido Trotter

335 f78ede4e Guido Trotter
    """
336 f78ede4e Guido Trotter
    return self._UnlockedSetDiskID(disk, node_name)
337 f78ede4e Guido Trotter
338 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
339 b2fddf63 Iustin Pop
  def AddTcpUdpPort(self, port):
340 b2fddf63 Iustin Pop
    """Adds a new port to the available port pool.
341 b2fddf63 Iustin Pop

342 b2fddf63 Iustin Pop
    """
343 264bb3c5 Michael Hanselmann
    if not isinstance(port, int):
344 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed for port")
345 264bb3c5 Michael Hanselmann
346 264bb3c5 Michael Hanselmann
    self._OpenConfig()
347 b2fddf63 Iustin Pop
    self._config_data.cluster.tcpudp_port_pool.add(port)
348 264bb3c5 Michael Hanselmann
    self._WriteConfig()
349 264bb3c5 Michael Hanselmann
350 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
351 b2fddf63 Iustin Pop
  def GetPortList(self):
352 264bb3c5 Michael Hanselmann
    """Returns a copy of the current port list.
353 264bb3c5 Michael Hanselmann

354 264bb3c5 Michael Hanselmann
    """
355 264bb3c5 Michael Hanselmann
    self._OpenConfig()
356 b2fddf63 Iustin Pop
    return self._config_data.cluster.tcpudp_port_pool.copy()
357 264bb3c5 Michael Hanselmann
358 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
359 a8083063 Iustin Pop
  def AllocatePort(self):
360 a8083063 Iustin Pop
    """Allocate a port.
361 a8083063 Iustin Pop

362 b2fddf63 Iustin Pop
    The port will be taken from the available port pool or from the
363 b2fddf63 Iustin Pop
    default port range (and in this case we increase
364 b2fddf63 Iustin Pop
    highest_used_port).
365 a8083063 Iustin Pop

366 a8083063 Iustin Pop
    """
367 a8083063 Iustin Pop
    self._OpenConfig()
368 a8083063 Iustin Pop
369 264bb3c5 Michael Hanselmann
    # If there are TCP/IP ports configured, we use them first.
370 b2fddf63 Iustin Pop
    if self._config_data.cluster.tcpudp_port_pool:
371 b2fddf63 Iustin Pop
      port = self._config_data.cluster.tcpudp_port_pool.pop()
372 264bb3c5 Michael Hanselmann
    else:
373 264bb3c5 Michael Hanselmann
      port = self._config_data.cluster.highest_used_port + 1
374 264bb3c5 Michael Hanselmann
      if port >= constants.LAST_DRBD_PORT:
375 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("The highest used port is greater"
376 3ecf6786 Iustin Pop
                                        " than %s. Aborting." %
377 3ecf6786 Iustin Pop
                                        constants.LAST_DRBD_PORT)
378 264bb3c5 Michael Hanselmann
      self._config_data.cluster.highest_used_port = port
379 a8083063 Iustin Pop
380 a8083063 Iustin Pop
    self._WriteConfig()
381 a8083063 Iustin Pop
    return port
382 a8083063 Iustin Pop
383 a81c53c9 Iustin Pop
  def _ComputeDRBDMap(self, instance):
384 a81c53c9 Iustin Pop
    """Compute the used DRBD minor/nodes.
385 a81c53c9 Iustin Pop

386 a81c53c9 Iustin Pop
    Return: dictionary of node_name: dict of minor: instance_name. The
387 a81c53c9 Iustin Pop
    returned dict will have all the nodes in it (even if with an empty
388 a81c53c9 Iustin Pop
    list).
389 a81c53c9 Iustin Pop

390 a81c53c9 Iustin Pop
    """
391 a81c53c9 Iustin Pop
    def _AppendUsedPorts(instance_name, disk, used):
392 f9518d38 Iustin Pop
      if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
393 f9518d38 Iustin Pop
        nodeA, nodeB, dummy, minorA, minorB = disk.logical_id[:5]
394 a81c53c9 Iustin Pop
        for node, port in ((nodeA, minorA), (nodeB, minorB)):
395 a81c53c9 Iustin Pop
          assert node in used, "Instance node not found in node list"
396 a81c53c9 Iustin Pop
          if port in used[node]:
397 a81c53c9 Iustin Pop
            raise errors.ProgrammerError("DRBD minor already used:"
398 a81c53c9 Iustin Pop
                                         " %s/%s, %s/%s" %
399 a81c53c9 Iustin Pop
                                         (node, port, instance_name,
400 a81c53c9 Iustin Pop
                                          used[node][port]))
401 a81c53c9 Iustin Pop
402 a81c53c9 Iustin Pop
          used[node][port] = instance_name
403 a81c53c9 Iustin Pop
      if disk.children:
404 a81c53c9 Iustin Pop
        for child in disk.children:
405 a81c53c9 Iustin Pop
          _AppendUsedPorts(instance_name, child, used)
406 a81c53c9 Iustin Pop
407 a81c53c9 Iustin Pop
    my_dict = dict((node, {}) for node in self._config_data.nodes)
408 a81c53c9 Iustin Pop
    for (node, minor), instance in self._temporary_drbds.iteritems():
409 a81c53c9 Iustin Pop
      my_dict[node][minor] = instance
410 a81c53c9 Iustin Pop
    for instance in self._config_data.instances.itervalues():
411 a81c53c9 Iustin Pop
      for disk in instance.disks:
412 a81c53c9 Iustin Pop
        _AppendUsedPorts(instance.name, disk, my_dict)
413 a81c53c9 Iustin Pop
    return my_dict
414 a81c53c9 Iustin Pop
415 a81c53c9 Iustin Pop
  @locking.ssynchronized(_config_lock)
416 a81c53c9 Iustin Pop
  def AllocateDRBDMinor(self, nodes, instance):
417 a81c53c9 Iustin Pop
    """Allocate a drbd minor.
418 a81c53c9 Iustin Pop

419 a81c53c9 Iustin Pop
    The free minor will be automatically computed from the existing
420 a81c53c9 Iustin Pop
    devices. A node can be given multiple times in order to allocate
421 a81c53c9 Iustin Pop
    multiple minors. The result is the list of minors, in the same
422 a81c53c9 Iustin Pop
    order as the passed nodes.
423 a81c53c9 Iustin Pop

424 a81c53c9 Iustin Pop
    """
425 a81c53c9 Iustin Pop
    self._OpenConfig()
426 a81c53c9 Iustin Pop
427 a81c53c9 Iustin Pop
    d_map = self._ComputeDRBDMap(instance)
428 a81c53c9 Iustin Pop
    result = []
429 a81c53c9 Iustin Pop
    for nname in nodes:
430 a81c53c9 Iustin Pop
      ndata = d_map[nname]
431 a81c53c9 Iustin Pop
      if not ndata:
432 a81c53c9 Iustin Pop
        # no minors used, we can start at 0
433 a81c53c9 Iustin Pop
        result.append(0)
434 a81c53c9 Iustin Pop
        ndata[0] = instance
435 d48663e4 Iustin Pop
        self._temporary_drbds[(nname, 0)] = instance
436 a81c53c9 Iustin Pop
        continue
437 a81c53c9 Iustin Pop
      keys = ndata.keys()
438 a81c53c9 Iustin Pop
      keys.sort()
439 a81c53c9 Iustin Pop
      ffree = utils.FirstFree(keys)
440 a81c53c9 Iustin Pop
      if ffree is None:
441 a81c53c9 Iustin Pop
        # return the next minor
442 a81c53c9 Iustin Pop
        # TODO: implement high-limit check
443 a81c53c9 Iustin Pop
        minor = keys[-1] + 1
444 a81c53c9 Iustin Pop
      else:
445 a81c53c9 Iustin Pop
        minor = ffree
446 a81c53c9 Iustin Pop
      result.append(minor)
447 a81c53c9 Iustin Pop
      ndata[minor] = instance
448 a81c53c9 Iustin Pop
      assert (nname, minor) not in self._temporary_drbds, \
449 a81c53c9 Iustin Pop
             "Attempt to reuse reserved DRBD minor"
450 a81c53c9 Iustin Pop
      self._temporary_drbds[(nname, minor)] = instance
451 a81c53c9 Iustin Pop
    logging.debug("Request to allocate drbd minors, input: %s, returning %s",
452 a81c53c9 Iustin Pop
                  nodes, result)
453 a81c53c9 Iustin Pop
    return result
454 a81c53c9 Iustin Pop
455 a81c53c9 Iustin Pop
  @locking.ssynchronized(_config_lock)
456 a81c53c9 Iustin Pop
  def ReleaseDRBDMinors(self, instance):
457 a81c53c9 Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
458 a81c53c9 Iustin Pop

459 a81c53c9 Iustin Pop
    This should be called on both the error paths and on the success
460 a81c53c9 Iustin Pop
    paths (after the instance has been added or updated).
461 a81c53c9 Iustin Pop

462 a81c53c9 Iustin Pop
    @type instance: string
463 a81c53c9 Iustin Pop
    @param instance: the instance for which temporary minors should be
464 a81c53c9 Iustin Pop
                     released
465 a81c53c9 Iustin Pop

466 a81c53c9 Iustin Pop
    """
467 a81c53c9 Iustin Pop
    for key, name in self._temporary_drbds.items():
468 a81c53c9 Iustin Pop
      if name == instance:
469 a81c53c9 Iustin Pop
        del self._temporary_drbds[key]
470 a81c53c9 Iustin Pop
471 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
472 4a8b186a Michael Hanselmann
  def GetConfigVersion(self):
473 4a8b186a Michael Hanselmann
    """Get the configuration version.
474 4a8b186a Michael Hanselmann

475 4a8b186a Michael Hanselmann
    @return: Config version
476 4a8b186a Michael Hanselmann

477 4a8b186a Michael Hanselmann
    """
478 4a8b186a Michael Hanselmann
    return self._config_data.version
479 4a8b186a Michael Hanselmann
480 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
481 4a8b186a Michael Hanselmann
  def GetClusterName(self):
482 4a8b186a Michael Hanselmann
    """Get cluster name.
483 4a8b186a Michael Hanselmann

484 4a8b186a Michael Hanselmann
    @return: Cluster name
485 4a8b186a Michael Hanselmann

486 4a8b186a Michael Hanselmann
    """
487 4a8b186a Michael Hanselmann
    self._OpenConfig()
488 4a8b186a Michael Hanselmann
    return self._config_data.cluster.cluster_name
489 4a8b186a Michael Hanselmann
490 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
491 4a8b186a Michael Hanselmann
  def GetMasterNode(self):
492 4a8b186a Michael Hanselmann
    """Get the hostname of the master node for this cluster.
493 4a8b186a Michael Hanselmann

494 4a8b186a Michael Hanselmann
    @return: Master hostname
495 4a8b186a Michael Hanselmann

496 4a8b186a Michael Hanselmann
    """
497 4a8b186a Michael Hanselmann
    self._OpenConfig()
498 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_node
499 4a8b186a Michael Hanselmann
500 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
501 4a8b186a Michael Hanselmann
  def GetMasterIP(self):
502 4a8b186a Michael Hanselmann
    """Get the IP of the master node for this cluster.
503 4a8b186a Michael Hanselmann

504 4a8b186a Michael Hanselmann
    @return: Master IP
505 4a8b186a Michael Hanselmann

506 4a8b186a Michael Hanselmann
    """
507 4a8b186a Michael Hanselmann
    self._OpenConfig()
508 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_ip
509 4a8b186a Michael Hanselmann
510 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
511 4a8b186a Michael Hanselmann
  def GetMasterNetdev(self):
512 4a8b186a Michael Hanselmann
    """Get the master network device for this cluster.
513 4a8b186a Michael Hanselmann

514 4a8b186a Michael Hanselmann
    """
515 4a8b186a Michael Hanselmann
    self._OpenConfig()
516 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_netdev
517 4a8b186a Michael Hanselmann
518 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
519 4a8b186a Michael Hanselmann
  def GetFileStorageDir(self):
520 4a8b186a Michael Hanselmann
    """Get the file storage dir for this cluster.
521 4a8b186a Michael Hanselmann

522 4a8b186a Michael Hanselmann
    """
523 4a8b186a Michael Hanselmann
    self._OpenConfig()
524 4a8b186a Michael Hanselmann
    return self._config_data.cluster.file_storage_dir
525 4a8b186a Michael Hanselmann
526 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
527 4a8b186a Michael Hanselmann
  def GetHypervisorType(self):
528 4a8b186a Michael Hanselmann
    """Get the hypervisor type for this cluster.
529 4a8b186a Michael Hanselmann

530 4a8b186a Michael Hanselmann
    """
531 4a8b186a Michael Hanselmann
    self._OpenConfig()
532 4a8b186a Michael Hanselmann
    return self._config_data.cluster.hypervisor
533 4a8b186a Michael Hanselmann
534 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
535 a8083063 Iustin Pop
  def GetHostKey(self):
536 a8083063 Iustin Pop
    """Return the rsa hostkey from the config.
537 a8083063 Iustin Pop

538 a8083063 Iustin Pop
    Args: None
539 a8083063 Iustin Pop

540 a8083063 Iustin Pop
    Returns: rsa hostkey
541 a8083063 Iustin Pop
    """
542 a8083063 Iustin Pop
    self._OpenConfig()
543 a8083063 Iustin Pop
    return self._config_data.cluster.rsahostkeypub
544 a8083063 Iustin Pop
545 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
546 a8083063 Iustin Pop
  def AddInstance(self, instance):
547 a8083063 Iustin Pop
    """Add an instance to the config.
548 a8083063 Iustin Pop

549 a8083063 Iustin Pop
    This should be used after creating a new instance.
550 a8083063 Iustin Pop

551 a8083063 Iustin Pop
    Args:
552 a8083063 Iustin Pop
      instance: the instance object
553 a8083063 Iustin Pop
    """
554 a8083063 Iustin Pop
    if not isinstance(instance, objects.Instance):
555 a8083063 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
556 a8083063 Iustin Pop
557 e00fb268 Iustin Pop
    if instance.disk_template != constants.DT_DISKLESS:
558 e00fb268 Iustin Pop
      all_lvs = instance.MapLVsByNode()
559 74a48621 Iustin Pop
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
560 923b1523 Iustin Pop
561 a8083063 Iustin Pop
    self._OpenConfig()
562 b989e85d Iustin Pop
    instance.serial_no = 1
563 a8083063 Iustin Pop
    self._config_data.instances[instance.name] = instance
564 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
565 a8083063 Iustin Pop
    self._WriteConfig()
566 a8083063 Iustin Pop
567 6a408fb2 Iustin Pop
  def _SetInstanceStatus(self, instance_name, status):
568 6a408fb2 Iustin Pop
    """Set the instance's status to a given value.
569 a8083063 Iustin Pop

570 a8083063 Iustin Pop
    """
571 6a408fb2 Iustin Pop
    if status not in ("up", "down"):
572 6a408fb2 Iustin Pop
      raise errors.ProgrammerError("Invalid status '%s' passed to"
573 6a408fb2 Iustin Pop
                                   " ConfigWriter._SetInstanceStatus()" %
574 6a408fb2 Iustin Pop
                                   status)
575 a8083063 Iustin Pop
    self._OpenConfig()
576 a8083063 Iustin Pop
577 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
578 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" %
579 3ecf6786 Iustin Pop
                                      instance_name)
580 a8083063 Iustin Pop
    instance = self._config_data.instances[instance_name]
581 455a3445 Iustin Pop
    if instance.status != status:
582 455a3445 Iustin Pop
      instance.status = status
583 b989e85d Iustin Pop
      instance.serial_no += 1
584 455a3445 Iustin Pop
      self._WriteConfig()
585 a8083063 Iustin Pop
586 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
587 6a408fb2 Iustin Pop
  def MarkInstanceUp(self, instance_name):
588 6a408fb2 Iustin Pop
    """Mark the instance status to up in the config.
589 6a408fb2 Iustin Pop

590 6a408fb2 Iustin Pop
    """
591 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "up")
592 6a408fb2 Iustin Pop
593 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
594 a8083063 Iustin Pop
  def RemoveInstance(self, instance_name):
595 a8083063 Iustin Pop
    """Remove the instance from the configuration.
596 a8083063 Iustin Pop

597 a8083063 Iustin Pop
    """
598 a8083063 Iustin Pop
    self._OpenConfig()
599 a8083063 Iustin Pop
600 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
601 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
602 a8083063 Iustin Pop
    del self._config_data.instances[instance_name]
603 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
604 a8083063 Iustin Pop
    self._WriteConfig()
605 a8083063 Iustin Pop
606 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
607 fc95f88f Iustin Pop
  def RenameInstance(self, old_name, new_name):
608 fc95f88f Iustin Pop
    """Rename an instance.
609 fc95f88f Iustin Pop

610 fc95f88f Iustin Pop
    This needs to be done in ConfigWriter and not by RemoveInstance
611 fc95f88f Iustin Pop
    combined with AddInstance as only we can guarantee an atomic
612 fc95f88f Iustin Pop
    rename.
613 fc95f88f Iustin Pop

614 fc95f88f Iustin Pop
    """
615 fc95f88f Iustin Pop
    self._OpenConfig()
616 fc95f88f Iustin Pop
    if old_name not in self._config_data.instances:
617 fc95f88f Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
618 fc95f88f Iustin Pop
    inst = self._config_data.instances[old_name]
619 fc95f88f Iustin Pop
    del self._config_data.instances[old_name]
620 fc95f88f Iustin Pop
    inst.name = new_name
621 b23c4333 Manuel Franceschini
622 b23c4333 Manuel Franceschini
    for disk in inst.disks:
623 b23c4333 Manuel Franceschini
      if disk.dev_type == constants.LD_FILE:
624 b23c4333 Manuel Franceschini
        # rename the file paths in logical and physical id
625 b23c4333 Manuel Franceschini
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
626 b23c4333 Manuel Franceschini
        disk.physical_id = disk.logical_id = (disk.logical_id[0],
627 b23c4333 Manuel Franceschini
                                              os.path.join(file_storage_dir,
628 b23c4333 Manuel Franceschini
                                                           inst.name,
629 b23c4333 Manuel Franceschini
                                                           disk.iv_name))
630 b23c4333 Manuel Franceschini
631 fc95f88f Iustin Pop
    self._config_data.instances[inst.name] = inst
632 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
633 fc95f88f Iustin Pop
    self._WriteConfig()
634 fc95f88f Iustin Pop
635 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
636 a8083063 Iustin Pop
  def MarkInstanceDown(self, instance_name):
637 a8083063 Iustin Pop
    """Mark the status of an instance to down in the configuration.
638 a8083063 Iustin Pop

639 a8083063 Iustin Pop
    """
640 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "down")
641 a8083063 Iustin Pop
642 94bbfece Iustin Pop
  def _UnlockedGetInstanceList(self):
643 94bbfece Iustin Pop
    """Get the list of instances.
644 94bbfece Iustin Pop

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

647 94bbfece Iustin Pop
    """
648 94bbfece Iustin Pop
    self._OpenConfig()
649 94bbfece Iustin Pop
    return self._config_data.instances.keys()
650 94bbfece Iustin Pop
651 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
652 a8083063 Iustin Pop
  def GetInstanceList(self):
653 a8083063 Iustin Pop
    """Get the list of instances.
654 a8083063 Iustin Pop

655 a8083063 Iustin Pop
    Returns:
656 a8083063 Iustin Pop
      array of instances, ex. ['instance2.example.com','instance1.example.com']
657 a8083063 Iustin Pop
      these contains all the instances, also the ones in Admin_down state
658 a8083063 Iustin Pop

659 a8083063 Iustin Pop
    """
660 94bbfece Iustin Pop
    return self._UnlockedGetInstanceList()
661 a8083063 Iustin Pop
662 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
663 a8083063 Iustin Pop
  def ExpandInstanceName(self, short_name):
664 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
665 a8083063 Iustin Pop

666 a8083063 Iustin Pop
    """
667 a8083063 Iustin Pop
    self._OpenConfig()
668 a8083063 Iustin Pop
669 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
670 a8083063 Iustin Pop
                                    self._config_data.instances.keys())
671 a8083063 Iustin Pop
672 94bbfece Iustin Pop
  def _UnlockedGetInstanceInfo(self, instance_name):
673 94bbfece Iustin Pop
    """Returns informations about an instance.
674 94bbfece Iustin Pop

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

677 94bbfece Iustin Pop
    """
678 94bbfece Iustin Pop
    self._OpenConfig()
679 94bbfece Iustin Pop
680 94bbfece Iustin Pop
    if instance_name not in self._config_data.instances:
681 94bbfece Iustin Pop
      return None
682 94bbfece Iustin Pop
683 94bbfece Iustin Pop
    return self._config_data.instances[instance_name]
684 94bbfece Iustin Pop
685 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
686 a8083063 Iustin Pop
  def GetInstanceInfo(self, instance_name):
687 a8083063 Iustin Pop
    """Returns informations about an instance.
688 a8083063 Iustin Pop

689 a8083063 Iustin Pop
    It takes the information from the configuration file. Other informations of
690 a8083063 Iustin Pop
    an instance are taken from the live systems.
691 a8083063 Iustin Pop

692 a8083063 Iustin Pop
    Args:
693 a8083063 Iustin Pop
      instance: name of the instance, ex instance1.example.com
694 a8083063 Iustin Pop

695 a8083063 Iustin Pop
    Returns:
696 a8083063 Iustin Pop
      the instance object
697 a8083063 Iustin Pop

698 a8083063 Iustin Pop
    """
699 94bbfece Iustin Pop
    return self._UnlockedGetInstanceInfo(instance_name)
700 a8083063 Iustin Pop
701 0b2de758 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
702 0b2de758 Iustin Pop
  def GetAllInstancesInfo(self):
703 0b2de758 Iustin Pop
    """Get the configuration of all instances.
704 0b2de758 Iustin Pop

705 0b2de758 Iustin Pop
    @rtype: dict
706 0b2de758 Iustin Pop
    @returns: dict of (instance, instance_info), where instance_info is what
707 0b2de758 Iustin Pop
              would GetInstanceInfo return for the node
708 0b2de758 Iustin Pop

709 0b2de758 Iustin Pop
    """
710 64d3bd52 Guido Trotter
    my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
711 64d3bd52 Guido Trotter
                    for instance in self._UnlockedGetInstanceList()])
712 0b2de758 Iustin Pop
    return my_dict
713 0b2de758 Iustin Pop
714 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
715 a8083063 Iustin Pop
  def AddNode(self, node):
716 a8083063 Iustin Pop
    """Add a node to the configuration.
717 a8083063 Iustin Pop

718 a8083063 Iustin Pop
    Args:
719 a8083063 Iustin Pop
      node: an object.Node instance
720 a8083063 Iustin Pop

721 a8083063 Iustin Pop
    """
722 d8470559 Michael Hanselmann
    logging.info("Adding node %s to configuration" % node.name)
723 d8470559 Michael Hanselmann
724 a8083063 Iustin Pop
    self._OpenConfig()
725 b989e85d Iustin Pop
    node.serial_no = 1
726 a8083063 Iustin Pop
    self._config_data.nodes[node.name] = node
727 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
728 a8083063 Iustin Pop
    self._WriteConfig()
729 a8083063 Iustin Pop
730 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
731 a8083063 Iustin Pop
  def RemoveNode(self, node_name):
732 a8083063 Iustin Pop
    """Remove a node from the configuration.
733 a8083063 Iustin Pop

734 a8083063 Iustin Pop
    """
735 d8470559 Michael Hanselmann
    logging.info("Removing node %s from configuration" % node_name)
736 d8470559 Michael Hanselmann
737 a8083063 Iustin Pop
    self._OpenConfig()
738 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
739 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
740 a8083063 Iustin Pop
741 a8083063 Iustin Pop
    del self._config_data.nodes[node_name]
742 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
743 a8083063 Iustin Pop
    self._WriteConfig()
744 a8083063 Iustin Pop
745 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
746 a8083063 Iustin Pop
  def ExpandNodeName(self, short_name):
747 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
748 a8083063 Iustin Pop

749 a8083063 Iustin Pop
    """
750 a8083063 Iustin Pop
    self._OpenConfig()
751 a8083063 Iustin Pop
752 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
753 a8083063 Iustin Pop
                                    self._config_data.nodes.keys())
754 a8083063 Iustin Pop
755 f78ede4e Guido Trotter
  def _UnlockedGetNodeInfo(self, node_name):
756 a8083063 Iustin Pop
    """Get the configuration of a node, as stored in the config.
757 a8083063 Iustin Pop

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

760 a8083063 Iustin Pop
    Args: node: nodename (tuple) of the node
761 a8083063 Iustin Pop

762 a8083063 Iustin Pop
    Returns: the node object
763 a8083063 Iustin Pop

764 a8083063 Iustin Pop
    """
765 a8083063 Iustin Pop
    self._OpenConfig()
766 a8083063 Iustin Pop
767 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
768 a8083063 Iustin Pop
      return None
769 a8083063 Iustin Pop
770 a8083063 Iustin Pop
    return self._config_data.nodes[node_name]
771 a8083063 Iustin Pop
772 f78ede4e Guido Trotter
773 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
774 f78ede4e Guido Trotter
  def GetNodeInfo(self, node_name):
775 f78ede4e Guido Trotter
    """Get the configuration of a node, as stored in the config.
776 f78ede4e Guido Trotter

777 f78ede4e Guido Trotter
    Args: node: nodename (tuple) of the node
778 f78ede4e Guido Trotter

779 f78ede4e Guido Trotter
    Returns: the node object
780 f78ede4e Guido Trotter

781 f78ede4e Guido Trotter
    """
782 f78ede4e Guido Trotter
    return self._UnlockedGetNodeInfo(node_name)
783 f78ede4e Guido Trotter
784 f78ede4e Guido Trotter
  def _UnlockedGetNodeList(self):
785 a8083063 Iustin Pop
    """Return the list of nodes which are in the configuration.
786 a8083063 Iustin Pop

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

789 a8083063 Iustin Pop
    """
790 a8083063 Iustin Pop
    self._OpenConfig()
791 a8083063 Iustin Pop
    return self._config_data.nodes.keys()
792 a8083063 Iustin Pop
793 f78ede4e Guido Trotter
794 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
795 f78ede4e Guido Trotter
  def GetNodeList(self):
796 f78ede4e Guido Trotter
    """Return the list of nodes which are in the configuration.
797 f78ede4e Guido Trotter

798 f78ede4e Guido Trotter
    """
799 f78ede4e Guido Trotter
    return self._UnlockedGetNodeList()
800 f78ede4e Guido Trotter
801 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
802 d65e5776 Iustin Pop
  def GetAllNodesInfo(self):
803 d65e5776 Iustin Pop
    """Get the configuration of all nodes.
804 d65e5776 Iustin Pop

805 d65e5776 Iustin Pop
    @rtype: dict
806 d65e5776 Iustin Pop
    @returns: dict of (node, node_info), where node_info is what
807 d65e5776 Iustin Pop
              would GetNodeInfo return for the node
808 d65e5776 Iustin Pop

809 d65e5776 Iustin Pop
    """
810 d65e5776 Iustin Pop
    my_dict = dict([(node, self._UnlockedGetNodeInfo(node))
811 d65e5776 Iustin Pop
                    for node in self._UnlockedGetNodeList()])
812 d65e5776 Iustin Pop
    return my_dict
813 d65e5776 Iustin Pop
814 a8083063 Iustin Pop
  def _BumpSerialNo(self):
815 a8083063 Iustin Pop
    """Bump up the serial number of the config.
816 a8083063 Iustin Pop

817 a8083063 Iustin Pop
    """
818 9d38c6e1 Iustin Pop
    self._config_data.serial_no += 1
819 a8083063 Iustin Pop
820 a8083063 Iustin Pop
  def _OpenConfig(self):
821 a8083063 Iustin Pop
    """Read the config data from disk.
822 a8083063 Iustin Pop

823 a8083063 Iustin Pop
    In case we already have configuration data and the config file has
824 a8083063 Iustin Pop
    the same mtime as when we read it, we skip the parsing of the
825 a8083063 Iustin Pop
    file, since de-serialisation could be slow.
826 a8083063 Iustin Pop

827 a8083063 Iustin Pop
    """
828 a8083063 Iustin Pop
    try:
829 a8083063 Iustin Pop
      st = os.stat(self._cfg_file)
830 a8083063 Iustin Pop
    except OSError, err:
831 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Can't stat config file: %s" % err)
832 a8083063 Iustin Pop
    if (self._config_data is not None and
833 a8083063 Iustin Pop
        self._config_time is not None and
834 264bb3c5 Michael Hanselmann
        self._config_time == st.st_mtime and
835 264bb3c5 Michael Hanselmann
        self._config_size == st.st_size and
836 264bb3c5 Michael Hanselmann
        self._config_inode == st.st_ino):
837 a8083063 Iustin Pop
      # data is current, so skip loading of config file
838 a8083063 Iustin Pop
      return
839 243cdbcc Michael Hanselmann
840 a8083063 Iustin Pop
    f = open(self._cfg_file, 'r')
841 a8083063 Iustin Pop
    try:
842 a8083063 Iustin Pop
      try:
843 8d14b30d Iustin Pop
        data = objects.ConfigData.FromDict(serializer.Load(f.read()))
844 a8083063 Iustin Pop
      except Exception, err:
845 3ecf6786 Iustin Pop
        raise errors.ConfigurationError(err)
846 a8083063 Iustin Pop
    finally:
847 a8083063 Iustin Pop
      f.close()
848 5b263ed7 Michael Hanselmann
849 5b263ed7 Michael Hanselmann
    # Make sure the configuration has the right version
850 5b263ed7 Michael Hanselmann
    _ValidateConfig(data)
851 5b263ed7 Michael Hanselmann
852 a8083063 Iustin Pop
    if (not hasattr(data, 'cluster') or
853 243cdbcc Michael Hanselmann
        not hasattr(data.cluster, 'rsahostkeypub')):
854 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Incomplete configuration"
855 243cdbcc Michael Hanselmann
                                      " (missing cluster.rsahostkeypub)")
856 a8083063 Iustin Pop
    self._config_data = data
857 a8083063 Iustin Pop
    self._config_time = st.st_mtime
858 264bb3c5 Michael Hanselmann
    self._config_size = st.st_size
859 264bb3c5 Michael Hanselmann
    self._config_inode = st.st_ino
860 a8083063 Iustin Pop
861 a8083063 Iustin Pop
  def _DistributeConfig(self):
862 a8083063 Iustin Pop
    """Distribute the configuration to the other nodes.
863 a8083063 Iustin Pop

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

867 a8083063 Iustin Pop
    """
868 a8083063 Iustin Pop
    if self._offline:
869 a8083063 Iustin Pop
      return True
870 a8083063 Iustin Pop
    bad = False
871 f78ede4e Guido Trotter
    nodelist = self._UnlockedGetNodeList()
872 89e1fc26 Iustin Pop
    myhostname = self._my_hostname
873 a8083063 Iustin Pop
874 9ff994da Guido Trotter
    try:
875 9ff994da Guido Trotter
      nodelist.remove(myhostname)
876 9ff994da Guido Trotter
    except ValueError:
877 9ff994da Guido Trotter
      pass
878 a8083063 Iustin Pop
879 41362e70 Guido Trotter
    result = rpc.call_upload_file(nodelist, self._cfg_file)
880 41362e70 Guido Trotter
    for node in nodelist:
881 a8083063 Iustin Pop
      if not result[node]:
882 74a48621 Iustin Pop
        logging.error("copy of file %s to node %s failed",
883 74a48621 Iustin Pop
                      self._cfg_file, node)
884 a8083063 Iustin Pop
        bad = True
885 a8083063 Iustin Pop
    return not bad
886 a8083063 Iustin Pop
887 a8083063 Iustin Pop
  def _WriteConfig(self, destination=None):
888 a8083063 Iustin Pop
    """Write the configuration data to persistent storage.
889 a8083063 Iustin Pop

890 a8083063 Iustin Pop
    """
891 a8083063 Iustin Pop
    if destination is None:
892 a8083063 Iustin Pop
      destination = self._cfg_file
893 a8083063 Iustin Pop
    self._BumpSerialNo()
894 8d14b30d Iustin Pop
    txt = serializer.Dump(self._config_data.ToDict())
895 a8083063 Iustin Pop
    dir_name, file_name = os.path.split(destination)
896 a8083063 Iustin Pop
    fd, name = tempfile.mkstemp('.newconfig', file_name, dir_name)
897 a8083063 Iustin Pop
    f = os.fdopen(fd, 'w')
898 a8083063 Iustin Pop
    try:
899 8d14b30d Iustin Pop
      f.write(txt)
900 a8083063 Iustin Pop
      os.fsync(f.fileno())
901 a8083063 Iustin Pop
    finally:
902 a8083063 Iustin Pop
      f.close()
903 a8083063 Iustin Pop
    # we don't need to do os.close(fd) as f.close() did it
904 a8083063 Iustin Pop
    os.rename(name, destination)
905 14e15659 Iustin Pop
    self.write_count += 1
906 fee9556c Iustin Pop
    # re-set our cache as not to re-read the config file
907 fee9556c Iustin Pop
    try:
908 fee9556c Iustin Pop
      st = os.stat(destination)
909 fee9556c Iustin Pop
    except OSError, err:
910 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Can't stat config file: %s" % err)
911 fee9556c Iustin Pop
    self._config_time = st.st_mtime
912 fee9556c Iustin Pop
    self._config_size = st.st_size
913 fee9556c Iustin Pop
    self._config_inode = st.st_ino
914 fee9556c Iustin Pop
    # and redistribute the config file
915 a8083063 Iustin Pop
    self._DistributeConfig()
916 a8083063 Iustin Pop
917 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
918 f6bd6e98 Michael Hanselmann
  def InitConfig(self, version, cluster_config, master_node_config):
919 a8083063 Iustin Pop
    """Create the initial cluster configuration.
920 a8083063 Iustin Pop

921 a8083063 Iustin Pop
    It will contain the current node, which will also be the master
922 b9eeeb02 Michael Hanselmann
    node, and no instances.
923 a8083063 Iustin Pop

924 f6bd6e98 Michael Hanselmann
    @type version: int
925 f6bd6e98 Michael Hanselmann
    @param version: Configuration version
926 b9eeeb02 Michael Hanselmann
    @type cluster_config: objects.Cluster
927 b9eeeb02 Michael Hanselmann
    @param cluster_config: Cluster configuration
928 b9eeeb02 Michael Hanselmann
    @type master_node_config: objects.Node
929 b9eeeb02 Michael Hanselmann
    @param master_node_config: Master node configuration
930 b9eeeb02 Michael Hanselmann

931 b9eeeb02 Michael Hanselmann
    """
932 b9eeeb02 Michael Hanselmann
    nodes = {
933 b9eeeb02 Michael Hanselmann
      master_node_config.name: master_node_config,
934 b9eeeb02 Michael Hanselmann
      }
935 b9eeeb02 Michael Hanselmann
936 f6bd6e98 Michael Hanselmann
    self._config_data = objects.ConfigData(version=version,
937 f6bd6e98 Michael Hanselmann
                                           cluster=cluster_config,
938 b9eeeb02 Michael Hanselmann
                                           nodes=nodes,
939 a8083063 Iustin Pop
                                           instances={},
940 9d38c6e1 Iustin Pop
                                           serial_no=1)
941 a8083063 Iustin Pop
    self._WriteConfig()
942 a8083063 Iustin Pop
943 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
944 a8083063 Iustin Pop
  def GetVGName(self):
945 a8083063 Iustin Pop
    """Return the volume group name.
946 a8083063 Iustin Pop

947 a8083063 Iustin Pop
    """
948 a8083063 Iustin Pop
    self._OpenConfig()
949 a8083063 Iustin Pop
    return self._config_data.cluster.volume_group_name
950 a8083063 Iustin Pop
951 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
952 89ff8e15 Manuel Franceschini
  def SetVGName(self, vg_name):
953 89ff8e15 Manuel Franceschini
    """Set the volume group name.
954 89ff8e15 Manuel Franceschini

955 89ff8e15 Manuel Franceschini
    """
956 89ff8e15 Manuel Franceschini
    self._OpenConfig()
957 2d4011cd Manuel Franceschini
    self._config_data.cluster.volume_group_name = vg_name
958 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
959 89ff8e15 Manuel Franceschini
    self._WriteConfig()
960 89ff8e15 Manuel Franceschini
961 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
962 a8083063 Iustin Pop
  def GetDefBridge(self):
963 a8083063 Iustin Pop
    """Return the default bridge.
964 a8083063 Iustin Pop

965 a8083063 Iustin Pop
    """
966 a8083063 Iustin Pop
    self._OpenConfig()
967 a8083063 Iustin Pop
    return self._config_data.cluster.default_bridge
968 a8083063 Iustin Pop
969 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
970 a8083063 Iustin Pop
  def GetMACPrefix(self):
971 a8083063 Iustin Pop
    """Return the mac prefix.
972 a8083063 Iustin Pop

973 a8083063 Iustin Pop
    """
974 a8083063 Iustin Pop
    self._OpenConfig()
975 a8083063 Iustin Pop
    return self._config_data.cluster.mac_prefix
976 62779dd0 Iustin Pop
977 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
978 62779dd0 Iustin Pop
  def GetClusterInfo(self):
979 62779dd0 Iustin Pop
    """Returns informations about the cluster
980 62779dd0 Iustin Pop

981 62779dd0 Iustin Pop
    Returns:
982 62779dd0 Iustin Pop
      the cluster object
983 62779dd0 Iustin Pop

984 62779dd0 Iustin Pop
    """
985 62779dd0 Iustin Pop
    self._OpenConfig()
986 62779dd0 Iustin Pop
987 62779dd0 Iustin Pop
    return self._config_data.cluster
988 e00fb268 Iustin Pop
989 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
990 e00fb268 Iustin Pop
  def Update(self, target):
991 e00fb268 Iustin Pop
    """Notify function to be called after updates.
992 e00fb268 Iustin Pop

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

999 e00fb268 Iustin Pop
    """
1000 e00fb268 Iustin Pop
    if self._config_data is None:
1001 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Configuration file not read,"
1002 3ecf6786 Iustin Pop
                                   " cannot save.")
1003 e00fb268 Iustin Pop
    if isinstance(target, objects.Cluster):
1004 e00fb268 Iustin Pop
      test = target == self._config_data.cluster
1005 e00fb268 Iustin Pop
    elif isinstance(target, objects.Node):
1006 e00fb268 Iustin Pop
      test = target in self._config_data.nodes.values()
1007 e00fb268 Iustin Pop
    elif isinstance(target, objects.Instance):
1008 e00fb268 Iustin Pop
      test = target in self._config_data.instances.values()
1009 e00fb268 Iustin Pop
    else:
1010 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
1011 3ecf6786 Iustin Pop
                                   " ConfigWriter.Update" % type(target))
1012 e00fb268 Iustin Pop
    if not test:
1013 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Configuration updated since object"
1014 3ecf6786 Iustin Pop
                                      " has been read or unknown object")
1015 b989e85d Iustin Pop
    target.serial_no += 1
1016 b989e85d Iustin Pop
1017 e00fb268 Iustin Pop
    self._WriteConfig()