Statistics
| Branch: | Tag: | Revision:

root / lib / config.py @ 0a1e74d9

History | View | Annotate | Download (23.1 kB)

1 2f31098c Iustin Pop
#
2 a8083063 Iustin Pop
#
3 a8083063 Iustin Pop
4 a8083063 Iustin Pop
# Copyright (C) 2006, 2007 Google Inc.
5 a8083063 Iustin Pop
#
6 a8083063 Iustin Pop
# This program is free software; you can redistribute it and/or modify
7 a8083063 Iustin Pop
# it under the terms of the GNU General Public License as published by
8 a8083063 Iustin Pop
# the Free Software Foundation; either version 2 of the License, or
9 a8083063 Iustin Pop
# (at your option) any later version.
10 a8083063 Iustin Pop
#
11 a8083063 Iustin Pop
# This program is distributed in the hope that it will be useful, but
12 a8083063 Iustin Pop
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 a8083063 Iustin Pop
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 a8083063 Iustin Pop
# General Public License for more details.
15 a8083063 Iustin Pop
#
16 a8083063 Iustin Pop
# You should have received a copy of the GNU General Public License
17 a8083063 Iustin Pop
# along with this program; if not, write to the Free Software
18 a8083063 Iustin Pop
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 a8083063 Iustin Pop
# 02110-1301, USA.
20 a8083063 Iustin Pop
21 a8083063 Iustin Pop
22 a8083063 Iustin Pop
"""Configuration management for Ganeti
23 a8083063 Iustin Pop

24 319856a9 Michael Hanselmann
This module provides the interface to the Ganeti cluster configuration.
25 a8083063 Iustin Pop

26 319856a9 Michael Hanselmann
The configuration data is stored on every node but is updated on the master
27 319856a9 Michael Hanselmann
only. After each update, the master distributes the data to the other nodes.
28 a8083063 Iustin Pop

29 319856a9 Michael Hanselmann
Currently, the data storage format is JSON. YAML was slow and consuming too
30 319856a9 Michael Hanselmann
much memory.
31 a8083063 Iustin Pop

32 a8083063 Iustin Pop
"""
33 a8083063 Iustin Pop
34 a8083063 Iustin Pop
import os
35 a8083063 Iustin Pop
import tempfile
36 a8083063 Iustin Pop
import random
37 a8083063 Iustin Pop
38 a8083063 Iustin Pop
from ganeti import errors
39 f78ede4e Guido Trotter
from ganeti import locking
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 f78ede4e Guido Trotter
_config_lock = locking.SharedLock()
50 f78ede4e Guido Trotter
51 f78ede4e Guido Trotter
52 243cdbcc Michael Hanselmann
def ValidateConfig():
53 243cdbcc Michael Hanselmann
  sstore = ssconf.SimpleStore()
54 243cdbcc Michael Hanselmann
55 243cdbcc Michael Hanselmann
  if sstore.GetConfigVersion() != constants.CONFIG_VERSION:
56 243cdbcc Michael Hanselmann
    raise errors.ConfigurationError("Cluster configuration version"
57 243cdbcc Michael Hanselmann
                                    " mismatch, got %s instead of %s" %
58 243cdbcc Michael Hanselmann
                                    (sstore.GetConfigVersion(),
59 243cdbcc Michael Hanselmann
                                     constants.CONFIG_VERSION))
60 a8083063 Iustin Pop
61 319856a9 Michael Hanselmann
62 a8083063 Iustin Pop
class ConfigWriter:
63 098c0958 Michael Hanselmann
  """The interface to the cluster configuration.
64 a8083063 Iustin Pop

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

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

97 a8083063 Iustin Pop
    This should check the current instances for duplicates.
98 a8083063 Iustin Pop

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

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

123 1862d460 Alexander Schreiber
    """
124 1862d460 Alexander Schreiber
    self._OpenConfig()
125 1862d460 Alexander Schreiber
    all_macs = self._AllMACs()
126 1862d460 Alexander Schreiber
    return mac in all_macs
127 1862d460 Alexander Schreiber
128 923b1523 Iustin Pop
  def _ComputeAllLVs(self):
129 923b1523 Iustin Pop
    """Compute the list of all LVs.
130 923b1523 Iustin Pop

131 923b1523 Iustin Pop
    """
132 923b1523 Iustin Pop
    self._OpenConfig()
133 923b1523 Iustin Pop
    lvnames = set()
134 923b1523 Iustin Pop
    for instance in self._config_data.instances.values():
135 923b1523 Iustin Pop
      node_data = instance.MapLVsByNode()
136 923b1523 Iustin Pop
      for lv_list in node_data.values():
137 923b1523 Iustin Pop
        lvnames.update(lv_list)
138 923b1523 Iustin Pop
    return lvnames
139 923b1523 Iustin Pop
140 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
141 923b1523 Iustin Pop
  def GenerateUniqueID(self, exceptions=None):
142 923b1523 Iustin Pop
    """Generate an unique disk name.
143 923b1523 Iustin Pop

144 923b1523 Iustin Pop
    This checks the current node, instances and disk names for
145 923b1523 Iustin Pop
    duplicates.
146 923b1523 Iustin Pop

147 923b1523 Iustin Pop
    Args:
148 923b1523 Iustin Pop
      - exceptions: a list with some other names which should be checked
149 923b1523 Iustin Pop
                    for uniqueness (used for example when you want to get
150 923b1523 Iustin Pop
                    more than one id at one time without adding each one in
151 923b1523 Iustin Pop
                    turn to the config file
152 923b1523 Iustin Pop

153 923b1523 Iustin Pop
    Returns: the unique id as a string
154 923b1523 Iustin Pop

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

177 a8083063 Iustin Pop
    """
178 a8083063 Iustin Pop
    self._OpenConfig()
179 a8083063 Iustin Pop
180 a8083063 Iustin Pop
    result = []
181 a8083063 Iustin Pop
    for instance in self._config_data.instances.values():
182 a8083063 Iustin Pop
      for nic in instance.nics:
183 a8083063 Iustin Pop
        result.append(nic.mac)
184 a8083063 Iustin Pop
185 a8083063 Iustin Pop
    return result
186 a8083063 Iustin Pop
187 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
188 a8083063 Iustin Pop
  def VerifyConfig(self):
189 a8083063 Iustin Pop
    """Stub verify function.
190 a8083063 Iustin Pop
    """
191 a8083063 Iustin Pop
    self._OpenConfig()
192 a8083063 Iustin Pop
193 a8083063 Iustin Pop
    result = []
194 a8083063 Iustin Pop
    seen_macs = []
195 a8083063 Iustin Pop
    data = self._config_data
196 a8083063 Iustin Pop
    for instance_name in data.instances:
197 a8083063 Iustin Pop
      instance = data.instances[instance_name]
198 a8083063 Iustin Pop
      if instance.primary_node not in data.nodes:
199 8522ceeb Iustin Pop
        result.append("instance '%s' has invalid primary node '%s'" %
200 a8083063 Iustin Pop
                      (instance_name, instance.primary_node))
201 a8083063 Iustin Pop
      for snode in instance.secondary_nodes:
202 a8083063 Iustin Pop
        if snode not in data.nodes:
203 8522ceeb Iustin Pop
          result.append("instance '%s' has invalid secondary node '%s'" %
204 a8083063 Iustin Pop
                        (instance_name, snode))
205 a8083063 Iustin Pop
      for idx, nic in enumerate(instance.nics):
206 a8083063 Iustin Pop
        if nic.mac in seen_macs:
207 8522ceeb Iustin Pop
          result.append("instance '%s' has NIC %d mac %s duplicate" %
208 a8083063 Iustin Pop
                        (instance_name, idx, nic.mac))
209 a8083063 Iustin Pop
        else:
210 a8083063 Iustin Pop
          seen_macs.append(nic.mac)
211 a8083063 Iustin Pop
    return result
212 a8083063 Iustin Pop
213 f78ede4e Guido Trotter
  def _UnlockedSetDiskID(self, disk, node_name):
214 a8083063 Iustin Pop
    """Convert the unique ID to the ID needed on the target nodes.
215 a8083063 Iustin Pop

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

218 a8083063 Iustin Pop
    The routine descends down and updates its children also, because
219 a8083063 Iustin Pop
    this helps when the only the top device is passed to the remote
220 a8083063 Iustin Pop
    node.
221 a8083063 Iustin Pop

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

224 a8083063 Iustin Pop
    """
225 a8083063 Iustin Pop
    if disk.children:
226 a8083063 Iustin Pop
      for child in disk.children:
227 f78ede4e Guido Trotter
        self._UnlockedSetDiskID(child, node_name)
228 a8083063 Iustin Pop
229 a8083063 Iustin Pop
    if disk.logical_id is None and disk.physical_id is not None:
230 a8083063 Iustin Pop
      return
231 a1f445d3 Iustin Pop
    if disk.dev_type in constants.LDS_DRBD:
232 a8083063 Iustin Pop
      pnode, snode, port = disk.logical_id
233 a8083063 Iustin Pop
      if node_name not in (pnode, snode):
234 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("DRBD device not knowing node %s" %
235 3ecf6786 Iustin Pop
                                        node_name)
236 f78ede4e Guido Trotter
      pnode_info = self._UnlockedGetNodeInfo(pnode)
237 f78ede4e Guido Trotter
      snode_info = self._UnlockedGetNodeInfo(snode)
238 a8083063 Iustin Pop
      if pnode_info is None or snode_info is None:
239 a8083063 Iustin Pop
        raise errors.ConfigurationError("Can't find primary or secondary node"
240 a8083063 Iustin Pop
                                        " for %s" % str(disk))
241 a8083063 Iustin Pop
      if pnode == node_name:
242 a8083063 Iustin Pop
        disk.physical_id = (pnode_info.secondary_ip, port,
243 a8083063 Iustin Pop
                            snode_info.secondary_ip, port)
244 a8083063 Iustin Pop
      else: # it must be secondary, we tested above
245 a8083063 Iustin Pop
        disk.physical_id = (snode_info.secondary_ip, port,
246 a8083063 Iustin Pop
                            pnode_info.secondary_ip, port)
247 a8083063 Iustin Pop
    else:
248 a8083063 Iustin Pop
      disk.physical_id = disk.logical_id
249 a8083063 Iustin Pop
    return
250 a8083063 Iustin Pop
251 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
252 f78ede4e Guido Trotter
  def SetDiskID(self, disk, node_name):
253 f78ede4e Guido Trotter
    """Convert the unique ID to the ID needed on the target nodes.
254 f78ede4e Guido Trotter

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

257 f78ede4e Guido Trotter
    The routine descends down and updates its children also, because
258 f78ede4e Guido Trotter
    this helps when the only the top device is passed to the remote
259 f78ede4e Guido Trotter
    node.
260 f78ede4e Guido Trotter

261 f78ede4e Guido Trotter
    """
262 f78ede4e Guido Trotter
    return self._UnlockedSetDiskID(disk, node_name)
263 f78ede4e Guido Trotter
264 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
265 b2fddf63 Iustin Pop
  def AddTcpUdpPort(self, port):
266 b2fddf63 Iustin Pop
    """Adds a new port to the available port pool.
267 b2fddf63 Iustin Pop

268 b2fddf63 Iustin Pop
    """
269 264bb3c5 Michael Hanselmann
    if not isinstance(port, int):
270 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed for port")
271 264bb3c5 Michael Hanselmann
272 264bb3c5 Michael Hanselmann
    self._OpenConfig()
273 b2fddf63 Iustin Pop
    self._config_data.cluster.tcpudp_port_pool.add(port)
274 264bb3c5 Michael Hanselmann
    self._WriteConfig()
275 264bb3c5 Michael Hanselmann
276 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
277 b2fddf63 Iustin Pop
  def GetPortList(self):
278 264bb3c5 Michael Hanselmann
    """Returns a copy of the current port list.
279 264bb3c5 Michael Hanselmann

280 264bb3c5 Michael Hanselmann
    """
281 264bb3c5 Michael Hanselmann
    self._OpenConfig()
282 b2fddf63 Iustin Pop
    return self._config_data.cluster.tcpudp_port_pool.copy()
283 264bb3c5 Michael Hanselmann
284 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
285 a8083063 Iustin Pop
  def AllocatePort(self):
286 a8083063 Iustin Pop
    """Allocate a port.
287 a8083063 Iustin Pop

288 b2fddf63 Iustin Pop
    The port will be taken from the available port pool or from the
289 b2fddf63 Iustin Pop
    default port range (and in this case we increase
290 b2fddf63 Iustin Pop
    highest_used_port).
291 a8083063 Iustin Pop

292 a8083063 Iustin Pop
    """
293 a8083063 Iustin Pop
    self._OpenConfig()
294 a8083063 Iustin Pop
295 264bb3c5 Michael Hanselmann
    # If there are TCP/IP ports configured, we use them first.
296 b2fddf63 Iustin Pop
    if self._config_data.cluster.tcpudp_port_pool:
297 b2fddf63 Iustin Pop
      port = self._config_data.cluster.tcpudp_port_pool.pop()
298 264bb3c5 Michael Hanselmann
    else:
299 264bb3c5 Michael Hanselmann
      port = self._config_data.cluster.highest_used_port + 1
300 264bb3c5 Michael Hanselmann
      if port >= constants.LAST_DRBD_PORT:
301 3ecf6786 Iustin Pop
        raise errors.ConfigurationError("The highest used port is greater"
302 3ecf6786 Iustin Pop
                                        " than %s. Aborting." %
303 3ecf6786 Iustin Pop
                                        constants.LAST_DRBD_PORT)
304 264bb3c5 Michael Hanselmann
      self._config_data.cluster.highest_used_port = port
305 a8083063 Iustin Pop
306 a8083063 Iustin Pop
    self._WriteConfig()
307 a8083063 Iustin Pop
    return port
308 a8083063 Iustin Pop
309 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
310 a8083063 Iustin Pop
  def GetHostKey(self):
311 a8083063 Iustin Pop
    """Return the rsa hostkey from the config.
312 a8083063 Iustin Pop

313 a8083063 Iustin Pop
    Args: None
314 a8083063 Iustin Pop

315 a8083063 Iustin Pop
    Returns: rsa hostkey
316 a8083063 Iustin Pop
    """
317 a8083063 Iustin Pop
    self._OpenConfig()
318 a8083063 Iustin Pop
    return self._config_data.cluster.rsahostkeypub
319 a8083063 Iustin Pop
320 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
321 a8083063 Iustin Pop
  def AddInstance(self, instance):
322 a8083063 Iustin Pop
    """Add an instance to the config.
323 a8083063 Iustin Pop

324 a8083063 Iustin Pop
    This should be used after creating a new instance.
325 a8083063 Iustin Pop

326 a8083063 Iustin Pop
    Args:
327 a8083063 Iustin Pop
      instance: the instance object
328 a8083063 Iustin Pop
    """
329 a8083063 Iustin Pop
    if not isinstance(instance, objects.Instance):
330 a8083063 Iustin Pop
      raise errors.ProgrammerError("Invalid type passed to AddInstance")
331 a8083063 Iustin Pop
332 e00fb268 Iustin Pop
    if instance.disk_template != constants.DT_DISKLESS:
333 e00fb268 Iustin Pop
      all_lvs = instance.MapLVsByNode()
334 e00fb268 Iustin Pop
      logger.Info("Instance '%s' DISK_LAYOUT: %s" % (instance.name, all_lvs))
335 923b1523 Iustin Pop
336 a8083063 Iustin Pop
    self._OpenConfig()
337 a8083063 Iustin Pop
    self._config_data.instances[instance.name] = instance
338 a8083063 Iustin Pop
    self._WriteConfig()
339 a8083063 Iustin Pop
340 6a408fb2 Iustin Pop
  def _SetInstanceStatus(self, instance_name, status):
341 6a408fb2 Iustin Pop
    """Set the instance's status to a given value.
342 a8083063 Iustin Pop

343 a8083063 Iustin Pop
    """
344 6a408fb2 Iustin Pop
    if status not in ("up", "down"):
345 6a408fb2 Iustin Pop
      raise errors.ProgrammerError("Invalid status '%s' passed to"
346 6a408fb2 Iustin Pop
                                   " ConfigWriter._SetInstanceStatus()" %
347 6a408fb2 Iustin Pop
                                   status)
348 a8083063 Iustin Pop
    self._OpenConfig()
349 a8083063 Iustin Pop
350 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
351 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" %
352 3ecf6786 Iustin Pop
                                      instance_name)
353 a8083063 Iustin Pop
    instance = self._config_data.instances[instance_name]
354 455a3445 Iustin Pop
    if instance.status != status:
355 455a3445 Iustin Pop
      instance.status = status
356 455a3445 Iustin Pop
      self._WriteConfig()
357 a8083063 Iustin Pop
358 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
359 6a408fb2 Iustin Pop
  def MarkInstanceUp(self, instance_name):
360 6a408fb2 Iustin Pop
    """Mark the instance status to up in the config.
361 6a408fb2 Iustin Pop

362 6a408fb2 Iustin Pop
    """
363 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "up")
364 6a408fb2 Iustin Pop
365 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
366 a8083063 Iustin Pop
  def RemoveInstance(self, instance_name):
367 a8083063 Iustin Pop
    """Remove the instance from the configuration.
368 a8083063 Iustin Pop

369 a8083063 Iustin Pop
    """
370 a8083063 Iustin Pop
    self._OpenConfig()
371 a8083063 Iustin Pop
372 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
373 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % instance_name)
374 a8083063 Iustin Pop
    del self._config_data.instances[instance_name]
375 a8083063 Iustin Pop
    self._WriteConfig()
376 a8083063 Iustin Pop
377 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
378 fc95f88f Iustin Pop
  def RenameInstance(self, old_name, new_name):
379 fc95f88f Iustin Pop
    """Rename an instance.
380 fc95f88f Iustin Pop

381 fc95f88f Iustin Pop
    This needs to be done in ConfigWriter and not by RemoveInstance
382 fc95f88f Iustin Pop
    combined with AddInstance as only we can guarantee an atomic
383 fc95f88f Iustin Pop
    rename.
384 fc95f88f Iustin Pop

385 fc95f88f Iustin Pop
    """
386 fc95f88f Iustin Pop
    self._OpenConfig()
387 fc95f88f Iustin Pop
    if old_name not in self._config_data.instances:
388 fc95f88f Iustin Pop
      raise errors.ConfigurationError("Unknown instance '%s'" % old_name)
389 fc95f88f Iustin Pop
    inst = self._config_data.instances[old_name]
390 fc95f88f Iustin Pop
    del self._config_data.instances[old_name]
391 fc95f88f Iustin Pop
    inst.name = new_name
392 b23c4333 Manuel Franceschini
393 b23c4333 Manuel Franceschini
    for disk in inst.disks:
394 b23c4333 Manuel Franceschini
      if disk.dev_type == constants.LD_FILE:
395 b23c4333 Manuel Franceschini
        # rename the file paths in logical and physical id
396 b23c4333 Manuel Franceschini
        file_storage_dir = os.path.dirname(os.path.dirname(disk.logical_id[1]))
397 b23c4333 Manuel Franceschini
        disk.physical_id = disk.logical_id = (disk.logical_id[0],
398 b23c4333 Manuel Franceschini
                                              os.path.join(file_storage_dir,
399 b23c4333 Manuel Franceschini
                                                           inst.name,
400 b23c4333 Manuel Franceschini
                                                           disk.iv_name))
401 b23c4333 Manuel Franceschini
402 fc95f88f Iustin Pop
    self._config_data.instances[inst.name] = inst
403 fc95f88f Iustin Pop
    self._WriteConfig()
404 fc95f88f Iustin Pop
405 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
406 a8083063 Iustin Pop
  def MarkInstanceDown(self, instance_name):
407 a8083063 Iustin Pop
    """Mark the status of an instance to down in the configuration.
408 a8083063 Iustin Pop

409 a8083063 Iustin Pop
    """
410 6a408fb2 Iustin Pop
    self._SetInstanceStatus(instance_name, "down")
411 a8083063 Iustin Pop
412 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
413 a8083063 Iustin Pop
  def GetInstanceList(self):
414 a8083063 Iustin Pop
    """Get the list of instances.
415 a8083063 Iustin Pop

416 a8083063 Iustin Pop
    Returns:
417 a8083063 Iustin Pop
      array of instances, ex. ['instance2.example.com','instance1.example.com']
418 a8083063 Iustin Pop
      these contains all the instances, also the ones in Admin_down state
419 a8083063 Iustin Pop

420 a8083063 Iustin Pop
    """
421 a8083063 Iustin Pop
    self._OpenConfig()
422 a8083063 Iustin Pop
423 a8083063 Iustin Pop
    return self._config_data.instances.keys()
424 a8083063 Iustin Pop
425 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
426 a8083063 Iustin Pop
  def ExpandInstanceName(self, short_name):
427 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
428 a8083063 Iustin Pop

429 a8083063 Iustin Pop
    """
430 a8083063 Iustin Pop
    self._OpenConfig()
431 a8083063 Iustin Pop
432 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
433 a8083063 Iustin Pop
                                    self._config_data.instances.keys())
434 a8083063 Iustin Pop
435 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
436 a8083063 Iustin Pop
  def GetInstanceInfo(self, instance_name):
437 a8083063 Iustin Pop
    """Returns informations about an instance.
438 a8083063 Iustin Pop

439 a8083063 Iustin Pop
    It takes the information from the configuration file. Other informations of
440 a8083063 Iustin Pop
    an instance are taken from the live systems.
441 a8083063 Iustin Pop

442 a8083063 Iustin Pop
    Args:
443 a8083063 Iustin Pop
      instance: name of the instance, ex instance1.example.com
444 a8083063 Iustin Pop

445 a8083063 Iustin Pop
    Returns:
446 a8083063 Iustin Pop
      the instance object
447 a8083063 Iustin Pop

448 a8083063 Iustin Pop
    """
449 a8083063 Iustin Pop
    self._OpenConfig()
450 a8083063 Iustin Pop
451 a8083063 Iustin Pop
    if instance_name not in self._config_data.instances:
452 a8083063 Iustin Pop
      return None
453 a8083063 Iustin Pop
454 a8083063 Iustin Pop
    return self._config_data.instances[instance_name]
455 a8083063 Iustin Pop
456 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
457 a8083063 Iustin Pop
  def AddNode(self, node):
458 a8083063 Iustin Pop
    """Add a node to the configuration.
459 a8083063 Iustin Pop

460 a8083063 Iustin Pop
    Args:
461 a8083063 Iustin Pop
      node: an object.Node instance
462 a8083063 Iustin Pop

463 a8083063 Iustin Pop
    """
464 a8083063 Iustin Pop
    self._OpenConfig()
465 a8083063 Iustin Pop
    self._config_data.nodes[node.name] = node
466 a8083063 Iustin Pop
    self._WriteConfig()
467 a8083063 Iustin Pop
468 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
469 a8083063 Iustin Pop
  def RemoveNode(self, node_name):
470 a8083063 Iustin Pop
    """Remove a node from the configuration.
471 a8083063 Iustin Pop

472 a8083063 Iustin Pop
    """
473 a8083063 Iustin Pop
    self._OpenConfig()
474 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
475 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Unknown node '%s'" % node_name)
476 a8083063 Iustin Pop
477 a8083063 Iustin Pop
    del self._config_data.nodes[node_name]
478 a8083063 Iustin Pop
    self._WriteConfig()
479 a8083063 Iustin Pop
480 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
481 a8083063 Iustin Pop
  def ExpandNodeName(self, short_name):
482 a8083063 Iustin Pop
    """Attempt to expand an incomplete instance name.
483 a8083063 Iustin Pop

484 a8083063 Iustin Pop
    """
485 a8083063 Iustin Pop
    self._OpenConfig()
486 a8083063 Iustin Pop
487 a8083063 Iustin Pop
    return utils.MatchNameComponent(short_name,
488 a8083063 Iustin Pop
                                    self._config_data.nodes.keys())
489 a8083063 Iustin Pop
490 f78ede4e Guido Trotter
  def _UnlockedGetNodeInfo(self, node_name):
491 a8083063 Iustin Pop
    """Get the configuration of a node, as stored in the config.
492 a8083063 Iustin Pop

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

495 a8083063 Iustin Pop
    Args: node: nodename (tuple) of the node
496 a8083063 Iustin Pop

497 a8083063 Iustin Pop
    Returns: the node object
498 a8083063 Iustin Pop

499 a8083063 Iustin Pop
    """
500 a8083063 Iustin Pop
    self._OpenConfig()
501 a8083063 Iustin Pop
502 a8083063 Iustin Pop
    if node_name not in self._config_data.nodes:
503 a8083063 Iustin Pop
      return None
504 a8083063 Iustin Pop
505 a8083063 Iustin Pop
    return self._config_data.nodes[node_name]
506 a8083063 Iustin Pop
507 f78ede4e Guido Trotter
508 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
509 f78ede4e Guido Trotter
  def GetNodeInfo(self, node_name):
510 f78ede4e Guido Trotter
    """Get the configuration of a node, as stored in the config.
511 f78ede4e Guido Trotter

512 f78ede4e Guido Trotter
    Args: node: nodename (tuple) of the node
513 f78ede4e Guido Trotter

514 f78ede4e Guido Trotter
    Returns: the node object
515 f78ede4e Guido Trotter

516 f78ede4e Guido Trotter
    """
517 f78ede4e Guido Trotter
    return self._UnlockedGetNodeInfo(node_name)
518 f78ede4e Guido Trotter
519 f78ede4e Guido Trotter
  def _UnlockedGetNodeList(self):
520 a8083063 Iustin Pop
    """Return the list of nodes which are in the configuration.
521 a8083063 Iustin Pop

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

524 a8083063 Iustin Pop
    """
525 a8083063 Iustin Pop
    self._OpenConfig()
526 a8083063 Iustin Pop
    return self._config_data.nodes.keys()
527 a8083063 Iustin Pop
528 f78ede4e Guido Trotter
529 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
530 f78ede4e Guido Trotter
  def GetNodeList(self):
531 f78ede4e Guido Trotter
    """Return the list of nodes which are in the configuration.
532 f78ede4e Guido Trotter

533 f78ede4e Guido Trotter
    """
534 f78ede4e Guido Trotter
    return self._UnlockedGetNodeList()
535 f78ede4e Guido Trotter
536 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
537 a8083063 Iustin Pop
  def DumpConfig(self):
538 a8083063 Iustin Pop
    """Return the entire configuration of the cluster.
539 a8083063 Iustin Pop
    """
540 a8083063 Iustin Pop
    self._OpenConfig()
541 a8083063 Iustin Pop
    return self._config_data
542 a8083063 Iustin Pop
543 a8083063 Iustin Pop
  def _BumpSerialNo(self):
544 a8083063 Iustin Pop
    """Bump up the serial number of the config.
545 a8083063 Iustin Pop

546 a8083063 Iustin Pop
    """
547 a8083063 Iustin Pop
    self._config_data.cluster.serial_no += 1
548 a8083063 Iustin Pop
549 a8083063 Iustin Pop
  def _OpenConfig(self):
550 a8083063 Iustin Pop
    """Read the config data from disk.
551 a8083063 Iustin Pop

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

556 a8083063 Iustin Pop
    """
557 a8083063 Iustin Pop
    try:
558 a8083063 Iustin Pop
      st = os.stat(self._cfg_file)
559 a8083063 Iustin Pop
    except OSError, err:
560 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Can't stat config file: %s" % err)
561 a8083063 Iustin Pop
    if (self._config_data is not None and
562 a8083063 Iustin Pop
        self._config_time is not None and
563 264bb3c5 Michael Hanselmann
        self._config_time == st.st_mtime and
564 264bb3c5 Michael Hanselmann
        self._config_size == st.st_size and
565 264bb3c5 Michael Hanselmann
        self._config_inode == st.st_ino):
566 a8083063 Iustin Pop
      # data is current, so skip loading of config file
567 a8083063 Iustin Pop
      return
568 243cdbcc Michael Hanselmann
569 243cdbcc Michael Hanselmann
    # Make sure the configuration has the right version
570 243cdbcc Michael Hanselmann
    ValidateConfig()
571 243cdbcc Michael Hanselmann
572 a8083063 Iustin Pop
    f = open(self._cfg_file, 'r')
573 a8083063 Iustin Pop
    try:
574 a8083063 Iustin Pop
      try:
575 8d14b30d Iustin Pop
        data = objects.ConfigData.FromDict(serializer.Load(f.read()))
576 a8083063 Iustin Pop
      except Exception, err:
577 3ecf6786 Iustin Pop
        raise errors.ConfigurationError(err)
578 a8083063 Iustin Pop
    finally:
579 a8083063 Iustin Pop
      f.close()
580 a8083063 Iustin Pop
    if (not hasattr(data, 'cluster') or
581 243cdbcc Michael Hanselmann
        not hasattr(data.cluster, 'rsahostkeypub')):
582 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Incomplete configuration"
583 243cdbcc Michael Hanselmann
                                      " (missing cluster.rsahostkeypub)")
584 a8083063 Iustin Pop
    self._config_data = data
585 a8083063 Iustin Pop
    self._config_time = st.st_mtime
586 264bb3c5 Michael Hanselmann
    self._config_size = st.st_size
587 264bb3c5 Michael Hanselmann
    self._config_inode = st.st_ino
588 a8083063 Iustin Pop
589 a8083063 Iustin Pop
  def _DistributeConfig(self):
590 a8083063 Iustin Pop
    """Distribute the configuration to the other nodes.
591 a8083063 Iustin Pop

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

595 a8083063 Iustin Pop
    """
596 a8083063 Iustin Pop
    if self._offline:
597 a8083063 Iustin Pop
      return True
598 a8083063 Iustin Pop
    bad = False
599 f78ede4e Guido Trotter
    nodelist = self._UnlockedGetNodeList()
600 89e1fc26 Iustin Pop
    myhostname = self._my_hostname
601 a8083063 Iustin Pop
602 9ff994da Guido Trotter
    try:
603 9ff994da Guido Trotter
      nodelist.remove(myhostname)
604 9ff994da Guido Trotter
    except ValueError:
605 9ff994da Guido Trotter
      pass
606 a8083063 Iustin Pop
607 41362e70 Guido Trotter
    result = rpc.call_upload_file(nodelist, self._cfg_file)
608 41362e70 Guido Trotter
    for node in nodelist:
609 a8083063 Iustin Pop
      if not result[node]:
610 a8083063 Iustin Pop
        logger.Error("copy of file %s to node %s failed" %
611 a8083063 Iustin Pop
                     (self._cfg_file, node))
612 a8083063 Iustin Pop
        bad = True
613 a8083063 Iustin Pop
    return not bad
614 a8083063 Iustin Pop
615 a8083063 Iustin Pop
  def _WriteConfig(self, destination=None):
616 a8083063 Iustin Pop
    """Write the configuration data to persistent storage.
617 a8083063 Iustin Pop

618 a8083063 Iustin Pop
    """
619 a8083063 Iustin Pop
    if destination is None:
620 a8083063 Iustin Pop
      destination = self._cfg_file
621 a8083063 Iustin Pop
    self._BumpSerialNo()
622 8d14b30d Iustin Pop
    txt = serializer.Dump(self._config_data.ToDict())
623 a8083063 Iustin Pop
    dir_name, file_name = os.path.split(destination)
624 a8083063 Iustin Pop
    fd, name = tempfile.mkstemp('.newconfig', file_name, dir_name)
625 a8083063 Iustin Pop
    f = os.fdopen(fd, 'w')
626 a8083063 Iustin Pop
    try:
627 8d14b30d Iustin Pop
      f.write(txt)
628 a8083063 Iustin Pop
      os.fsync(f.fileno())
629 a8083063 Iustin Pop
    finally:
630 a8083063 Iustin Pop
      f.close()
631 a8083063 Iustin Pop
    # we don't need to do os.close(fd) as f.close() did it
632 a8083063 Iustin Pop
    os.rename(name, destination)
633 14e15659 Iustin Pop
    self.write_count += 1
634 fee9556c Iustin Pop
    # re-set our cache as not to re-read the config file
635 fee9556c Iustin Pop
    try:
636 fee9556c Iustin Pop
      st = os.stat(destination)
637 fee9556c Iustin Pop
    except OSError, err:
638 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Can't stat config file: %s" % err)
639 fee9556c Iustin Pop
    self._config_time = st.st_mtime
640 fee9556c Iustin Pop
    self._config_size = st.st_size
641 fee9556c Iustin Pop
    self._config_inode = st.st_ino
642 fee9556c Iustin Pop
    # and redistribute the config file
643 a8083063 Iustin Pop
    self._DistributeConfig()
644 a8083063 Iustin Pop
645 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
646 a8083063 Iustin Pop
  def InitConfig(self, node, primary_ip, secondary_ip,
647 5fcdc80d Iustin Pop
                 hostkeypub, mac_prefix, vg_name, def_bridge):
648 a8083063 Iustin Pop
    """Create the initial cluster configuration.
649 a8083063 Iustin Pop

650 a8083063 Iustin Pop
    It will contain the current node, which will also be the master
651 a8083063 Iustin Pop
    node, and no instances or operating systmes.
652 a8083063 Iustin Pop

653 a8083063 Iustin Pop
    Args:
654 a8083063 Iustin Pop
      node: the nodename of the initial node
655 a8083063 Iustin Pop
      primary_ip: the IP address of the current host
656 a8083063 Iustin Pop
      secondary_ip: the secondary IP of the current host or None
657 a8083063 Iustin Pop
      hostkeypub: the public hostkey of this host
658 a8083063 Iustin Pop

659 264bb3c5 Michael Hanselmann
    """
660 a8083063 Iustin Pop
    hu_port = constants.FIRST_DRBD_PORT - 1
661 243cdbcc Michael Hanselmann
    globalconfig = objects.Cluster(serial_no=1,
662 a8083063 Iustin Pop
                                   rsahostkeypub=hostkeypub,
663 a8083063 Iustin Pop
                                   highest_used_port=hu_port,
664 a8083063 Iustin Pop
                                   mac_prefix=mac_prefix,
665 a8083063 Iustin Pop
                                   volume_group_name=vg_name,
666 b2fddf63 Iustin Pop
                                   default_bridge=def_bridge,
667 b2fddf63 Iustin Pop
                                   tcpudp_port_pool=set())
668 a8083063 Iustin Pop
    if secondary_ip is None:
669 a8083063 Iustin Pop
      secondary_ip = primary_ip
670 a8083063 Iustin Pop
    nodeconfig = objects.Node(name=node, primary_ip=primary_ip,
671 a8083063 Iustin Pop
                              secondary_ip=secondary_ip)
672 a8083063 Iustin Pop
673 a8083063 Iustin Pop
    self._config_data = objects.ConfigData(nodes={node: nodeconfig},
674 a8083063 Iustin Pop
                                           instances={},
675 b2fddf63 Iustin Pop
                                           cluster=globalconfig)
676 a8083063 Iustin Pop
    self._WriteConfig()
677 a8083063 Iustin Pop
678 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
679 a8083063 Iustin Pop
  def GetVGName(self):
680 a8083063 Iustin Pop
    """Return the volume group name.
681 a8083063 Iustin Pop

682 a8083063 Iustin Pop
    """
683 a8083063 Iustin Pop
    self._OpenConfig()
684 a8083063 Iustin Pop
    return self._config_data.cluster.volume_group_name
685 a8083063 Iustin Pop
686 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
687 89ff8e15 Manuel Franceschini
  def SetVGName(self, vg_name):
688 89ff8e15 Manuel Franceschini
    """Set the volume group name.
689 89ff8e15 Manuel Franceschini

690 89ff8e15 Manuel Franceschini
    """
691 89ff8e15 Manuel Franceschini
    self._OpenConfig()
692 2d4011cd Manuel Franceschini
    self._config_data.cluster.volume_group_name = vg_name
693 89ff8e15 Manuel Franceschini
    self._WriteConfig()
694 89ff8e15 Manuel Franceschini
695 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
696 a8083063 Iustin Pop
  def GetDefBridge(self):
697 a8083063 Iustin Pop
    """Return the default bridge.
698 a8083063 Iustin Pop

699 a8083063 Iustin Pop
    """
700 a8083063 Iustin Pop
    self._OpenConfig()
701 a8083063 Iustin Pop
    return self._config_data.cluster.default_bridge
702 a8083063 Iustin Pop
703 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
704 a8083063 Iustin Pop
  def GetMACPrefix(self):
705 a8083063 Iustin Pop
    """Return the mac prefix.
706 a8083063 Iustin Pop

707 a8083063 Iustin Pop
    """
708 a8083063 Iustin Pop
    self._OpenConfig()
709 a8083063 Iustin Pop
    return self._config_data.cluster.mac_prefix
710 62779dd0 Iustin Pop
711 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock, shared=1)
712 62779dd0 Iustin Pop
  def GetClusterInfo(self):
713 62779dd0 Iustin Pop
    """Returns informations about the cluster
714 62779dd0 Iustin Pop

715 62779dd0 Iustin Pop
    Returns:
716 62779dd0 Iustin Pop
      the cluster object
717 62779dd0 Iustin Pop

718 62779dd0 Iustin Pop
    """
719 62779dd0 Iustin Pop
    self._OpenConfig()
720 62779dd0 Iustin Pop
721 62779dd0 Iustin Pop
    return self._config_data.cluster
722 e00fb268 Iustin Pop
723 f78ede4e Guido Trotter
  @locking.ssynchronized(_config_lock)
724 e00fb268 Iustin Pop
  def Update(self, target):
725 e00fb268 Iustin Pop
    """Notify function to be called after updates.
726 e00fb268 Iustin Pop

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

733 e00fb268 Iustin Pop
    """
734 e00fb268 Iustin Pop
    if self._config_data is None:
735 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Configuration file not read,"
736 3ecf6786 Iustin Pop
                                   " cannot save.")
737 e00fb268 Iustin Pop
    if isinstance(target, objects.Cluster):
738 e00fb268 Iustin Pop
      test = target == self._config_data.cluster
739 e00fb268 Iustin Pop
    elif isinstance(target, objects.Node):
740 e00fb268 Iustin Pop
      test = target in self._config_data.nodes.values()
741 e00fb268 Iustin Pop
    elif isinstance(target, objects.Instance):
742 e00fb268 Iustin Pop
      test = target in self._config_data.instances.values()
743 e00fb268 Iustin Pop
    else:
744 3ecf6786 Iustin Pop
      raise errors.ProgrammerError("Invalid object type (%s) passed to"
745 3ecf6786 Iustin Pop
                                   " ConfigWriter.Update" % type(target))
746 e00fb268 Iustin Pop
    if not test:
747 3ecf6786 Iustin Pop
      raise errors.ConfigurationError("Configuration updated since object"
748 3ecf6786 Iustin Pop
                                      " has been read or unknown object")
749 e00fb268 Iustin Pop
    self._WriteConfig()