Merge branch 'stable-2.16' into stable-2.17
[ganeti-github.git] / test / py / testutils / config_mock.py
1 #
2 #
3
4 # Copyright (C) 2013 Google Inc.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are
9 # met:
10 #
11 # 1. Redistributions of source code must retain the above copyright notice,
12 # this list of conditions and the following disclaimer.
13 #
14 # 2. Redistributions in binary form must reproduce the above copyright
15 # notice, this list of conditions and the following disclaimer in the
16 # documentation and/or other materials provided with the distribution.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19 # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30
31 """Support for mocking the cluster configuration"""
32
33
34 import random
35 import time
36 import uuid as uuid_module
37
38 from ganeti import config
39 from ganeti import constants
40 from ganeti import errors
41 from ganeti.network import AddressPool
42 from ganeti import objects
43 from ganeti import utils
44
45 import mocks
46
47
48 RESERVE_ACTION = "reserve"
49 RELEASE_ACTION = "release"
50
51
52 def _StubGetEntResolver():
53 return mocks.FakeGetentResolver()
54
55
56 def _UpdateIvNames(base_idx, disks):
57 """Update the C{iv_name} attribute of disks.
58
59 @type disks: list of L{objects.Disk}
60
61 """
62 for (idx, disk) in enumerate(disks):
63 disk.iv_name = "disk/%s" % (base_idx + idx)
64
65
66 # pylint: disable=R0904
67 # pylint: disable=W0102
68 class ConfigMock(config.ConfigWriter):
69 """A mocked cluster configuration with added methods for easy customization.
70
71 """
72
73 def __init__(self, cfg_file="/dev/null"):
74 self._cur_group_id = 1
75 self._cur_node_id = 1
76 self._cur_inst_id = 1
77 self._cur_disk_id = 1
78 self._cur_os_id = 1
79 self._cur_nic_id = 1
80 self._cur_net_id = 1
81 self._default_os = None
82 self._mocked_config_store = None
83
84 self._temporary_macs = config.TemporaryReservationManager()
85 self._temporary_secrets = config.TemporaryReservationManager()
86 self._temporary_lvs = config.TemporaryReservationManager()
87 self._temporary_ips = config.TemporaryReservationManager()
88
89 super(ConfigMock, self).__init__(cfg_file=cfg_file,
90 _getents=_StubGetEntResolver(),
91 offline=True)
92
93 with self.GetConfigManager():
94 self._CreateConfig()
95
96 def _GetUuid(self):
97 return str(uuid_module.uuid4())
98
99 def _GetObjUuid(self, obj):
100 if obj is None:
101 return None
102 elif isinstance(obj, objects.ConfigObject):
103 return obj.uuid
104 else:
105 return obj
106
107 def AddNewNodeGroup(self,
108 uuid=None,
109 name=None,
110 ndparams=None,
111 diskparams=None,
112 ipolicy=None,
113 hv_state_static={},
114 disk_state_static=None,
115 alloc_policy=None,
116 networks=None):
117 """Add a new L{objects.NodeGroup} to the cluster configuration
118
119 See L{objects.NodeGroup} for parameter documentation.
120
121 @rtype: L{objects.NodeGroup}
122 @return: the newly added node group
123
124 """
125 group_id = self._cur_group_id
126 self._cur_group_id += 1
127
128 if uuid is None:
129 uuid = self._GetUuid()
130 if name is None:
131 name = "mock_group_%d" % group_id
132 if networks is None:
133 networks = {}
134
135 group = objects.NodeGroup(uuid=uuid,
136 name=name,
137 ndparams=ndparams,
138 diskparams=diskparams,
139 ipolicy=ipolicy,
140 hv_state_static=hv_state_static,
141 disk_state_static=disk_state_static,
142 alloc_policy=alloc_policy,
143 networks=networks,
144 members=[])
145
146 self._UnlockedAddNodeGroup(group, None, True)
147 return group
148
149 # pylint: disable=R0913
150 def AddNewNode(self,
151 uuid=None,
152 name=None,
153 primary_ip=None,
154 secondary_ip=None,
155 master_candidate=True,
156 offline=False,
157 drained=False,
158 group=None,
159 master_capable=True,
160 vm_capable=True,
161 ndparams=None,
162 powered=True,
163 hv_state=None,
164 hv_state_static={},
165 disk_state=None,
166 disk_state_static=None):
167 """Add a new L{objects.Node} to the cluster configuration
168
169 See L{objects.Node} for parameter documentation.
170
171 @rtype: L{objects.Node}
172 @return: the newly added node
173
174 """
175 node_id = self._cur_node_id
176 self._cur_node_id += 1
177
178 if uuid is None:
179 uuid = self._GetUuid()
180 if name is None:
181 name = "mock_node_%d.example.com" % node_id
182 if primary_ip is None:
183 primary_ip = "192.0.2.%d" % node_id
184 if secondary_ip is None:
185 secondary_ip = "203.0.113.%d" % node_id
186 if group is None:
187 group = self._default_group.uuid
188 group = self._GetObjUuid(group)
189 if ndparams is None:
190 ndparams = {}
191
192 node = objects.Node(uuid=uuid,
193 name=name,
194 primary_ip=primary_ip,
195 secondary_ip=secondary_ip,
196 master_candidate=master_candidate,
197 offline=offline,
198 drained=drained,
199 group=group,
200 master_capable=master_capable,
201 vm_capable=vm_capable,
202 ndparams=ndparams,
203 powered=powered,
204 hv_state=hv_state,
205 hv_state_static=hv_state_static,
206 disk_state=disk_state,
207 disk_state_static=disk_state_static)
208
209 self._UnlockedAddNode(node, None)
210 return node
211
212 def AddNewInstance(self,
213 uuid=None,
214 name=None,
215 primary_node=None,
216 os=None,
217 hypervisor=None,
218 hvparams=None,
219 beparams=None,
220 osparams=None,
221 osparams_private=None,
222 admin_state=None,
223 admin_state_source=None,
224 nics=None,
225 disks=None,
226 disk_template=None,
227 disks_active=None,
228 network_port=None,
229 secondary_node=None):
230 """Add a new L{objects.Instance} to the cluster configuration
231
232 See L{objects.Instance} for parameter documentation.
233
234 @rtype: L{objects.Instance}
235 @return: the newly added instance
236
237 """
238 inst_id = self._cur_inst_id
239 self._cur_inst_id += 1
240
241 if uuid is None:
242 uuid = self._GetUuid()
243 if name is None:
244 name = "mock_inst_%d.example.com" % inst_id
245 if primary_node is None:
246 primary_node = self._master_node.uuid
247 primary_node = self._GetObjUuid(primary_node)
248 if os is None:
249 os = self.GetDefaultOs().name + objects.OS.VARIANT_DELIM +\
250 self.GetDefaultOs().supported_variants[0]
251 if hypervisor is None:
252 hypervisor = self.GetClusterInfo().enabled_hypervisors[0]
253 if hvparams is None:
254 hvparams = {}
255 if beparams is None:
256 beparams = {}
257 if osparams is None:
258 osparams = {}
259 if osparams_private is None:
260 osparams_private = {}
261 if admin_state is None:
262 admin_state = constants.ADMINST_DOWN
263 if admin_state_source is None:
264 admin_state_source = constants.ADMIN_SOURCE
265 if nics is None:
266 nics = [self.CreateNic()]
267 if disk_template is None:
268 if disks is None:
269 # user chose nothing, so create a plain disk for him
270 disk_template = constants.DT_PLAIN
271 elif len(disks) == 0:
272 disk_template = constants.DT_DISKLESS
273 else:
274 disk_template = disks[0].dev_type
275 if disks is None:
276 if disk_template == constants.DT_DISKLESS:
277 disks = []
278 elif disk_template == constants.DT_EXT:
279 provider = "mock_provider"
280 disks = [self.CreateDisk(dev_type=disk_template,
281 primary_node=primary_node,
282 secondary_node=secondary_node,
283 params={constants.IDISK_PROVIDER: provider})]
284 else:
285 disks = [self.CreateDisk(dev_type=disk_template,
286 primary_node=primary_node,
287 secondary_node=secondary_node)]
288 if disks_active is None:
289 disks_active = admin_state == constants.ADMINST_UP
290
291 inst = objects.Instance(uuid=uuid,
292 name=name,
293 primary_node=primary_node,
294 os=os,
295 hypervisor=hypervisor,
296 hvparams=hvparams,
297 beparams=beparams,
298 osparams=osparams,
299 osparams_private=osparams_private,
300 admin_state=admin_state,
301 admin_state_source=admin_state_source,
302 nics=nics,
303 disks=[],
304 disks_active=disks_active,
305 network_port=network_port)
306 self.AddInstance(inst, None)
307 for disk in disks:
308 self.AddInstanceDisk(inst.uuid, disk)
309 return inst
310
311 def AddNewNetwork(self,
312 uuid=None,
313 name=None,
314 mac_prefix=None,
315 network=None,
316 network6=None,
317 gateway=None,
318 gateway6=None,
319 reservations=None,
320 ext_reservations=None):
321 """Add a new L{objects.Network} to the cluster configuration
322
323 See L{objects.Network} for parameter documentation.
324
325 @rtype: L{objects.Network}
326 @return: the newly added network
327
328 """
329 net_id = self._cur_net_id
330 self._cur_net_id += 1
331
332 if uuid is None:
333 uuid = self._GetUuid()
334 if name is None:
335 name = "mock_net_%d" % net_id
336 if network is None:
337 network = "198.51.100.0/24"
338 if gateway is None:
339 if network[-3:] == "/24":
340 gateway = network[:-4] + "1"
341 else:
342 gateway = "198.51.100.1"
343 if network[-3:] == "/24" and gateway == network[:-4] + "1":
344 if reservations is None:
345 reservations = "0" * 256
346 if ext_reservations:
347 ext_reservations = "11" + ("0" * 253) + "1"
348 elif reservations is None or ext_reservations is None:
349 raise AssertionError("You have to specify 'reservations' and"
350 " 'ext_reservations'!")
351
352 net = objects.Network(uuid=uuid,
353 name=name,
354 mac_prefix=mac_prefix,
355 network=network,
356 network6=network6,
357 gateway=gateway,
358 gateway6=gateway6,
359 reservations=reservations,
360 ext_reservations=ext_reservations)
361 self.AddNetwork(net, None)
362 return net
363
364 def AddOrphanDisk(self, **params):
365 disk = self.CreateDisk(**params)
366 self._UnlockedAddDisk(disk)
367
368 def ConnectNetworkToGroup(self, net, group, netparams=None):
369 """Connect the given network to the group.
370
371 @type net: string or L{objects.Network}
372 @param net: network object or UUID
373 @type group: string of L{objects.NodeGroup}
374 @param group: node group object of UUID
375 @type netparams: dict
376 @param netparams: network parameters for this connection
377
378 """
379 net_obj = None
380 if isinstance(net, objects.Network):
381 net_obj = net
382 else:
383 net_obj = self.GetNetwork(net)
384
385 group_obj = None
386 if isinstance(group, objects.NodeGroup):
387 group_obj = group
388 else:
389 group_obj = self.GetNodeGroup(group)
390
391 if net_obj is None or group_obj is None:
392 raise AssertionError("Failed to get network or node group")
393
394 if netparams is None:
395 netparams = {
396 constants.NIC_MODE: constants.NIC_MODE_BRIDGED,
397 constants.NIC_LINK: "br_mock"
398 }
399
400 group_obj.networks[net_obj.uuid] = netparams
401
402 def CreateDisk(self,
403 uuid=None,
404 name=None,
405 dev_type=constants.DT_PLAIN,
406 logical_id=None,
407 children=None,
408 nodes=None,
409 iv_name=None,
410 size=1024,
411 mode=constants.DISK_RDWR,
412 params=None,
413 spindles=None,
414 primary_node=None,
415 secondary_node=None,
416 create_nodes=False,
417 instance_disk_index=0):
418 """Create a new L{objecs.Disk} object
419
420 @rtype: L{objects.Disk}
421 @return: the newly create disk object
422
423 """
424 disk_id = self._cur_disk_id
425 self._cur_disk_id += 1
426
427 if uuid is None:
428 uuid = self._GetUuid()
429 if name is None:
430 name = "mock_disk_%d" % disk_id
431 if params is None:
432 params = {}
433
434 if dev_type == constants.DT_DRBD8:
435 pnode_uuid = self._GetObjUuid(primary_node)
436 snode_uuid = self._GetObjUuid(secondary_node)
437 if logical_id is not None:
438 pnode_uuid = logical_id[0]
439 snode_uuid = logical_id[1]
440
441 if pnode_uuid is None and create_nodes:
442 pnode_uuid = self.AddNewNode().uuid
443 if snode_uuid is None and create_nodes:
444 snode_uuid = self.AddNewNode().uuid
445
446 if pnode_uuid is None or snode_uuid is None:
447 raise AssertionError("Trying to create DRBD disk without nodes!")
448
449 if logical_id is None:
450 logical_id = (pnode_uuid, snode_uuid,
451 constants.FIRST_DRBD_PORT + disk_id,
452 disk_id, disk_id, "mock_secret")
453 if children is None:
454 data_child = self.CreateDisk(dev_type=constants.DT_PLAIN,
455 size=size)
456 meta_child = self.CreateDisk(dev_type=constants.DT_PLAIN,
457 size=constants.DRBD_META_SIZE)
458 children = [data_child, meta_child]
459
460 if nodes is None:
461 nodes = [pnode_uuid, snode_uuid]
462 elif dev_type == constants.DT_PLAIN:
463 if logical_id is None:
464 logical_id = ("mockvg", "mock_disk_%d" % disk_id)
465 if nodes is None and primary_node is not None:
466 nodes = [primary_node]
467 elif dev_type in constants.DTS_FILEBASED:
468 if logical_id is None:
469 logical_id = (constants.FD_LOOP, "/file/storage/disk%d" % disk_id)
470 if (nodes is None and primary_node is not None and
471 dev_type == constants.DT_FILE):
472 nodes = [primary_node]
473 elif dev_type == constants.DT_BLOCK:
474 if logical_id is None:
475 logical_id = (constants.BLOCKDEV_DRIVER_MANUAL,
476 "/dev/disk/disk%d" % disk_id)
477 elif dev_type == constants.DT_EXT:
478 if logical_id is None:
479 provider = params.get(constants.IDISK_PROVIDER, None)
480 if provider is None:
481 raise AssertionError("You must specify a 'provider' for 'ext' disks")
482 logical_id = (provider, "mock_disk_%d" % disk_id)
483 elif logical_id is None:
484 raise NotImplementedError
485 if children is None:
486 children = []
487 if nodes is None:
488 nodes = []
489 if iv_name is None:
490 iv_name = "disk/%d" % instance_disk_index
491
492 return objects.Disk(uuid=uuid,
493 name=name,
494 dev_type=dev_type,
495 logical_id=logical_id,
496 children=children,
497 nodes=nodes,
498 iv_name=iv_name,
499 size=size,
500 mode=mode,
501 params=params,
502 spindles=spindles)
503
504 def GetDefaultOs(self):
505 if self._default_os is None:
506 self._default_os = self.CreateOs(name="mocked_os")
507 return self._default_os
508
509 def CreateOs(self,
510 name=None,
511 path=None,
512 api_versions=None,
513 create_script=None,
514 export_script=None,
515 import_script=None,
516 rename_script=None,
517 verify_script=None,
518 supported_variants=None,
519 supported_parameters=None):
520 """Create a new L{objects.OS} object
521
522 @rtype: L{object.OS}
523 @return: the newly create OS objects
524
525 """
526 os_id = self._cur_os_id
527 self._cur_os_id += 1
528
529 if name is None:
530 name = "mock_os_%d" % os_id
531 if path is None:
532 path = "/mocked/path/%d" % os_id
533 if api_versions is None:
534 api_versions = [constants.OS_API_V20]
535 if create_script is None:
536 create_script = "mock_create.sh"
537 if export_script is None:
538 export_script = "mock_export.sh"
539 if import_script is None:
540 import_script = "mock_import.sh"
541 if rename_script is None:
542 rename_script = "mock_rename.sh"
543 if verify_script is None:
544 verify_script = "mock_verify.sh"
545 if supported_variants is None:
546 supported_variants = ["default"]
547 if supported_parameters is None:
548 supported_parameters = ["mock_param"]
549
550 return objects.OS(name=name,
551 path=path,
552 api_versions=api_versions,
553 create_script=create_script,
554 export_script=export_script,
555 import_script=import_script,
556 rename_script=rename_script,
557 verify_script=verify_script,
558 supported_variants=supported_variants,
559 supported_parameters=supported_parameters)
560
561 def CreateNic(self,
562 uuid=None,
563 name=None,
564 mac=None,
565 ip=None,
566 network=None,
567 nicparams=None,
568 netinfo=None):
569 """Create a new L{objecs.NIC} object
570
571 @rtype: L{objects.NIC}
572 @return: the newly create NIC object
573
574 """
575 nic_id = self._cur_nic_id
576 self._cur_nic_id += 1
577
578 if uuid is None:
579 uuid = self._GetUuid()
580 if name is None:
581 name = "mock_nic_%d" % nic_id
582 if mac is None:
583 mac = "aa:00:00:aa:%02x:%02x" % (nic_id / 0xff, nic_id % 0xff)
584 if isinstance(network, objects.Network):
585 if ip:
586 pool = AddressPool(network)
587 pool.Reserve(ip)
588 network = network.uuid
589 if nicparams is None:
590 nicparams = {}
591
592 return objects.NIC(uuid=uuid,
593 name=name,
594 mac=mac,
595 ip=ip,
596 network=network,
597 nicparams=nicparams,
598 netinfo=netinfo)
599
600 def SetEnabledDiskTemplates(self, enabled_disk_templates):
601 """Set the enabled disk templates in the cluster.
602
603 This also takes care of required IPolicy updates.
604
605 @type enabled_disk_templates: list of string
606 @param enabled_disk_templates: list of disk templates to enable
607
608 """
609 cluster = self.GetClusterInfo()
610 cluster.enabled_disk_templates = list(enabled_disk_templates)
611 cluster.ipolicy[constants.IPOLICY_DTS] = list(enabled_disk_templates)
612
613 def ComputeDRBDMap(self):
614 return dict((node_uuid, {}) for node_uuid in self._ConfigData().nodes)
615
616 def AllocateDRBDMinor(self, node_uuids, disk_uuid):
617 return [0] * len(node_uuids)
618
619 def ReleaseDRBDMinors(self, disk_uuid):
620 pass
621
622 def SetIPolicyField(self, category, field, value):
623 """Set a value of a desired ipolicy field.
624
625 @type category: one of L{constants.ISPECS_MAX}, L{constants.ISPECS_MIN},
626 L{constants.ISPECS_STD}
627 @param category: Whether to change the default value, or the upper or lower
628 bound.
629 @type field: string
630 @param field: The field to change.
631 @type value: any
632 @param value: The value to assign.
633
634 """
635 if category not in [constants.ISPECS_MAX, constants.ISPECS_MIN,
636 constants.ISPECS_STD]:
637 raise ValueError("Invalid ipolicy category %s" % category)
638
639 ipolicy_dict = self.GetClusterInfo().ipolicy[constants.ISPECS_MINMAX][0]
640 ipolicy_dict[category][field] = value
641
642 def _CreateConfig(self):
643 self._config_data = objects.ConfigData(
644 version=constants.CONFIG_VERSION,
645 cluster=None,
646 nodegroups={},
647 nodes={},
648 instances={},
649 networks={},
650 disks={})
651
652 master_node_uuid = self._GetUuid()
653
654 self._cluster = objects.Cluster(
655 serial_no=1,
656 rsahostkeypub="",
657 highest_used_port=(constants.FIRST_DRBD_PORT - 1),
658 tcpudp_port_pool=set(),
659 mac_prefix="aa:00:00",
660 volume_group_name="xenvg",
661 reserved_lvs=None,
662 drbd_usermode_helper="/bin/true",
663 master_node=master_node_uuid,
664 master_ip="192.0.2.254",
665 master_netdev=constants.DEFAULT_BRIDGE,
666 master_netmask=None,
667 use_external_mip_script=None,
668 cluster_name="cluster.example.com",
669 file_storage_dir="/tmp",
670 shared_file_storage_dir=None,
671 enabled_hypervisors=[constants.HT_XEN_HVM, constants.HT_XEN_PVM,
672 constants.HT_KVM],
673 hvparams=constants.HVC_DEFAULTS.copy(),
674 ipolicy=None,
675 os_hvp={self.GetDefaultOs().name: constants.HVC_DEFAULTS.copy()},
676 beparams=None,
677 osparams=None,
678 osparams_private_cluster=None,
679 nicparams={constants.PP_DEFAULT: constants.NICC_DEFAULTS},
680 ndparams=None,
681 diskparams=None,
682 candidate_pool_size=3,
683 modify_etc_hosts=False,
684 modify_ssh_setup=False,
685 maintain_node_health=False,
686 uid_pool=None,
687 default_iallocator="mock_iallocator",
688 hidden_os=None,
689 blacklisted_os=None,
690 primary_ip_family=None,
691 prealloc_wipe_disks=None,
692 enabled_disk_templates=list(constants.DISK_TEMPLATE_PREFERENCE),
693 )
694 self._cluster.ctime = self._cluster.mtime = time.time()
695 self._cluster.UpgradeConfig()
696 self._ConfigData().cluster = self._cluster
697
698 self._default_group = self.AddNewNodeGroup(name="default")
699 self._master_node = self.AddNewNode(uuid=master_node_uuid)
700
701 def _OpenConfig(self, _accept_foreign, force=False):
702 self._config_data = self._mocked_config_store
703
704 def _WriteConfig(self, destination=None, releaselock=False):
705 self._mocked_config_store = self._ConfigData()
706
707 def _GetRpc(self, _address_list):
708 raise AssertionError("This should not be used during tests!")
709
710 def _UnlockedGetNetworkMACPrefix(self, net_uuid):
711 """Return the network mac prefix if it exists or the cluster level default.
712
713 """
714 prefix = None
715 if net_uuid:
716 nobj = self._UnlockedGetNetwork(net_uuid)
717 if nobj.mac_prefix:
718 prefix = nobj.mac_prefix
719
720 return prefix
721
722 def _GenerateOneMAC(self, prefix=None):
723 """Return a function that randomly generates a MAC suffic
724 and appends it to the given prefix. If prefix is not given get
725 the cluster level default.
726
727 """
728 if not prefix:
729 prefix = self._ConfigData().cluster.mac_prefix
730
731 def GenMac():
732 byte1 = random.randrange(0, 256)
733 byte2 = random.randrange(0, 256)
734 byte3 = random.randrange(0, 256)
735 mac = "%s:%02x:%02x:%02x" % (prefix, byte1, byte2, byte3)
736 return mac
737
738 return GenMac
739
740 def GenerateMAC(self, net_uuid, ec_id):
741 """Generate a MAC for an instance.
742
743 This should check the current instances for duplicates.
744
745 """
746 existing = self._AllMACs()
747 prefix = self._UnlockedGetNetworkMACPrefix(net_uuid)
748 gen_mac = self._GenerateOneMAC(prefix)
749 return self._temporary_macs.Generate(existing, gen_mac, ec_id)
750
751 def ReserveMAC(self, mac, ec_id):
752 """Reserve a MAC for an instance.
753
754 This only checks instances managed by this cluster, it does not
755 check for potential collisions elsewhere.
756
757 """
758 all_macs = self._AllMACs()
759 if mac in all_macs:
760 raise errors.ReservationError("mac already in use")
761 else:
762 self._temporary_macs.Reserve(ec_id, mac)
763
764 def GenerateDRBDSecret(self, ec_id):
765 """Generate a DRBD secret.
766
767 This checks the current disks for duplicates.
768
769 """
770 return self._temporary_secrets.Generate(self._AllDRBDSecrets(),
771 utils.GenerateSecret,
772 ec_id)
773
774 def ReserveLV(self, lv_name, ec_id):
775 """Reserve an VG/LV pair for an instance.
776
777 @type lv_name: string
778 @param lv_name: the logical volume name to reserve
779
780 """
781 all_lvs = self._AllLVs()
782 if lv_name in all_lvs:
783 raise errors.ReservationError("LV already in use")
784 else:
785 self._temporary_lvs.Reserve(ec_id, lv_name)
786
787 def _UnlockedCommitTemporaryIps(self, ec_id):
788 """Commit all reserved IP address to their respective pools
789
790 """
791 for action, address, net_uuid in self._temporary_ips.GetECReserved(ec_id):
792 self._UnlockedCommitIp(action, net_uuid, address)
793
794 def _UnlockedCommitIp(self, action, net_uuid, address):
795 """Commit a reserved IP address to an IP pool.
796
797 The IP address is taken from the network's IP pool and marked as reserved.
798
799 """
800 nobj = self._UnlockedGetNetwork(net_uuid)
801 pool = AddressPool(nobj)
802 if action == RESERVE_ACTION:
803 pool.Reserve(address)
804 elif action == RELEASE_ACTION:
805 pool.Release(address)
806
807 def _UnlockedReleaseIp(self, net_uuid, address, ec_id):
808 """Give a specific IP address back to an IP pool.
809
810 The IP address is returned to the IP pool designated by pool_id and marked
811 as reserved.
812
813 """
814 self._temporary_ips.Reserve(ec_id,
815 (RELEASE_ACTION, address, net_uuid))
816
817 def ReleaseIp(self, net_uuid, address, ec_id):
818 """Give a specified IP address back to an IP pool.
819
820 This is just a wrapper around _UnlockedReleaseIp.
821
822 """
823 if net_uuid:
824 self._UnlockedReleaseIp(net_uuid, address, ec_id)
825
826 def GenerateIp(self, net_uuid, ec_id):
827 """Find a free IPv4 address for an instance.
828
829 """
830 nobj = self._UnlockedGetNetwork(net_uuid)
831 pool = AddressPool(nobj)
832
833 def gen_one():
834 try:
835 ip = pool.GenerateFree()
836 except errors.AddressPoolError:
837 raise errors.ReservationError("Cannot generate IP. Network is full")
838 return (RESERVE_ACTION, ip, net_uuid)
839
840 _, address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
841 return address
842
843 def _UnlockedReserveIp(self, net_uuid, address, ec_id, check=True):
844 """Reserve a given IPv4 address for use by an instance.
845
846 """
847 nobj = self._UnlockedGetNetwork(net_uuid)
848 pool = AddressPool(nobj)
849 try:
850 isreserved = pool.IsReserved(address)
851 isextreserved = pool.IsReserved(address, external=True)
852 except errors.AddressPoolError:
853 raise errors.ReservationError("IP address not in network")
854 if isreserved:
855 raise errors.ReservationError("IP address already in use")
856 if check and isextreserved:
857 raise errors.ReservationError("IP is externally reserved")
858 return self._temporary_ips.Reserve(ec_id,
859 (RESERVE_ACTION,
860 address, net_uuid))
861
862 def ReserveIp(self, net_uuid, address, ec_id, check=True):
863 """Reserve a given IPv4 address for use by an instance.
864
865 """
866 if net_uuid:
867 return self._UnlockedReserveIp(net_uuid, address, ec_id, check)
868
869 def AddInstance(self, instance, ec_id, replace=False):
870 """Add an instance to the config.
871
872 """
873 instance.serial_no = 1
874 instance.ctime = instance.mtime = time.time()
875 self._ConfigData().instances[instance.uuid] = instance
876 self._ConfigData().cluster.serial_no += 1 # pylint: disable=E1103
877 self.ReleaseDRBDMinors(instance.uuid)
878 self._UnlockedCommitTemporaryIps(ec_id)
879
880 def _UnlockedAddDisk(self, disk):
881 disk.UpgradeConfig()
882 self._ConfigData().disks[disk.uuid] = disk
883 self._ConfigData().cluster.serial_no += 1 # pylint: disable=E1103
884 self.ReleaseDRBDMinors(disk.uuid)
885
886 def _UnlockedAttachInstanceDisk(self, inst_uuid, disk_uuid, idx=None):
887 instance = self._UnlockedGetInstanceInfo(inst_uuid)
888 if idx is None:
889 idx = len(instance.disks)
890 instance.disks.insert(idx, disk_uuid)
891 instance_disks = self._UnlockedGetInstanceDisks(inst_uuid)
892 for (disk_idx, disk) in enumerate(instance_disks[idx:]):
893 disk.iv_name = "disk/%s" % (idx + disk_idx)
894 instance.serial_no += 1
895 instance.mtime = time.time()
896
897 def AddInstanceDisk(self, inst_uuid, disk, idx=None, replace=False):
898 self._UnlockedAddDisk(disk)
899 self._UnlockedAttachInstanceDisk(inst_uuid, disk.uuid, idx)
900
901 def AttachInstanceDisk(self, inst_uuid, disk_uuid, idx=None):
902 self._UnlockedAttachInstanceDisk(inst_uuid, disk_uuid, idx)
903
904 def GetDisk(self, disk_uuid):
905 """Retrieves a disk object if present.
906
907 """
908 return self._ConfigData().disks[disk_uuid]
909
910 def AllocatePort(self):
911 return 1
912
913 def Update(self, target, feedback_fn, ec_id=None):
914 def replace_in(target, tdict):
915 tdict[target.uuid] = target
916
917 update_serial = False
918 if isinstance(target, objects.Cluster):
919 self._ConfigData().cluster = target
920 elif isinstance(target, objects.Node):
921 replace_in(target, self._ConfigData().nodes)
922 update_serial = True
923 elif isinstance(target, objects.Instance):
924 replace_in(target, self._ConfigData().instances)
925 elif isinstance(target, objects.NodeGroup):
926 replace_in(target, self._ConfigData().nodegroups)
927 elif isinstance(target, objects.Network):
928 replace_in(target, self._ConfigData().networks)
929 elif isinstance(target, objects.Disk):
930 replace_in(target, self._ConfigData().disks)
931
932 target.serial_no += 1
933 target.mtime = now = time.time()
934
935 if update_serial:
936 self._ConfigData().cluster.serial_no += 1 # pylint: disable=E1103
937 self._ConfigData().cluster.mtime = now
938
939 def SetInstancePrimaryNode(self, inst_uuid, target_node_uuid):
940 self._UnlockedGetInstanceInfo(inst_uuid).primary_node = target_node_uuid
941
942 def _SetInstanceStatus(self, inst_uuid, status,
943 disks_active, admin_state_source):
944 if inst_uuid not in self._ConfigData().instances:
945 raise errors.ConfigurationError("Unknown instance '%s'" %
946 inst_uuid)
947 instance = self._ConfigData().instances[inst_uuid]
948
949 if status is None:
950 status = instance.admin_state
951 if disks_active is None:
952 disks_active = instance.disks_active
953 if admin_state_source is None:
954 admin_state_source = instance.admin_state_source
955
956 assert status in constants.ADMINST_ALL, \
957 "Invalid status '%s' passed to SetInstanceStatus" % (status,)
958
959 if instance.admin_state != status or \
960 instance.disks_active != disks_active or \
961 instance.admin_state_source != admin_state_source:
962 instance.admin_state = status
963 instance.disks_active = disks_active
964 instance.admin_state_source = admin_state_source
965 instance.serial_no += 1
966 instance.mtime = time.time()
967 return instance
968
969 def _UnlockedDetachInstanceDisk(self, inst_uuid, disk_uuid):
970 """Detach a disk from an instance.
971
972 @type inst_uuid: string
973 @param inst_uuid: The UUID of the instance object
974 @type disk_uuid: string
975 @param disk_uuid: The UUID of the disk object
976
977 """
978 instance = self._UnlockedGetInstanceInfo(inst_uuid)
979 if instance is None:
980 raise errors.ConfigurationError("Instance %s doesn't exist"
981 % inst_uuid)
982 if disk_uuid not in self._ConfigData().disks:
983 raise errors.ConfigurationError("Disk %s doesn't exist" % disk_uuid)
984
985 # Check if disk is attached to the instance
986 if disk_uuid not in instance.disks:
987 raise errors.ProgrammerError("Disk %s is not attached to an instance"
988 % disk_uuid)
989
990 idx = instance.disks.index(disk_uuid)
991 instance.disks.remove(disk_uuid)
992 instance_disks = self._UnlockedGetInstanceDisks(inst_uuid)
993 _UpdateIvNames(idx, instance_disks[idx:])
994 instance.serial_no += 1
995 instance.mtime = time.time()
996
997 def DetachInstanceDisk(self, inst_uuid, disk_uuid):
998 self._UnlockedDetachInstanceDisk(inst_uuid, disk_uuid)
999
1000 def RemoveInstanceDisk(self, inst_uuid, disk_uuid):
1001 self._UnlockedDetachInstanceDisk(inst_uuid, disk_uuid)
1002 self._UnlockedRemoveDisk(disk_uuid)
1003
1004 def RemoveInstance(self, inst_uuid):
1005 del self._ConfigData().instances[inst_uuid]
1006
1007 def AddTcpUdpPort(self, port):
1008 self._ConfigData().cluster.tcpudp_port_pool.add(port)