Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 87622829

History | View | Annotate | Download (30.7 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._offline = offline
68 a8083063 Iustin Pop
    if cfg_file is None:
69 a8083063 Iustin Pop
      self._cfg_file = constants.CLUSTER_CONF_FILE
70 a8083063 Iustin Pop
    else:
71 a8083063 Iustin Pop
      self._cfg_file = cfg_file
72 923b1523 Iustin Pop
    self._temporary_ids = set()
73 a81c53c9 Iustin Pop
    self._temporary_drbds = {}
74 89e1fc26 Iustin Pop
    # Note: in order to prevent errors when resolving our name in
75 89e1fc26 Iustin Pop
    # _DistributeConfig, we compute it here once and reuse it; it's
76 89e1fc26 Iustin Pop
    # better to raise an error before starting to modify the config
77 89e1fc26 Iustin Pop
    # file than after it was modified
78 89e1fc26 Iustin Pop
    self._my_hostname = utils.HostInfo().name
79 3d3a04bc Iustin Pop
    self._OpenConfig()
80 a8083063 Iustin Pop
81 a8083063 Iustin Pop
  # this method needs to be static, so that we can call it on the class
82 a8083063 Iustin Pop
  @staticmethod
83 a8083063 Iustin Pop
  def IsCluster():
84 a8083063 Iustin Pop
    """Check if the cluster is configured.
85 a8083063 Iustin Pop

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

93 a8083063 Iustin Pop
    This should check the current instances for duplicates.
94 a8083063 Iustin Pop

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

115 1862d460 Alexander Schreiber
    This only checks instances managed by this cluster, it does not
116 1862d460 Alexander Schreiber
    check for potential collisions elsewhere.
117 1862d460 Alexander Schreiber

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

126 f9518d38 Iustin Pop
    This checks the current disks for duplicates.
127 f9518d38 Iustin Pop

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

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

155 923b1523 Iustin Pop
    This checks the current node, instances and disk names for
156 923b1523 Iustin Pop
    duplicates.
157 923b1523 Iustin Pop

158 923b1523 Iustin Pop
    Args:
159 923b1523 Iustin Pop
      - exceptions: a list with some other names which should be checked
160 923b1523 Iustin Pop
                    for uniqueness (used for example when you want to get
161 923b1523 Iustin Pop
                    more than one id at one time without adding each one in
162 923b1523 Iustin Pop
                    turn to the config file
163 923b1523 Iustin Pop

164 923b1523 Iustin Pop
    Returns: the unique id as a string
165 923b1523 Iustin Pop

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

188 a8083063 Iustin Pop
    """
189 a8083063 Iustin Pop
    result = []
190 a8083063 Iustin Pop
    for instance in self._config_data.instances.values():
191 a8083063 Iustin Pop
      for nic in instance.nics:
192 a8083063 Iustin Pop
        result.append(nic.mac)
193 a8083063 Iustin Pop
194 a8083063 Iustin Pop
    return result
195 a8083063 Iustin Pop
196 f9518d38 Iustin Pop
  def _AllDRBDSecrets(self):
197 f9518d38 Iustin Pop
    """Return all DRBD secrets present in the config.
198 f9518d38 Iustin Pop

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

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

282 a8083063 Iustin Pop
    The routine descends down and updates its children also, because
283 a8083063 Iustin Pop
    this helps when the only the top device is passed to the remote
284 a8083063 Iustin Pop
    node.
285 a8083063 Iustin Pop

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

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

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

321 f78ede4e Guido Trotter
    The routine descends down and updates its children also, because
322 f78ede4e Guido Trotter
    this helps when the only the top device is passed to the remote
323 f78ede4e Guido Trotter
    node.
324 f78ede4e Guido Trotter

325 f78ede4e Guido Trotter
    """
326 f78ede4e Guido Trotter
    return self._UnlockedSetDiskID(disk, node_name)
327 f78ede4e Guido Trotter
328 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
329 b2fddf63 Iustin Pop
  def AddTcpUdpPort(self, port):
330 b2fddf63 Iustin Pop
    """Adds a new port to the available port pool.
331 b2fddf63 Iustin Pop

332 b2fddf63 Iustin Pop
    """
333 264bb3c5 Michael Hanselmann
    if not isinstance(port, int):
334 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed for port")
335 264bb3c5 Michael Hanselmann
336 b2fddf63 Iustin Pop
    self._config_data.cluster.tcpudp_port_pool.add(port)
337 264bb3c5 Michael Hanselmann
    self._WriteConfig()
338 264bb3c5 Michael Hanselmann
339 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
340 b2fddf63 Iustin Pop
  def GetPortList(self):
341 264bb3c5 Michael Hanselmann
    """Returns a copy of the current port list.
342 264bb3c5 Michael Hanselmann

343 264bb3c5 Michael Hanselmann
    """
344 b2fddf63 Iustin Pop
    return self._config_data.cluster.tcpudp_port_pool.copy()
345 264bb3c5 Michael Hanselmann
346 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
347 a8083063 Iustin Pop
  def AllocatePort(self):
348 a8083063 Iustin Pop
    """Allocate a port.
349 a8083063 Iustin Pop

350 b2fddf63 Iustin Pop
    The port will be taken from the available port pool or from the
351 b2fddf63 Iustin Pop
    default port range (and in this case we increase
352 b2fddf63 Iustin Pop
    highest_used_port).
353 a8083063 Iustin Pop

354 a8083063 Iustin Pop
    """
355 264bb3c5 Michael Hanselmann
    # If there are TCP/IP ports configured, we use them first.
356 b2fddf63 Iustin Pop
    if self._config_data.cluster.tcpudp_port_pool:
357 b2fddf63 Iustin Pop
      port = self._config_data.cluster.tcpudp_port_pool.pop()
358 264bb3c5 Michael Hanselmann
    else:
359 264bb3c5 Michael Hanselmann
      port = self._config_data.cluster.highest_used_port + 1
360 264bb3c5 Michael Hanselmann
      if port >= constants.LAST_DRBD_PORT:
361 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("The highest used port is greater"
362 3ecf6786 Iustin Pop
                                        " than %s. Aborting." %
363 3ecf6786 Iustin Pop
                                        constants.LAST_DRBD_PORT)
364 264bb3c5 Michael Hanselmann
      self._config_data.cluster.highest_used_port = port
365 a8083063 Iustin Pop
366 a8083063 Iustin Pop
    self._WriteConfig()
367 a8083063 Iustin Pop
    return port
368 a8083063 Iustin Pop
369 a81c53c9 Iustin Pop
  def _ComputeDRBDMap(self, instance):
370 a81c53c9 Iustin Pop
    """Compute the used DRBD minor/nodes.
371 a81c53c9 Iustin Pop

372 a81c53c9 Iustin Pop
    Return: dictionary of node_name: dict of minor: instance_name. The
373 a81c53c9 Iustin Pop
    returned dict will have all the nodes in it (even if with an empty
374 a81c53c9 Iustin Pop
    list).
375 a81c53c9 Iustin Pop

376 a81c53c9 Iustin Pop
    """
377 a81c53c9 Iustin Pop
    def _AppendUsedPorts(instance_name, disk, used):
378 f9518d38 Iustin Pop
      if disk.dev_type == constants.LD_DRBD8 and len(disk.logical_id) >= 5:
379 f9518d38 Iustin Pop
        nodeA, nodeB, dummy, minorA, minorB = disk.logical_id[:5]
380 a81c53c9 Iustin Pop
        for node, port in ((nodeA, minorA), (nodeB, minorB)):
381 a81c53c9 Iustin Pop
          assert node in used, "Instance node not found in node list"
382 a81c53c9 Iustin Pop
          if port in used[node]:
383 a81c53c9 Iustin Pop
            raise errors.ProgrammerError("DRBD minor already used:"
384 a81c53c9 Iustin Pop
                                         " %s/%s, %s/%s" %
385 a81c53c9 Iustin Pop
                                         (node, port, instance_name,
386 a81c53c9 Iustin Pop
                                          used[node][port]))
387 a81c53c9 Iustin Pop
388 a81c53c9 Iustin Pop
          used[node][port] = instance_name
389 a81c53c9 Iustin Pop
      if disk.children:
390 a81c53c9 Iustin Pop
        for child in disk.children:
391 a81c53c9 Iustin Pop
          _AppendUsedPorts(instance_name, child, used)
392 a81c53c9 Iustin Pop
393 a81c53c9 Iustin Pop
    my_dict = dict((node, {}) for node in self._config_data.nodes)
394 a81c53c9 Iustin Pop
    for (node, minor), instance in self._temporary_drbds.iteritems():
395 a81c53c9 Iustin Pop
      my_dict[node][minor] = instance
396 a81c53c9 Iustin Pop
    for instance in self._config_data.instances.itervalues():
397 a81c53c9 Iustin Pop
      for disk in instance.disks:
398 a81c53c9 Iustin Pop
        _AppendUsedPorts(instance.name, disk, my_dict)
399 a81c53c9 Iustin Pop
    return my_dict
400 a81c53c9 Iustin Pop
401 a81c53c9 Iustin Pop
  @locking.ssynchronized(_config_lock)
402 a81c53c9 Iustin Pop
  def AllocateDRBDMinor(self, nodes, instance):
403 a81c53c9 Iustin Pop
    """Allocate a drbd minor.
404 a81c53c9 Iustin Pop

405 a81c53c9 Iustin Pop
    The free minor will be automatically computed from the existing
406 a81c53c9 Iustin Pop
    devices. A node can be given multiple times in order to allocate
407 a81c53c9 Iustin Pop
    multiple minors. The result is the list of minors, in the same
408 a81c53c9 Iustin Pop
    order as the passed nodes.
409 a81c53c9 Iustin Pop

410 a81c53c9 Iustin Pop
    """
411 a81c53c9 Iustin Pop
    d_map = self._ComputeDRBDMap(instance)
412 a81c53c9 Iustin Pop
    result = []
413 a81c53c9 Iustin Pop
    for nname in nodes:
414 a81c53c9 Iustin Pop
      ndata = d_map[nname]
415 a81c53c9 Iustin Pop
      if not ndata:
416 a81c53c9 Iustin Pop
        # no minors used, we can start at 0
417 a81c53c9 Iustin Pop
        result.append(0)
418 a81c53c9 Iustin Pop
        ndata[0] = instance
419 d48663e4 Iustin Pop
        self._temporary_drbds[(nname, 0)] = instance
420 a81c53c9 Iustin Pop
        continue
421 a81c53c9 Iustin Pop
      keys = ndata.keys()
422 a81c53c9 Iustin Pop
      keys.sort()
423 a81c53c9 Iustin Pop
      ffree = utils.FirstFree(keys)
424 a81c53c9 Iustin Pop
      if ffree is None:
425 a81c53c9 Iustin Pop
        # return the next minor
426 a81c53c9 Iustin Pop
        # TODO: implement high-limit check
427 a81c53c9 Iustin Pop
        minor = keys[-1] + 1
428 a81c53c9 Iustin Pop
      else:
429 a81c53c9 Iustin Pop
        minor = ffree
430 a81c53c9 Iustin Pop
      result.append(minor)
431 a81c53c9 Iustin Pop
      ndata[minor] = instance
432 a81c53c9 Iustin Pop
      assert (nname, minor) not in self._temporary_drbds, \
433 a81c53c9 Iustin Pop
             "Attempt to reuse reserved DRBD minor"
434 a81c53c9 Iustin Pop
      self._temporary_drbds[(nname, minor)] = instance
435 a81c53c9 Iustin Pop
    logging.debug("Request to allocate drbd minors, input: %s, returning %s",
436 a81c53c9 Iustin Pop
                  nodes, result)
437 a81c53c9 Iustin Pop
    return result
438 a81c53c9 Iustin Pop
439 a81c53c9 Iustin Pop
  @locking.ssynchronized(_config_lock)
440 a81c53c9 Iustin Pop
  def ReleaseDRBDMinors(self, instance):
441 a81c53c9 Iustin Pop
    """Release temporary drbd minors allocated for a given instance.
442 a81c53c9 Iustin Pop

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

446 a81c53c9 Iustin Pop
    @type instance: string
447 a81c53c9 Iustin Pop
    @param instance: the instance for which temporary minors should be
448 a81c53c9 Iustin Pop
                     released
449 a81c53c9 Iustin Pop

450 a81c53c9 Iustin Pop
    """
451 a81c53c9 Iustin Pop
    for key, name in self._temporary_drbds.items():
452 a81c53c9 Iustin Pop
      if name == instance:
453 a81c53c9 Iustin Pop
        del self._temporary_drbds[key]
454 a81c53c9 Iustin Pop
455 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
456 4a8b186a Michael Hanselmann
  def GetConfigVersion(self):
457 4a8b186a Michael Hanselmann
    """Get the configuration version.
458 4a8b186a Michael Hanselmann

459 4a8b186a Michael Hanselmann
    @return: Config version
460 4a8b186a Michael Hanselmann

461 4a8b186a Michael Hanselmann
    """
462 4a8b186a Michael Hanselmann
    return self._config_data.version
463 4a8b186a Michael Hanselmann
464 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
465 4a8b186a Michael Hanselmann
  def GetClusterName(self):
466 4a8b186a Michael Hanselmann
    """Get cluster name.
467 4a8b186a Michael Hanselmann

468 4a8b186a Michael Hanselmann
    @return: Cluster name
469 4a8b186a Michael Hanselmann

470 4a8b186a Michael Hanselmann
    """
471 4a8b186a Michael Hanselmann
    return self._config_data.cluster.cluster_name
472 4a8b186a Michael Hanselmann
473 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
474 4a8b186a Michael Hanselmann
  def GetMasterNode(self):
475 4a8b186a Michael Hanselmann
    """Get the hostname of the master node for this cluster.
476 4a8b186a Michael Hanselmann

477 4a8b186a Michael Hanselmann
    @return: Master hostname
478 4a8b186a Michael Hanselmann

479 4a8b186a Michael Hanselmann
    """
480 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_node
481 4a8b186a Michael Hanselmann
482 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
483 4a8b186a Michael Hanselmann
  def GetMasterIP(self):
484 4a8b186a Michael Hanselmann
    """Get the IP of the master node for this cluster.
485 4a8b186a Michael Hanselmann

486 4a8b186a Michael Hanselmann
    @return: Master IP
487 4a8b186a Michael Hanselmann

488 4a8b186a Michael Hanselmann
    """
489 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_ip
490 4a8b186a Michael Hanselmann
491 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
492 4a8b186a Michael Hanselmann
  def GetMasterNetdev(self):
493 4a8b186a Michael Hanselmann
    """Get the master network device for this cluster.
494 4a8b186a Michael Hanselmann

495 4a8b186a Michael Hanselmann
    """
496 4a8b186a Michael Hanselmann
    return self._config_data.cluster.master_netdev
497 4a8b186a Michael Hanselmann
498 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
499 4a8b186a Michael Hanselmann
  def GetFileStorageDir(self):
500 4a8b186a Michael Hanselmann
    """Get the file storage dir for this cluster.
501 4a8b186a Michael Hanselmann

502 4a8b186a Michael Hanselmann
    """
503 4a8b186a Michael Hanselmann
    return self._config_data.cluster.file_storage_dir
504 4a8b186a Michael Hanselmann
505 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
506 4a8b186a Michael Hanselmann
  def GetHypervisorType(self):
507 4a8b186a Michael Hanselmann
    """Get the hypervisor type for this cluster.
508 4a8b186a Michael Hanselmann

509 4a8b186a Michael Hanselmann
    """
510 64272529 Iustin Pop
    return self._config_data.cluster.default_hypervisor
511 4a8b186a Michael Hanselmann
512 4a8b186a Michael Hanselmann
  @locking.ssynchronized(_config_lock, shared=1)
513 a8083063 Iustin Pop
  def GetHostKey(self):
514 a8083063 Iustin Pop
    """Return the rsa hostkey from the config.
515 a8083063 Iustin Pop

516 a8083063 Iustin Pop
    Args: None
517 a8083063 Iustin Pop

518 a8083063 Iustin Pop
    Returns: rsa hostkey
519 a8083063 Iustin Pop
    """
520 a8083063 Iustin Pop
    return self._config_data.cluster.rsahostkeypub
521 a8083063 Iustin Pop
522 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
523 a8083063 Iustin Pop
  def AddInstance(self, instance):
524 a8083063 Iustin Pop
    """Add an instance to the config.
525 a8083063 Iustin Pop

526 a8083063 Iustin Pop
    This should be used after creating a new instance.
527 a8083063 Iustin Pop

528 a8083063 Iustin Pop
    Args:
529 a8083063 Iustin Pop
      instance: the instance object
530 a8083063 Iustin Pop
    """
531 a8083063 Iustin Pop
    if not isinstance(instance, objects.Instance):
532 a8083063 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
533 a8083063 Iustin Pop
534 e00fb268 Iustin Pop
    if instance.disk_template != constants.DT_DISKLESS:
535 e00fb268 Iustin Pop
      all_lvs = instance.MapLVsByNode()
536 74a48621 Iustin Pop
      logging.info("Instance '%s' DISK_LAYOUT: %s", instance.name, all_lvs)
537 923b1523 Iustin Pop
538 b989e85d Iustin Pop
    instance.serial_no = 1
539 a8083063 Iustin Pop
    self._config_data.instances[instance.name] = instance
540 a8083063 Iustin Pop
    self._WriteConfig()
541 a8083063 Iustin Pop
542 6a408fb2 Iustin Pop
  def _SetInstanceStatus(self, instance_name, status):
543 6a408fb2 Iustin Pop
    """Set the instance's status to a given value.
544 a8083063 Iustin Pop

545 a8083063 Iustin Pop
    """
546 6a408fb2 Iustin Pop
    if status not in ("up", "down"):
547 6a408fb2 Iustin Pop
      raise errors.ProgrammerError("Invalid status '%s' passed to"
548 6a408fb2 Iustin Pop
                                   " ConfigWriter._SetInstanceStatus()" %
549 6a408fb2 Iustin Pop
                                   status)
550 a8083063 Iustin Pop
551 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
552 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" %
553 3ecf6786 Iustin Pop
                                      instance_name)
554 a8083063 Iustin Pop
    instance = self._config_data.instances[instance_name]
555 455a3445 Iustin Pop
    if instance.status != status:
556 455a3445 Iustin Pop
      instance.status = status
557 b989e85d Iustin Pop
      instance.serial_no += 1
558 455a3445 Iustin Pop
      self._WriteConfig()
559 a8083063 Iustin Pop
560 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
561 6a408fb2 Iustin Pop
  def MarkInstanceUp(self, instance_name):
562 6a408fb2 Iustin Pop
    """Mark the instance status to up in the config.
563 6a408fb2 Iustin Pop

564 6a408fb2 Iustin Pop
    """
565 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "up")
566 6a408fb2 Iustin Pop
567 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
568 a8083063 Iustin Pop
  def RemoveInstance(self, instance_name):
569 a8083063 Iustin Pop
    """Remove the instance from the configuration.
570 a8083063 Iustin Pop

571 a8083063 Iustin Pop
    """
572 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
573 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
574 a8083063 Iustin Pop
    del self._config_data.instances[instance_name]
575 a8083063 Iustin Pop
    self._WriteConfig()
576 a8083063 Iustin Pop
577 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
578 fc95f88f Iustin Pop
  def RenameInstance(self, old_name, new_name):
579 fc95f88f Iustin Pop
    """Rename an instance.
580 fc95f88f Iustin Pop

581 fc95f88f Iustin Pop
    This needs to be done in ConfigWriter and not by RemoveInstance
582 fc95f88f Iustin Pop
    combined with AddInstance as only we can guarantee an atomic
583 fc95f88f Iustin Pop
    rename.
584 fc95f88f Iustin Pop

585 fc95f88f Iustin Pop
    """
586 fc95f88f Iustin Pop
    if old_name not in self._config_data.instances:
587 fc95f88f Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
588 fc95f88f Iustin Pop
    inst = self._config_data.instances[old_name]
589 fc95f88f Iustin Pop
    del self._config_data.instances[old_name]
590 fc95f88f Iustin Pop
    inst.name = new_name
591 b23c4333 Manuel Franceschini
592 b23c4333 Manuel Franceschini
    for disk in inst.disks:
593 b23c4333 Manuel Franceschini
      if disk.dev_type == constants.LD_FILE:
594 b23c4333 Manuel Franceschini
        # rename the file paths in logical and physical id
595 b23c4333 Manuel Franceschini
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
596 b23c4333 Manuel Franceschini
        disk.physical_id = disk.logical_id = (disk.logical_id[0],
597 b23c4333 Manuel Franceschini
                                              os.path.join(file_storage_dir,
598 b23c4333 Manuel Franceschini
                                                           inst.name,
599 b23c4333 Manuel Franceschini
                                                           disk.iv_name))
600 b23c4333 Manuel Franceschini
601 fc95f88f Iustin Pop
    self._config_data.instances[inst.name] = inst
602 fc95f88f Iustin Pop
    self._WriteConfig()
603 fc95f88f Iustin Pop
604 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
605 a8083063 Iustin Pop
  def MarkInstanceDown(self, instance_name):
606 a8083063 Iustin Pop
    """Mark the status of an instance to down in the configuration.
607 a8083063 Iustin Pop

608 a8083063 Iustin Pop
    """
609 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "down")
610 a8083063 Iustin Pop
611 94bbfece Iustin Pop
  def _UnlockedGetInstanceList(self):
612 94bbfece Iustin Pop
    """Get the list of instances.
613 94bbfece Iustin Pop

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

616 94bbfece Iustin Pop
    """
617 94bbfece Iustin Pop
    return self._config_data.instances.keys()
618 94bbfece Iustin Pop
619 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
620 a8083063 Iustin Pop
  def GetInstanceList(self):
621 a8083063 Iustin Pop
    """Get the list of instances.
622 a8083063 Iustin Pop

623 a8083063 Iustin Pop
    Returns:
624 a8083063 Iustin Pop
      array of instances, ex. ['instance2.example.com','instance1.example.com']
625 a8083063 Iustin Pop
      these contains all the instances, also the ones in Admin_down state
626 a8083063 Iustin Pop

627 a8083063 Iustin Pop
    """
628 94bbfece Iustin Pop
    return self._UnlockedGetInstanceList()
629 a8083063 Iustin Pop
630 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
631 a8083063 Iustin Pop
  def ExpandInstanceName(self, short_name):
632 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
633 a8083063 Iustin Pop

634 a8083063 Iustin Pop
    """
635 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
636 a8083063 Iustin Pop
                                    self._config_data.instances.keys())
637 a8083063 Iustin Pop
638 94bbfece Iustin Pop
  def _UnlockedGetInstanceInfo(self, instance_name):
639 94bbfece Iustin Pop
    """Returns informations about an instance.
640 94bbfece Iustin Pop

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

643 94bbfece Iustin Pop
    """
644 94bbfece Iustin Pop
    if instance_name not in self._config_data.instances:
645 94bbfece Iustin Pop
      return None
646 94bbfece Iustin Pop
647 94bbfece Iustin Pop
    return self._config_data.instances[instance_name]
648 94bbfece Iustin Pop
649 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
650 a8083063 Iustin Pop
  def GetInstanceInfo(self, instance_name):
651 a8083063 Iustin Pop
    """Returns informations about an instance.
652 a8083063 Iustin Pop

653 a8083063 Iustin Pop
    It takes the information from the configuration file. Other informations of
654 a8083063 Iustin Pop
    an instance are taken from the live systems.
655 a8083063 Iustin Pop

656 a8083063 Iustin Pop
    Args:
657 a8083063 Iustin Pop
      instance: name of the instance, ex instance1.example.com
658 a8083063 Iustin Pop

659 a8083063 Iustin Pop
    Returns:
660 a8083063 Iustin Pop
      the instance object
661 a8083063 Iustin Pop

662 a8083063 Iustin Pop
    """
663 94bbfece Iustin Pop
    return self._UnlockedGetInstanceInfo(instance_name)
664 a8083063 Iustin Pop
665 0b2de758 Iustin Pop
  @locking.ssynchronized(_config_lock, shared=1)
666 0b2de758 Iustin Pop
  def GetAllInstancesInfo(self):
667 0b2de758 Iustin Pop
    """Get the configuration of all instances.
668 0b2de758 Iustin Pop

669 0b2de758 Iustin Pop
    @rtype: dict
670 0b2de758 Iustin Pop
    @returns: dict of (instance, instance_info), where instance_info is what
671 0b2de758 Iustin Pop
              would GetInstanceInfo return for the node
672 0b2de758 Iustin Pop

673 0b2de758 Iustin Pop
    """
674 64d3bd52 Guido Trotter
    my_dict = dict([(instance, self._UnlockedGetInstanceInfo(instance))
675 64d3bd52 Guido Trotter
                    for instance in self._UnlockedGetInstanceList()])
676 0b2de758 Iustin Pop
    return my_dict
677 0b2de758 Iustin Pop
678 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
679 a8083063 Iustin Pop
  def AddNode(self, node):
680 a8083063 Iustin Pop
    """Add a node to the configuration.
681 a8083063 Iustin Pop

682 a8083063 Iustin Pop
    Args:
683 a8083063 Iustin Pop
      node: an object.Node instance
684 a8083063 Iustin Pop

685 a8083063 Iustin Pop
    """
686 d8470559 Michael Hanselmann
    logging.info("Adding node %s to configuration" % node.name)
687 d8470559 Michael Hanselmann
688 b989e85d Iustin Pop
    node.serial_no = 1
689 a8083063 Iustin Pop
    self._config_data.nodes[node.name] = node
690 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
691 a8083063 Iustin Pop
    self._WriteConfig()
692 a8083063 Iustin Pop
693 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
694 a8083063 Iustin Pop
  def RemoveNode(self, node_name):
695 a8083063 Iustin Pop
    """Remove a node from the configuration.
696 a8083063 Iustin Pop

697 a8083063 Iustin Pop
    """
698 d8470559 Michael Hanselmann
    logging.info("Removing node %s from configuration" % node_name)
699 d8470559 Michael Hanselmann
700 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
701 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
702 a8083063 Iustin Pop
703 a8083063 Iustin Pop
    del self._config_data.nodes[node_name]
704 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
705 a8083063 Iustin Pop
    self._WriteConfig()
706 a8083063 Iustin Pop
707 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
708 a8083063 Iustin Pop
  def ExpandNodeName(self, short_name):
709 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
710 a8083063 Iustin Pop

711 a8083063 Iustin Pop
    """
712 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
713 a8083063 Iustin Pop
                                    self._config_data.nodes.keys())
714 a8083063 Iustin Pop
715 f78ede4e Guido Trotter
  def _UnlockedGetNodeInfo(self, node_name):
716 a8083063 Iustin Pop
    """Get the configuration of a node, as stored in the config.
717 a8083063 Iustin Pop

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

720 a8083063 Iustin Pop
    Args: node: nodename (tuple) of the node
721 a8083063 Iustin Pop

722 a8083063 Iustin Pop
    Returns: the node object
723 a8083063 Iustin Pop

724 a8083063 Iustin Pop
    """
725 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
726 a8083063 Iustin Pop
      return None
727 a8083063 Iustin Pop
728 a8083063 Iustin Pop
    return self._config_data.nodes[node_name]
729 a8083063 Iustin Pop
730 f78ede4e Guido Trotter
731 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
732 f78ede4e Guido Trotter
  def GetNodeInfo(self, node_name):
733 f78ede4e Guido Trotter
    """Get the configuration of a node, as stored in the config.
734 f78ede4e Guido Trotter

735 f78ede4e Guido Trotter
    Args: node: nodename (tuple) of the node
736 f78ede4e Guido Trotter

737 f78ede4e Guido Trotter
    Returns: the node object
738 f78ede4e Guido Trotter

739 f78ede4e Guido Trotter
    """
740 f78ede4e Guido Trotter
    return self._UnlockedGetNodeInfo(node_name)
741 f78ede4e Guido Trotter
742 f78ede4e Guido Trotter
  def _UnlockedGetNodeList(self):
743 a8083063 Iustin Pop
    """Return the list of nodes which are in the configuration.
744 a8083063 Iustin Pop

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

747 a8083063 Iustin Pop
    """
748 a8083063 Iustin Pop
    return self._config_data.nodes.keys()
749 a8083063 Iustin Pop
750 f78ede4e Guido Trotter
751 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
752 f78ede4e Guido Trotter
  def GetNodeList(self):
753 f78ede4e Guido Trotter
    """Return the list of nodes which are in the configuration.
754 f78ede4e Guido Trotter

755 f78ede4e Guido Trotter
    """
756 f78ede4e Guido Trotter
    return self._UnlockedGetNodeList()
757 f78ede4e Guido Trotter
758 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
759 d65e5776 Iustin Pop
  def GetAllNodesInfo(self):
760 d65e5776 Iustin Pop
    """Get the configuration of all nodes.
761 d65e5776 Iustin Pop

762 d65e5776 Iustin Pop
    @rtype: dict
763 d65e5776 Iustin Pop
    @returns: dict of (node, node_info), where node_info is what
764 d65e5776 Iustin Pop
              would GetNodeInfo return for the node
765 d65e5776 Iustin Pop

766 d65e5776 Iustin Pop
    """
767 d65e5776 Iustin Pop
    my_dict = dict([(node, self._UnlockedGetNodeInfo(node))
768 d65e5776 Iustin Pop
                    for node in self._UnlockedGetNodeList()])
769 d65e5776 Iustin Pop
    return my_dict
770 d65e5776 Iustin Pop
771 a8083063 Iustin Pop
  def _BumpSerialNo(self):
772 a8083063 Iustin Pop
    """Bump up the serial number of the config.
773 a8083063 Iustin Pop

774 a8083063 Iustin Pop
    """
775 9d38c6e1 Iustin Pop
    self._config_data.serial_no += 1
776 a8083063 Iustin Pop
777 a8083063 Iustin Pop
  def _OpenConfig(self):
778 a8083063 Iustin Pop
    """Read the config data from disk.
779 a8083063 Iustin Pop

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

784 a8083063 Iustin Pop
    """
785 a8083063 Iustin Pop
    f = open(self._cfg_file, 'r')
786 a8083063 Iustin Pop
    try:
787 a8083063 Iustin Pop
      try:
788 8d14b30d Iustin Pop
        data = objects.ConfigData.FromDict(serializer.Load(f.read()))
789 a8083063 Iustin Pop
      except Exception, err:
790 3ecf6786 Iustin Pop
        raise errors.ConfigurationError(err)
791 a8083063 Iustin Pop
    finally:
792 a8083063 Iustin Pop
      f.close()
793 5b263ed7 Michael Hanselmann
794 5b263ed7 Michael Hanselmann
    # Make sure the configuration has the right version
795 5b263ed7 Michael Hanselmann
    _ValidateConfig(data)
796 5b263ed7 Michael Hanselmann
797 a8083063 Iustin Pop
    if (not hasattr(data, 'cluster') or
798 243cdbcc Michael Hanselmann
        not hasattr(data.cluster, 'rsahostkeypub')):
799 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Incomplete configuration"
800 243cdbcc Michael Hanselmann
                                      " (missing cluster.rsahostkeypub)")
801 a8083063 Iustin Pop
    self._config_data = data
802 0779e3aa Iustin Pop
    # init the last serial as -1 so that the next write will cause
803 0779e3aa Iustin Pop
    # ssconf update
804 0779e3aa Iustin Pop
    self._last_cluster_serial = -1
805 a8083063 Iustin Pop
806 a8083063 Iustin Pop
  def _DistributeConfig(self):
807 a8083063 Iustin Pop
    """Distribute the configuration to the other nodes.
808 a8083063 Iustin Pop

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

812 a8083063 Iustin Pop
    """
813 a8083063 Iustin Pop
    if self._offline:
814 a8083063 Iustin Pop
      return True
815 a8083063 Iustin Pop
    bad = False
816 f78ede4e Guido Trotter
    nodelist = self._UnlockedGetNodeList()
817 89e1fc26 Iustin Pop
    myhostname = self._my_hostname
818 a8083063 Iustin Pop
819 9ff994da Guido Trotter
    try:
820 9ff994da Guido Trotter
      nodelist.remove(myhostname)
821 9ff994da Guido Trotter
    except ValueError:
822 9ff994da Guido Trotter
      pass
823 6b294c53 Iustin Pop
    # we can skip checking whether _UnlockedGetNodeInfo returns None
824 6b294c53 Iustin Pop
    # since the node list comes from _UnlocketGetNodeList, and we are
825 6b294c53 Iustin Pop
    # called with the lock held, so no modifications should take place
826 6b294c53 Iustin Pop
    # in between
827 6b294c53 Iustin Pop
    address_list = [self._UnlockedGetNodeInfo(name).primary_ip
828 6b294c53 Iustin Pop
                    for name in nodelist]
829 6b294c53 Iustin Pop
830 6b294c53 Iustin Pop
    result = rpc.RpcRunner.call_upload_file(nodelist, self._cfg_file,
831 6b294c53 Iustin Pop
                                            address_list=address_list)
832 41362e70 Guido Trotter
    for node in nodelist:
833 a8083063 Iustin Pop
      if not result[node]:
834 74a48621 Iustin Pop
        logging.error("copy of file %s to node %s failed",
835 74a48621 Iustin Pop
                      self._cfg_file, node)
836 a8083063 Iustin Pop
        bad = True
837 a8083063 Iustin Pop
    return not bad
838 a8083063 Iustin Pop
839 a8083063 Iustin Pop
  def _WriteConfig(self, destination=None):
840 a8083063 Iustin Pop
    """Write the configuration data to persistent storage.
841 a8083063 Iustin Pop

842 a8083063 Iustin Pop
    """
843 a8083063 Iustin Pop
    if destination is None:
844 a8083063 Iustin Pop
      destination = self._cfg_file
845 a8083063 Iustin Pop
    self._BumpSerialNo()
846 8d14b30d Iustin Pop
    txt = serializer.Dump(self._config_data.ToDict())
847 a8083063 Iustin Pop
    dir_name, file_name = os.path.split(destination)
848 a8083063 Iustin Pop
    fd, name = tempfile.mkstemp('.newconfig', file_name, dir_name)
849 a8083063 Iustin Pop
    f = os.fdopen(fd, 'w')
850 a8083063 Iustin Pop
    try:
851 8d14b30d Iustin Pop
      f.write(txt)
852 a8083063 Iustin Pop
      os.fsync(f.fileno())
853 a8083063 Iustin Pop
    finally:
854 a8083063 Iustin Pop
      f.close()
855 a8083063 Iustin Pop
    # we don't need to do os.close(fd) as f.close() did it
856 a8083063 Iustin Pop
    os.rename(name, destination)
857 14e15659 Iustin Pop
    self.write_count += 1
858 3d3a04bc Iustin Pop
859 fee9556c Iustin Pop
    # and redistribute the config file
860 a8083063 Iustin Pop
    self._DistributeConfig()
861 a8083063 Iustin Pop
862 54d1a06e Michael Hanselmann
    # Write ssconf files on all nodes (including locally)
863 0779e3aa Iustin Pop
    if self._last_cluster_serial < self._config_data.cluster.serial_no:
864 d9a855f1 Michael Hanselmann
      if not self._offline:
865 03d1dba2 Michael Hanselmann
        rpc.RpcRunner.call_write_ssconf_files(self._UnlockedGetNodeList(),
866 03d1dba2 Michael Hanselmann
                                              self._UnlockedGetSsconfValues())
867 0779e3aa Iustin Pop
      self._last_cluster_serial = self._config_data.cluster.serial_no
868 54d1a06e Michael Hanselmann
869 03d1dba2 Michael Hanselmann
  def _UnlockedGetSsconfValues(self):
870 03d1dba2 Michael Hanselmann
    return {
871 03d1dba2 Michael Hanselmann
      "cluster_name": self._config_data.cluster.cluster_name,
872 03d1dba2 Michael Hanselmann
      "master_ip": self._config_data.cluster.master_ip,
873 03d1dba2 Michael Hanselmann
      "master_netdev": self._config_data.cluster.master_netdev,
874 03d1dba2 Michael Hanselmann
      "master_node": self._config_data.cluster.master_node,
875 03d1dba2 Michael Hanselmann
      }
876 03d1dba2 Michael Hanselmann
877 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
878 f6bd6e98 Michael Hanselmann
  def InitConfig(self, version, cluster_config, master_node_config):
879 a8083063 Iustin Pop
    """Create the initial cluster configuration.
880 a8083063 Iustin Pop

881 a8083063 Iustin Pop
    It will contain the current node, which will also be the master
882 b9eeeb02 Michael Hanselmann
    node, and no instances.
883 a8083063 Iustin Pop

884 f6bd6e98 Michael Hanselmann
    @type version: int
885 f6bd6e98 Michael Hanselmann
    @param version: Configuration version
886 b9eeeb02 Michael Hanselmann
    @type cluster_config: objects.Cluster
887 b9eeeb02 Michael Hanselmann
    @param cluster_config: Cluster configuration
888 b9eeeb02 Michael Hanselmann
    @type master_node_config: objects.Node
889 b9eeeb02 Michael Hanselmann
    @param master_node_config: Master node configuration
890 b9eeeb02 Michael Hanselmann

891 b9eeeb02 Michael Hanselmann
    """
892 b9eeeb02 Michael Hanselmann
    nodes = {
893 b9eeeb02 Michael Hanselmann
      master_node_config.name: master_node_config,
894 b9eeeb02 Michael Hanselmann
      }
895 b9eeeb02 Michael Hanselmann
896 f6bd6e98 Michael Hanselmann
    self._config_data = objects.ConfigData(version=version,
897 f6bd6e98 Michael Hanselmann
                                           cluster=cluster_config,
898 b9eeeb02 Michael Hanselmann
                                           nodes=nodes,
899 a8083063 Iustin Pop
                                           instances={},
900 9d38c6e1 Iustin Pop
                                           serial_no=1)
901 a8083063 Iustin Pop
    self._WriteConfig()
902 a8083063 Iustin Pop
903 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
904 a8083063 Iustin Pop
  def GetVGName(self):
905 a8083063 Iustin Pop
    """Return the volume group name.
906 a8083063 Iustin Pop

907 a8083063 Iustin Pop
    """
908 a8083063 Iustin Pop
    return self._config_data.cluster.volume_group_name
909 a8083063 Iustin Pop
910 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
911 89ff8e15 Manuel Franceschini
  def SetVGName(self, vg_name):
912 89ff8e15 Manuel Franceschini
    """Set the volume group name.
913 89ff8e15 Manuel Franceschini

914 89ff8e15 Manuel Franceschini
    """
915 2d4011cd Manuel Franceschini
    self._config_data.cluster.volume_group_name = vg_name
916 b9f72b4e Iustin Pop
    self._config_data.cluster.serial_no += 1
917 89ff8e15 Manuel Franceschini
    self._WriteConfig()
918 89ff8e15 Manuel Franceschini
919 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
920 a8083063 Iustin Pop
  def GetDefBridge(self):
921 a8083063 Iustin Pop
    """Return the default bridge.
922 a8083063 Iustin Pop

923 a8083063 Iustin Pop
    """
924 a8083063 Iustin Pop
    return self._config_data.cluster.default_bridge
925 a8083063 Iustin Pop
926 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
927 a8083063 Iustin Pop
  def GetMACPrefix(self):
928 a8083063 Iustin Pop
    """Return the mac prefix.
929 a8083063 Iustin Pop

930 a8083063 Iustin Pop
    """
931 a8083063 Iustin Pop
    return self._config_data.cluster.mac_prefix
932 62779dd0 Iustin Pop
933 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
934 62779dd0 Iustin Pop
  def GetClusterInfo(self):
935 62779dd0 Iustin Pop
    """Returns informations about the cluster
936 62779dd0 Iustin Pop

937 62779dd0 Iustin Pop
    Returns:
938 62779dd0 Iustin Pop
      the cluster object
939 62779dd0 Iustin Pop

940 62779dd0 Iustin Pop
    """
941 62779dd0 Iustin Pop
    return self._config_data.cluster
942 e00fb268 Iustin Pop
943 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
944 e00fb268 Iustin Pop
  def Update(self, target):
945 e00fb268 Iustin Pop
    """Notify function to be called after updates.
946 e00fb268 Iustin Pop

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

953 e00fb268 Iustin Pop
    """
954 e00fb268 Iustin Pop
    if self._config_data is None:
955 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Configuration file not read,"
956 3ecf6786 Iustin Pop
                                   " cannot save.")
957 f34901f8 Iustin Pop
    update_serial = False
958 e00fb268 Iustin Pop
    if isinstance(target, objects.Cluster):
959 e00fb268 Iustin Pop
      test = target == self._config_data.cluster
960 e00fb268 Iustin Pop
    elif isinstance(target, objects.Node):
961 e00fb268 Iustin Pop
      test = target in self._config_data.nodes.values()
962 f34901f8 Iustin Pop
      update_serial = True
963 e00fb268 Iustin Pop
    elif isinstance(target, objects.Instance):
964 e00fb268 Iustin Pop
      test = target in self._config_data.instances.values()
965 e00fb268 Iustin Pop
    else:
966 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
967 3ecf6786 Iustin Pop
                                   " ConfigWriter.Update" % type(target))
968 e00fb268 Iustin Pop
    if not test:
969 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Configuration updated since object"
970 3ecf6786 Iustin Pop
                                      " has been read or unknown object")
971 f34901f8 Iustin Pop
    target.serial_no += 1
972 f34901f8 Iustin Pop
973 cff4c037 Iustin Pop
    if update_serial:
974 f34901f8 Iustin Pop
      # for node updates, we need to increase the cluster serial too
975 f34901f8 Iustin Pop
      self._config_data.cluster.serial_no += 1
976 b989e85d Iustin Pop
977 e00fb268 Iustin Pop
    self._WriteConfig()