Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 42a999d1

History | View | Annotate | Download (20.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 b23c4333 Manuel Franceschini
import re
38 a8083063 Iustin Pop
39 a8083063 Iustin Pop
from ganeti import errors
40 a8083063 Iustin Pop
from ganeti import logger
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
from ganeti import ssconf
47 243cdbcc Michael Hanselmann
48 243cdbcc Michael Hanselmann
49 243cdbcc Michael Hanselmann
def ValidateConfig():
50 243cdbcc Michael Hanselmann
  sstore = ssconf.SimpleStore()
51 243cdbcc Michael Hanselmann
52 243cdbcc Michael Hanselmann
  if sstore.GetConfigVersion() != constants.CONFIG_VERSION:
53 243cdbcc Michael Hanselmann
    raise errors.ConfigurationError("Cluster configuration version"
54 243cdbcc Michael Hanselmann
                                    " mismatch, got %s instead of %s" %
55 243cdbcc Michael Hanselmann
                                    (sstore.GetConfigVersion(),
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 a8083063 Iustin Pop
    self._config_data = None
66 a8083063 Iustin Pop
    self._config_time = None
67 264bb3c5 Michael Hanselmann
    self._config_size = None
68 264bb3c5 Michael Hanselmann
    self._config_inode = None
69 a8083063 Iustin Pop
    self._offline = offline
70 a8083063 Iustin Pop
    if cfg_file is None:
71 a8083063 Iustin Pop
      self._cfg_file = constants.CLUSTER_CONF_FILE
72 a8083063 Iustin Pop
    else:
73 a8083063 Iustin Pop
      self._cfg_file = cfg_file
74 923b1523 Iustin Pop
    self._temporary_ids = set()
75 89e1fc26 Iustin Pop
    # Note: in order to prevent errors when resolving our name in
76 89e1fc26 Iustin Pop
    # _DistributeConfig, we compute it here once and reuse it; it's
77 89e1fc26 Iustin Pop
    # better to raise an error before starting to modify the config
78 89e1fc26 Iustin Pop
    # file than after it was modified
79 89e1fc26 Iustin Pop
    self._my_hostname = utils.HostInfo().name
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 a8083063 Iustin Pop
  def GenerateMAC(self):
90 a8083063 Iustin Pop
    """Generate a MAC for an instance.
91 a8083063 Iustin Pop

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

94 a8083063 Iustin Pop
    """
95 a8083063 Iustin Pop
    self._OpenConfig()
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 1862d460 Alexander Schreiber
  def IsMacInUse(self, mac):
112 1862d460 Alexander Schreiber
    """Predicate: check if the specified MAC is in use in the Ganeti cluster.
113 1862d460 Alexander Schreiber

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

117 1862d460 Alexander Schreiber
    """
118 1862d460 Alexander Schreiber
    self._OpenConfig()
119 1862d460 Alexander Schreiber
    all_macs = self._AllMACs()
120 1862d460 Alexander Schreiber
    return mac in all_macs
121 1862d460 Alexander Schreiber
122 923b1523 Iustin Pop
  def _ComputeAllLVs(self):
123 923b1523 Iustin Pop
    """Compute the list of all LVs.
124 923b1523 Iustin Pop

125 923b1523 Iustin Pop
    """
126 923b1523 Iustin Pop
    self._OpenConfig()
127 923b1523 Iustin Pop
    lvnames = set()
128 923b1523 Iustin Pop
    for instance in self._config_data.instances.values():
129 923b1523 Iustin Pop
      node_data = instance.MapLVsByNode()
130 923b1523 Iustin Pop
      for lv_list in node_data.values():
131 923b1523 Iustin Pop
        lvnames.update(lv_list)
132 923b1523 Iustin Pop
    return lvnames
133 923b1523 Iustin Pop
134 923b1523 Iustin Pop
  def GenerateUniqueID(self, exceptions=None):
135 923b1523 Iustin Pop
    """Generate an unique disk name.
136 923b1523 Iustin Pop

137 923b1523 Iustin Pop
    This checks the current node, instances and disk names for
138 923b1523 Iustin Pop
    duplicates.
139 923b1523 Iustin Pop

140 923b1523 Iustin Pop
    Args:
141 923b1523 Iustin Pop
      - exceptions: a list with some other names which should be checked
142 923b1523 Iustin Pop
                    for uniqueness (used for example when you want to get
143 923b1523 Iustin Pop
                    more than one id at one time without adding each one in
144 923b1523 Iustin Pop
                    turn to the config file
145 923b1523 Iustin Pop

146 923b1523 Iustin Pop
    Returns: the unique id as a string
147 923b1523 Iustin Pop

148 923b1523 Iustin Pop
    """
149 923b1523 Iustin Pop
    existing = set()
150 923b1523 Iustin Pop
    existing.update(self._temporary_ids)
151 923b1523 Iustin Pop
    existing.update(self._ComputeAllLVs())
152 923b1523 Iustin Pop
    existing.update(self._config_data.instances.keys())
153 923b1523 Iustin Pop
    existing.update(self._config_data.nodes.keys())
154 923b1523 Iustin Pop
    if exceptions is not None:
155 923b1523 Iustin Pop
      existing.update(exceptions)
156 923b1523 Iustin Pop
    retries = 64
157 923b1523 Iustin Pop
    while retries > 0:
158 24818e8f Michael Hanselmann
      unique_id = utils.NewUUID()
159 923b1523 Iustin Pop
      if unique_id not in existing and unique_id is not None:
160 923b1523 Iustin Pop
        break
161 923b1523 Iustin Pop
    else:
162 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Not able generate an unique ID"
163 3ecf6786 Iustin Pop
                                      " (last tried ID: %s" % unique_id)
164 923b1523 Iustin Pop
    self._temporary_ids.add(unique_id)
165 923b1523 Iustin Pop
    return unique_id
166 923b1523 Iustin Pop
167 a8083063 Iustin Pop
  def _AllMACs(self):
168 a8083063 Iustin Pop
    """Return all MACs present in the config.
169 a8083063 Iustin Pop

170 a8083063 Iustin Pop
    """
171 a8083063 Iustin Pop
    self._OpenConfig()
172 a8083063 Iustin Pop
173 a8083063 Iustin Pop
    result = []
174 a8083063 Iustin Pop
    for instance in self._config_data.instances.values():
175 a8083063 Iustin Pop
      for nic in instance.nics:
176 a8083063 Iustin Pop
        result.append(nic.mac)
177 a8083063 Iustin Pop
178 a8083063 Iustin Pop
    return result
179 a8083063 Iustin Pop
180 a8083063 Iustin Pop
  def VerifyConfig(self):
181 a8083063 Iustin Pop
    """Stub verify function.
182 a8083063 Iustin Pop
    """
183 a8083063 Iustin Pop
    self._OpenConfig()
184 a8083063 Iustin Pop
185 a8083063 Iustin Pop
    result = []
186 a8083063 Iustin Pop
    seen_macs = []
187 a8083063 Iustin Pop
    data = self._config_data
188 a8083063 Iustin Pop
    for instance_name in data.instances:
189 a8083063 Iustin Pop
      instance = data.instances[instance_name]
190 a8083063 Iustin Pop
      if instance.primary_node not in data.nodes:
191 8522ceeb Iustin Pop
        result.append("instance '%s' has invalid primary node '%s'" %
192 a8083063 Iustin Pop
                      (instance_name, instance.primary_node))
193 a8083063 Iustin Pop
      for snode in instance.secondary_nodes:
194 a8083063 Iustin Pop
        if snode not in data.nodes:
195 8522ceeb Iustin Pop
          result.append("instance '%s' has invalid secondary node '%s'" %
196 a8083063 Iustin Pop
                        (instance_name, snode))
197 a8083063 Iustin Pop
      for idx, nic in enumerate(instance.nics):
198 a8083063 Iustin Pop
        if nic.mac in seen_macs:
199 8522ceeb Iustin Pop
          result.append("instance '%s' has NIC %d mac %s duplicate" %
200 a8083063 Iustin Pop
                        (instance_name, idx, nic.mac))
201 a8083063 Iustin Pop
        else:
202 a8083063 Iustin Pop
          seen_macs.append(nic.mac)
203 a8083063 Iustin Pop
    return result
204 a8083063 Iustin Pop
205 a8083063 Iustin Pop
  def SetDiskID(self, disk, node_name):
206 a8083063 Iustin Pop
    """Convert the unique ID to the ID needed on the target nodes.
207 a8083063 Iustin Pop

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

210 a8083063 Iustin Pop
    The routine descends down and updates its children also, because
211 a8083063 Iustin Pop
    this helps when the only the top device is passed to the remote
212 a8083063 Iustin Pop
    node.
213 a8083063 Iustin Pop

214 a8083063 Iustin Pop
    """
215 a8083063 Iustin Pop
    if disk.children:
216 a8083063 Iustin Pop
      for child in disk.children:
217 a8083063 Iustin Pop
        self.SetDiskID(child, node_name)
218 a8083063 Iustin Pop
219 a8083063 Iustin Pop
    if disk.logical_id is None and disk.physical_id is not None:
220 a8083063 Iustin Pop
      return
221 a1f445d3 Iustin Pop
    if disk.dev_type in constants.LDS_DRBD:
222 a8083063 Iustin Pop
      pnode, snode, port = disk.logical_id
223 a8083063 Iustin Pop
      if node_name not in (pnode, snode):
224 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
225 3ecf6786 Iustin Pop
                                        node_name)
226 a8083063 Iustin Pop
      pnode_info = self.GetNodeInfo(pnode)
227 a8083063 Iustin Pop
      snode_info = self.GetNodeInfo(snode)
228 a8083063 Iustin Pop
      if pnode_info is None or snode_info is None:
229 a8083063 Iustin Pop
        raise errors.ConfigurationError("Can't find primary or secondary node"
230 a8083063 Iustin Pop
                                        " for %s" % str(disk))
231 a8083063 Iustin Pop
      if pnode == node_name:
232 a8083063 Iustin Pop
        disk.physical_id = (pnode_info.secondary_ip, port,
233 a8083063 Iustin Pop
                            snode_info.secondary_ip, port)
234 a8083063 Iustin Pop
      else: # it must be secondary, we tested above
235 a8083063 Iustin Pop
        disk.physical_id = (snode_info.secondary_ip, port,
236 a8083063 Iustin Pop
                            pnode_info.secondary_ip, port)
237 a8083063 Iustin Pop
    else:
238 a8083063 Iustin Pop
      disk.physical_id = disk.logical_id
239 a8083063 Iustin Pop
    return
240 a8083063 Iustin Pop
241 b2fddf63 Iustin Pop
  def AddTcpUdpPort(self, port):
242 b2fddf63 Iustin Pop
    """Adds a new port to the available port pool.
243 b2fddf63 Iustin Pop

244 b2fddf63 Iustin Pop
    """
245 264bb3c5 Michael Hanselmann
    if not isinstance(port, int):
246 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed for port")
247 264bb3c5 Michael Hanselmann
248 264bb3c5 Michael Hanselmann
    self._OpenConfig()
249 b2fddf63 Iustin Pop
    self._config_data.cluster.tcpudp_port_pool.add(port)
250 264bb3c5 Michael Hanselmann
    self._WriteConfig()
251 264bb3c5 Michael Hanselmann
252 b2fddf63 Iustin Pop
  def GetPortList(self):
253 264bb3c5 Michael Hanselmann
    """Returns a copy of the current port list.
254 264bb3c5 Michael Hanselmann

255 264bb3c5 Michael Hanselmann
    """
256 264bb3c5 Michael Hanselmann
    self._OpenConfig()
257 b2fddf63 Iustin Pop
    return self._config_data.cluster.tcpudp_port_pool.copy()
258 264bb3c5 Michael Hanselmann
259 a8083063 Iustin Pop
  def AllocatePort(self):
260 a8083063 Iustin Pop
    """Allocate a port.
261 a8083063 Iustin Pop

262 b2fddf63 Iustin Pop
    The port will be taken from the available port pool or from the
263 b2fddf63 Iustin Pop
    default port range (and in this case we increase
264 b2fddf63 Iustin Pop
    highest_used_port).
265 a8083063 Iustin Pop

266 a8083063 Iustin Pop
    """
267 a8083063 Iustin Pop
    self._OpenConfig()
268 a8083063 Iustin Pop
269 264bb3c5 Michael Hanselmann
    # If there are TCP/IP ports configured, we use them first.
270 b2fddf63 Iustin Pop
    if self._config_data.cluster.tcpudp_port_pool:
271 b2fddf63 Iustin Pop
      port = self._config_data.cluster.tcpudp_port_pool.pop()
272 264bb3c5 Michael Hanselmann
    else:
273 264bb3c5 Michael Hanselmann
      port = self._config_data.cluster.highest_used_port + 1
274 264bb3c5 Michael Hanselmann
      if port >= constants.LAST_DRBD_PORT:
275 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("The highest used port is greater"
276 3ecf6786 Iustin Pop
                                        " than %s. Aborting." %
277 3ecf6786 Iustin Pop
                                        constants.LAST_DRBD_PORT)
278 264bb3c5 Michael Hanselmann
      self._config_data.cluster.highest_used_port = port
279 a8083063 Iustin Pop
280 a8083063 Iustin Pop
    self._WriteConfig()
281 a8083063 Iustin Pop
    return port
282 a8083063 Iustin Pop
283 a8083063 Iustin Pop
  def GetHostKey(self):
284 a8083063 Iustin Pop
    """Return the rsa hostkey from the config.
285 a8083063 Iustin Pop

286 a8083063 Iustin Pop
    Args: None
287 a8083063 Iustin Pop

288 a8083063 Iustin Pop
    Returns: rsa hostkey
289 a8083063 Iustin Pop
    """
290 a8083063 Iustin Pop
    self._OpenConfig()
291 a8083063 Iustin Pop
    return self._config_data.cluster.rsahostkeypub
292 a8083063 Iustin Pop
293 a8083063 Iustin Pop
  def AddInstance(self, instance):
294 a8083063 Iustin Pop
    """Add an instance to the config.
295 a8083063 Iustin Pop

296 a8083063 Iustin Pop
    This should be used after creating a new instance.
297 a8083063 Iustin Pop

298 a8083063 Iustin Pop
    Args:
299 a8083063 Iustin Pop
      instance: the instance object
300 a8083063 Iustin Pop
    """
301 a8083063 Iustin Pop
    if not isinstance(instance, objects.Instance):
302 a8083063 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
303 a8083063 Iustin Pop
304 e00fb268 Iustin Pop
    if instance.disk_template != constants.DT_DISKLESS:
305 e00fb268 Iustin Pop
      all_lvs = instance.MapLVsByNode()
306 e00fb268 Iustin Pop
      logger.Info("Instance '%s' DISK_LAYOUT: %s" % (instance.name, all_lvs))
307 923b1523 Iustin Pop
308 a8083063 Iustin Pop
    self._OpenConfig()
309 a8083063 Iustin Pop
    self._config_data.instances[instance.name] = instance
310 a8083063 Iustin Pop
    self._WriteConfig()
311 a8083063 Iustin Pop
312 6a408fb2 Iustin Pop
  def _SetInstanceStatus(self, instance_name, status):
313 6a408fb2 Iustin Pop
    """Set the instance's status to a given value.
314 a8083063 Iustin Pop

315 a8083063 Iustin Pop
    """
316 6a408fb2 Iustin Pop
    if status not in ("up", "down"):
317 6a408fb2 Iustin Pop
      raise errors.ProgrammerError("Invalid status '%s' passed to"
318 6a408fb2 Iustin Pop
                                   " ConfigWriter._SetInstanceStatus()" %
319 6a408fb2 Iustin Pop
                                   status)
320 a8083063 Iustin Pop
    self._OpenConfig()
321 a8083063 Iustin Pop
322 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
323 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" %
324 3ecf6786 Iustin Pop
                                      instance_name)
325 a8083063 Iustin Pop
    instance = self._config_data.instances[instance_name]
326 455a3445 Iustin Pop
    if instance.status != status:
327 455a3445 Iustin Pop
      instance.status = status
328 455a3445 Iustin Pop
      self._WriteConfig()
329 a8083063 Iustin Pop
330 6a408fb2 Iustin Pop
  def MarkInstanceUp(self, instance_name):
331 6a408fb2 Iustin Pop
    """Mark the instance status to up in the config.
332 6a408fb2 Iustin Pop

333 6a408fb2 Iustin Pop
    """
334 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "up")
335 6a408fb2 Iustin Pop
336 a8083063 Iustin Pop
  def RemoveInstance(self, instance_name):
337 a8083063 Iustin Pop
    """Remove the instance from the configuration.
338 a8083063 Iustin Pop

339 a8083063 Iustin Pop
    """
340 a8083063 Iustin Pop
    self._OpenConfig()
341 a8083063 Iustin Pop
342 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
343 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
344 a8083063 Iustin Pop
    del self._config_data.instances[instance_name]
345 a8083063 Iustin Pop
    self._WriteConfig()
346 a8083063 Iustin Pop
347 fc95f88f Iustin Pop
  def RenameInstance(self, old_name, new_name):
348 fc95f88f Iustin Pop
    """Rename an instance.
349 fc95f88f Iustin Pop

350 fc95f88f Iustin Pop
    This needs to be done in ConfigWriter and not by RemoveInstance
351 fc95f88f Iustin Pop
    combined with AddInstance as only we can guarantee an atomic
352 fc95f88f Iustin Pop
    rename.
353 fc95f88f Iustin Pop

354 fc95f88f Iustin Pop
    """
355 fc95f88f Iustin Pop
    self._OpenConfig()
356 fc95f88f Iustin Pop
    if old_name not in self._config_data.instances:
357 fc95f88f Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
358 fc95f88f Iustin Pop
    inst = self._config_data.instances[old_name]
359 fc95f88f Iustin Pop
    del self._config_data.instances[old_name]
360 fc95f88f Iustin Pop
    inst.name = new_name
361 b23c4333 Manuel Franceschini
362 b23c4333 Manuel Franceschini
    for disk in inst.disks:
363 b23c4333 Manuel Franceschini
      if disk.dev_type == constants.LD_FILE:
364 b23c4333 Manuel Franceschini
        # rename the file paths in logical and physical id
365 b23c4333 Manuel Franceschini
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
366 b23c4333 Manuel Franceschini
        disk.physical_id = disk.logical_id = (disk.logical_id[0],
367 b23c4333 Manuel Franceschini
                                              os.path.join(file_storage_dir,
368 b23c4333 Manuel Franceschini
                                                           inst.name,
369 b23c4333 Manuel Franceschini
                                                           disk.iv_name))
370 b23c4333 Manuel Franceschini
371 fc95f88f Iustin Pop
    self._config_data.instances[inst.name] = inst
372 fc95f88f Iustin Pop
    self._WriteConfig()
373 fc95f88f Iustin Pop
374 a8083063 Iustin Pop
  def MarkInstanceDown(self, instance_name):
375 a8083063 Iustin Pop
    """Mark the status of an instance to down in the configuration.
376 a8083063 Iustin Pop

377 a8083063 Iustin Pop
    """
378 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "down")
379 a8083063 Iustin Pop
380 a8083063 Iustin Pop
  def GetInstanceList(self):
381 a8083063 Iustin Pop
    """Get the list of instances.
382 a8083063 Iustin Pop

383 a8083063 Iustin Pop
    Returns:
384 a8083063 Iustin Pop
      array of instances, ex. ['instance2.example.com','instance1.example.com']
385 a8083063 Iustin Pop
      these contains all the instances, also the ones in Admin_down state
386 a8083063 Iustin Pop

387 a8083063 Iustin Pop
    """
388 a8083063 Iustin Pop
    self._OpenConfig()
389 a8083063 Iustin Pop
390 a8083063 Iustin Pop
    return self._config_data.instances.keys()
391 a8083063 Iustin Pop
392 a8083063 Iustin Pop
  def ExpandInstanceName(self, short_name):
393 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
394 a8083063 Iustin Pop

395 a8083063 Iustin Pop
    """
396 a8083063 Iustin Pop
    self._OpenConfig()
397 a8083063 Iustin Pop
398 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
399 a8083063 Iustin Pop
                                    self._config_data.instances.keys())
400 a8083063 Iustin Pop
401 a8083063 Iustin Pop
  def GetInstanceInfo(self, instance_name):
402 a8083063 Iustin Pop
    """Returns informations about an instance.
403 a8083063 Iustin Pop

404 a8083063 Iustin Pop
    It takes the information from the configuration file. Other informations of
405 a8083063 Iustin Pop
    an instance are taken from the live systems.
406 a8083063 Iustin Pop

407 a8083063 Iustin Pop
    Args:
408 a8083063 Iustin Pop
      instance: name of the instance, ex instance1.example.com
409 a8083063 Iustin Pop

410 a8083063 Iustin Pop
    Returns:
411 a8083063 Iustin Pop
      the instance object
412 a8083063 Iustin Pop

413 a8083063 Iustin Pop
    """
414 a8083063 Iustin Pop
    self._OpenConfig()
415 a8083063 Iustin Pop
416 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
417 a8083063 Iustin Pop
      return None
418 a8083063 Iustin Pop
419 a8083063 Iustin Pop
    return self._config_data.instances[instance_name]
420 a8083063 Iustin Pop
421 a8083063 Iustin Pop
  def AddNode(self, node):
422 a8083063 Iustin Pop
    """Add a node to the configuration.
423 a8083063 Iustin Pop

424 a8083063 Iustin Pop
    Args:
425 a8083063 Iustin Pop
      node: an object.Node instance
426 a8083063 Iustin Pop

427 a8083063 Iustin Pop
    """
428 a8083063 Iustin Pop
    self._OpenConfig()
429 a8083063 Iustin Pop
    self._config_data.nodes[node.name] = node
430 a8083063 Iustin Pop
    self._WriteConfig()
431 a8083063 Iustin Pop
432 a8083063 Iustin Pop
  def RemoveNode(self, node_name):
433 a8083063 Iustin Pop
    """Remove a node from the configuration.
434 a8083063 Iustin Pop

435 a8083063 Iustin Pop
    """
436 a8083063 Iustin Pop
    self._OpenConfig()
437 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
438 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
439 a8083063 Iustin Pop
440 a8083063 Iustin Pop
    del self._config_data.nodes[node_name]
441 a8083063 Iustin Pop
    self._WriteConfig()
442 a8083063 Iustin Pop
443 a8083063 Iustin Pop
  def ExpandNodeName(self, short_name):
444 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
445 a8083063 Iustin Pop

446 a8083063 Iustin Pop
    """
447 a8083063 Iustin Pop
    self._OpenConfig()
448 a8083063 Iustin Pop
449 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
450 a8083063 Iustin Pop
                                    self._config_data.nodes.keys())
451 a8083063 Iustin Pop
452 a8083063 Iustin Pop
  def GetNodeInfo(self, node_name):
453 a8083063 Iustin Pop
    """Get the configuration of a node, as stored in the config.
454 a8083063 Iustin Pop

455 a8083063 Iustin Pop
    Args: node: nodename (tuple) of the node
456 a8083063 Iustin Pop

457 a8083063 Iustin Pop
    Returns: the node object
458 a8083063 Iustin Pop

459 a8083063 Iustin Pop
    """
460 a8083063 Iustin Pop
    self._OpenConfig()
461 a8083063 Iustin Pop
462 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
463 a8083063 Iustin Pop
      return None
464 a8083063 Iustin Pop
465 a8083063 Iustin Pop
    return self._config_data.nodes[node_name]
466 a8083063 Iustin Pop
467 a8083063 Iustin Pop
  def GetNodeList(self):
468 a8083063 Iustin Pop
    """Return the list of nodes which are in the configuration.
469 a8083063 Iustin Pop

470 a8083063 Iustin Pop
    """
471 a8083063 Iustin Pop
    self._OpenConfig()
472 a8083063 Iustin Pop
    return self._config_data.nodes.keys()
473 a8083063 Iustin Pop
474 a8083063 Iustin Pop
  def DumpConfig(self):
475 a8083063 Iustin Pop
    """Return the entire configuration of the cluster.
476 a8083063 Iustin Pop
    """
477 a8083063 Iustin Pop
    self._OpenConfig()
478 a8083063 Iustin Pop
    return self._config_data
479 a8083063 Iustin Pop
480 a8083063 Iustin Pop
  def _BumpSerialNo(self):
481 a8083063 Iustin Pop
    """Bump up the serial number of the config.
482 a8083063 Iustin Pop

483 a8083063 Iustin Pop
    """
484 a8083063 Iustin Pop
    self._config_data.cluster.serial_no += 1
485 a8083063 Iustin Pop
486 a8083063 Iustin Pop
  def _OpenConfig(self):
487 a8083063 Iustin Pop
    """Read the config data from disk.
488 a8083063 Iustin Pop

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

493 a8083063 Iustin Pop
    """
494 a8083063 Iustin Pop
    try:
495 a8083063 Iustin Pop
      st = os.stat(self._cfg_file)
496 a8083063 Iustin Pop
    except OSError, err:
497 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Can't stat config file: %s" % err)
498 a8083063 Iustin Pop
    if (self._config_data is not None and
499 a8083063 Iustin Pop
        self._config_time is not None and
500 264bb3c5 Michael Hanselmann
        self._config_time == st.st_mtime and
501 264bb3c5 Michael Hanselmann
        self._config_size == st.st_size and
502 264bb3c5 Michael Hanselmann
        self._config_inode == st.st_ino):
503 a8083063 Iustin Pop
      # data is current, so skip loading of config file
504 a8083063 Iustin Pop
      return
505 243cdbcc Michael Hanselmann
506 243cdbcc Michael Hanselmann
    # Make sure the configuration has the right version
507 243cdbcc Michael Hanselmann
    ValidateConfig()
508 243cdbcc Michael Hanselmann
509 a8083063 Iustin Pop
    f = open(self._cfg_file, 'r')
510 a8083063 Iustin Pop
    try:
511 a8083063 Iustin Pop
      try:
512 8d14b30d Iustin Pop
        data = objects.ConfigData.FromDict(serializer.Load(f.read()))
513 a8083063 Iustin Pop
      except Exception, err:
514 3ecf6786 Iustin Pop
        raise errors.ConfigurationError(err)
515 a8083063 Iustin Pop
    finally:
516 a8083063 Iustin Pop
      f.close()
517 a8083063 Iustin Pop
    if (not hasattr(data, 'cluster') or
518 243cdbcc Michael Hanselmann
        not hasattr(data.cluster, 'rsahostkeypub')):
519 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Incomplete configuration"
520 243cdbcc Michael Hanselmann
                                      " (missing cluster.rsahostkeypub)")
521 a8083063 Iustin Pop
    self._config_data = data
522 a8083063 Iustin Pop
    self._config_time = st.st_mtime
523 264bb3c5 Michael Hanselmann
    self._config_size = st.st_size
524 264bb3c5 Michael Hanselmann
    self._config_inode = st.st_ino
525 a8083063 Iustin Pop
526 a8083063 Iustin Pop
  def _DistributeConfig(self):
527 a8083063 Iustin Pop
    """Distribute the configuration to the other nodes.
528 a8083063 Iustin Pop

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

532 a8083063 Iustin Pop
    """
533 a8083063 Iustin Pop
    if self._offline:
534 a8083063 Iustin Pop
      return True
535 a8083063 Iustin Pop
    bad = False
536 a8083063 Iustin Pop
    nodelist = self.GetNodeList()
537 89e1fc26 Iustin Pop
    myhostname = self._my_hostname
538 a8083063 Iustin Pop
539 9ff994da Guido Trotter
    try:
540 9ff994da Guido Trotter
      nodelist.remove(myhostname)
541 9ff994da Guido Trotter
    except ValueError:
542 9ff994da Guido Trotter
      pass
543 a8083063 Iustin Pop
544 41362e70 Guido Trotter
    result = rpc.call_upload_file(nodelist, self._cfg_file)
545 41362e70 Guido Trotter
    for node in nodelist:
546 a8083063 Iustin Pop
      if not result[node]:
547 a8083063 Iustin Pop
        logger.Error("copy of file %s to node %s failed" %
548 a8083063 Iustin Pop
                     (self._cfg_file, node))
549 a8083063 Iustin Pop
        bad = True
550 a8083063 Iustin Pop
    return not bad
551 a8083063 Iustin Pop
552 a8083063 Iustin Pop
  def _WriteConfig(self, destination=None):
553 a8083063 Iustin Pop
    """Write the configuration data to persistent storage.
554 a8083063 Iustin Pop

555 a8083063 Iustin Pop
    """
556 a8083063 Iustin Pop
    if destination is None:
557 a8083063 Iustin Pop
      destination = self._cfg_file
558 a8083063 Iustin Pop
    self._BumpSerialNo()
559 8d14b30d Iustin Pop
    txt = serializer.Dump(self._config_data.ToDict())
560 a8083063 Iustin Pop
    dir_name, file_name = os.path.split(destination)
561 a8083063 Iustin Pop
    fd, name = tempfile.mkstemp('.newconfig', file_name, dir_name)
562 a8083063 Iustin Pop
    f = os.fdopen(fd, 'w')
563 a8083063 Iustin Pop
    try:
564 8d14b30d Iustin Pop
      f.write(txt)
565 a8083063 Iustin Pop
      os.fsync(f.fileno())
566 a8083063 Iustin Pop
    finally:
567 a8083063 Iustin Pop
      f.close()
568 a8083063 Iustin Pop
    # we don't need to do os.close(fd) as f.close() did it
569 a8083063 Iustin Pop
    os.rename(name, destination)
570 14e15659 Iustin Pop
    self.write_count += 1
571 fee9556c Iustin Pop
    # re-set our cache as not to re-read the config file
572 fee9556c Iustin Pop
    try:
573 fee9556c Iustin Pop
      st = os.stat(destination)
574 fee9556c Iustin Pop
    except OSError, err:
575 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Can't stat config file: %s" % err)
576 fee9556c Iustin Pop
    self._config_time = st.st_mtime
577 fee9556c Iustin Pop
    self._config_size = st.st_size
578 fee9556c Iustin Pop
    self._config_inode = st.st_ino
579 fee9556c Iustin Pop
    # and redistribute the config file
580 a8083063 Iustin Pop
    self._DistributeConfig()
581 a8083063 Iustin Pop
582 a8083063 Iustin Pop
  def InitConfig(self, node, primary_ip, secondary_ip,
583 5fcdc80d Iustin Pop
                 hostkeypub, mac_prefix, vg_name, def_bridge):
584 a8083063 Iustin Pop
    """Create the initial cluster configuration.
585 a8083063 Iustin Pop

586 a8083063 Iustin Pop
    It will contain the current node, which will also be the master
587 a8083063 Iustin Pop
    node, and no instances or operating systmes.
588 a8083063 Iustin Pop

589 a8083063 Iustin Pop
    Args:
590 a8083063 Iustin Pop
      node: the nodename of the initial node
591 a8083063 Iustin Pop
      primary_ip: the IP address of the current host
592 a8083063 Iustin Pop
      secondary_ip: the secondary IP of the current host or None
593 a8083063 Iustin Pop
      hostkeypub: the public hostkey of this host
594 a8083063 Iustin Pop

595 264bb3c5 Michael Hanselmann
    """
596 a8083063 Iustin Pop
    hu_port = constants.FIRST_DRBD_PORT - 1
597 243cdbcc Michael Hanselmann
    globalconfig = objects.Cluster(serial_no=1,
598 a8083063 Iustin Pop
                                   rsahostkeypub=hostkeypub,
599 a8083063 Iustin Pop
                                   highest_used_port=hu_port,
600 a8083063 Iustin Pop
                                   mac_prefix=mac_prefix,
601 a8083063 Iustin Pop
                                   volume_group_name=vg_name,
602 b2fddf63 Iustin Pop
                                   default_bridge=def_bridge,
603 b2fddf63 Iustin Pop
                                   tcpudp_port_pool=set())
604 a8083063 Iustin Pop
    if secondary_ip is None:
605 a8083063 Iustin Pop
      secondary_ip = primary_ip
606 a8083063 Iustin Pop
    nodeconfig = objects.Node(name=node, primary_ip=primary_ip,
607 a8083063 Iustin Pop
                              secondary_ip=secondary_ip)
608 a8083063 Iustin Pop
609 a8083063 Iustin Pop
    self._config_data = objects.ConfigData(nodes={node: nodeconfig},
610 a8083063 Iustin Pop
                                           instances={},
611 b2fddf63 Iustin Pop
                                           cluster=globalconfig)
612 a8083063 Iustin Pop
    self._WriteConfig()
613 a8083063 Iustin Pop
614 a8083063 Iustin Pop
  def GetVGName(self):
615 a8083063 Iustin Pop
    """Return the volume group name.
616 a8083063 Iustin Pop

617 a8083063 Iustin Pop
    """
618 a8083063 Iustin Pop
    self._OpenConfig()
619 a8083063 Iustin Pop
    return self._config_data.cluster.volume_group_name
620 a8083063 Iustin Pop
621 89ff8e15 Manuel Franceschini
  def SetVGName(self, vg_name):
622 89ff8e15 Manuel Franceschini
    """Set the volume group name.
623 89ff8e15 Manuel Franceschini

624 89ff8e15 Manuel Franceschini
    """
625 89ff8e15 Manuel Franceschini
    self._OpenConfig()
626 2d4011cd Manuel Franceschini
    self._config_data.cluster.volume_group_name = vg_name
627 89ff8e15 Manuel Franceschini
    self._WriteConfig()
628 89ff8e15 Manuel Franceschini
629 a8083063 Iustin Pop
  def GetDefBridge(self):
630 a8083063 Iustin Pop
    """Return the default bridge.
631 a8083063 Iustin Pop

632 a8083063 Iustin Pop
    """
633 a8083063 Iustin Pop
    self._OpenConfig()
634 a8083063 Iustin Pop
    return self._config_data.cluster.default_bridge
635 a8083063 Iustin Pop
636 a8083063 Iustin Pop
  def GetMACPrefix(self):
637 a8083063 Iustin Pop
    """Return the mac prefix.
638 a8083063 Iustin Pop

639 a8083063 Iustin Pop
    """
640 a8083063 Iustin Pop
    self._OpenConfig()
641 a8083063 Iustin Pop
    return self._config_data.cluster.mac_prefix
642 62779dd0 Iustin Pop
643 62779dd0 Iustin Pop
  def GetClusterInfo(self):
644 62779dd0 Iustin Pop
    """Returns informations about the cluster
645 62779dd0 Iustin Pop

646 62779dd0 Iustin Pop
    Returns:
647 62779dd0 Iustin Pop
      the cluster object
648 62779dd0 Iustin Pop

649 62779dd0 Iustin Pop
    """
650 62779dd0 Iustin Pop
    self._OpenConfig()
651 62779dd0 Iustin Pop
652 62779dd0 Iustin Pop
    return self._config_data.cluster
653 e00fb268 Iustin Pop
654 e00fb268 Iustin Pop
  def Update(self, target):
655 e00fb268 Iustin Pop
    """Notify function to be called after updates.
656 e00fb268 Iustin Pop

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

663 e00fb268 Iustin Pop
    """
664 e00fb268 Iustin Pop
    if self._config_data is None:
665 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Configuration file not read,"
666 3ecf6786 Iustin Pop
                                   " cannot save.")
667 e00fb268 Iustin Pop
    if isinstance(target, objects.Cluster):
668 e00fb268 Iustin Pop
      test = target == self._config_data.cluster
669 e00fb268 Iustin Pop
    elif isinstance(target, objects.Node):
670 e00fb268 Iustin Pop
      test = target in self._config_data.nodes.values()
671 e00fb268 Iustin Pop
    elif isinstance(target, objects.Instance):
672 e00fb268 Iustin Pop
      test = target in self._config_data.instances.values()
673 e00fb268 Iustin Pop
    else:
674 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
675 3ecf6786 Iustin Pop
                                   " ConfigWriter.Update" % type(target))
676 e00fb268 Iustin Pop
    if not test:
677 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Configuration updated since object"
678 3ecf6786 Iustin Pop
                                      " has been read or unknown object")
679 e00fb268 Iustin Pop
    self._WriteConfig()