2c34a4dfc1cf81c504b6db8ee678afe6931521c4
[ganeti-github.git] / test / py / testutils_ssh.py
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2010, 2013, 2015 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 """Helper class to test ssh-related code."""
32
33 from ganeti import constants
34 from ganeti import pathutils
35 from ganeti import errors
36
37 from collections import namedtuple
38
39
40 class FakeSshFileManager(object):
41 """Class which 'fakes' the lowest layer of SSH key manipulation.
42
43 There are various operations which touch the nodes' SSH keys and their
44 respective key files (authorized_keys and ganeti_pub_keys). Those are
45 tedious to test as file operations have to be mocked on different levels
46 (direct access to the authorized_keys and ganeti_pub_keys) of the master
47 node, indirect access to those files of the non-master nodes (via the
48 ssh_update tool). In order to make unit tests of those operations more
49 readable and managable, we introduce this class, which mocks all
50 direct and indirect access to SSH key files on all nodes. This way,
51 the state of this FakeSshFileManager represents the state of a cluster's
52 nodes' SSH key files in a consise and easily accessible way.
53
54 """
55 def __init__(self):
56 # Dictionary mapping node name to node properties. The properties
57 # are a named tuple of (node_uuid, ssh_key, is_potential_master_candidate,
58 # is_master_candidate, is_master).
59 self._all_node_data = {}
60 # Dictionary emulating the authorized keys files of all nodes. The
61 # indices of the dictionary are the node names, the values are sets
62 # of keys (strings).
63 self._authorized_keys = {}
64 # Dictionary emulating the public keys file of all nodes. The indices
65 # of the dictionary are the node names where the public key file is
66 # 'located' (if it wasn't faked). The values of the dictionary are
67 # dictionaries itself. Each of those dictionaries is indexed by the
68 # node UUIDs mapping to a list of public keys.
69 self._public_keys = {} # dict of dicts
70 # Node name of the master node
71 self._master_node_name = None
72 # Dictionary mapping nodes by name to number of retries where 'RunCommand'
73 # succeeds. For example if set to '3', RunCommand will fail two times when
74 # called for this node before it succeeds in the 3rd retry.
75 self._max_retries = {}
76 # Dictionary mapping nodes by name to number of retries which
77 # 'RunCommand' has already carried out.
78 self._retries = {}
79
80 self._AssertTypePublicKeys()
81 self._AssertTypeAuthorizedKeys()
82
83 _NodeInfo = namedtuple(
84 "NodeInfo",
85 ["uuid",
86 "key",
87 "is_potential_master_candidate",
88 "is_master_candidate",
89 "is_master"])
90
91 def _SetMasterNodeName(self):
92 self._master_node_name = [name for name, node_info
93 in self._all_node_data.items()
94 if node_info.is_master][0]
95
96 def GetMasterNodeName(self):
97 return self._master_node_name
98
99 def _CreateNodeDict(self, num_nodes, num_pot_mcs, num_mcs):
100 """Creates a dictionary of all nodes and their properties."""
101
102 self._all_node_data = {}
103 for i in range(num_nodes):
104 name = "node_name_%i" % i
105 uuid = "node_uuid_%i" % i
106 key = "key%s" % i
107 self._public_keys[name] = {}
108 self._authorized_keys[name] = set()
109
110 pot_mc = i < num_pot_mcs
111 mc = i < num_mcs
112 master = i == num_mcs / 2
113
114 self._all_node_data[name] = self._NodeInfo(uuid, key, pot_mc, mc, master)
115
116 self._AssertTypePublicKeys()
117 self._AssertTypeAuthorizedKeys()
118
119 def _FillPublicKeyOfOneNode(self, receiving_node_name):
120 node_info = self._all_node_data[receiving_node_name]
121 # Nodes which are not potential master candidates receive no keys
122 if not node_info.is_potential_master_candidate:
123 return
124 for node_info in self._all_node_data.values():
125 if node_info.is_potential_master_candidate:
126 self._public_keys[receiving_node_name][node_info.uuid] = [node_info.key]
127
128 def _FillAuthorizedKeyOfOneNode(self, receiving_node_name):
129 for node_name, node_info in self._all_node_data.items():
130 if node_info.is_master_candidate \
131 or node_name == receiving_node_name:
132 self._authorized_keys[receiving_node_name].add(node_info.key)
133
134 def InitAllNodes(self, num_nodes, num_pot_mcs, num_mcs):
135 """Initializes the entire state of the cluster wrt SSH keys.
136
137 @type num_nodes: int
138 @param num_nodes: number of nodes in the cluster
139 @type num_pot_mcs: int
140 @param num_pot_mcs: number of potential master candidates in the cluster
141 @type num_mcs: in
142 @param num_mcs: number of master candidates in the cluster.
143
144 """
145 self._public_keys = {}
146 self._authorized_keys = {}
147 self._CreateNodeDict(num_nodes, num_pot_mcs, num_mcs)
148 for node in self._all_node_data.keys():
149 self._FillPublicKeyOfOneNode(node)
150 self._FillAuthorizedKeyOfOneNode(node)
151 self._SetMasterNodeName()
152 self._AssertTypePublicKeys()
153 self._AssertTypeAuthorizedKeys()
154
155 def SetMaxRetries(self, node_name, retries):
156 """Set the number of unsuccessful retries of 'RunCommand' per node.
157
158 @type node_name: string
159 @param node_name: name of the node
160 @type retries: integer
161 @param retries: number of unsuccessful retries
162
163 """
164 self._max_retries[node_name] = retries
165
166 def GetSshPortMap(self, port):
167 """Creates a SSH port map with all nodes mapped to the given port.
168
169 @type port: int
170 @param port: SSH port number for all nodes
171
172 """
173 port_map = {}
174 for node in self._all_node_data.keys():
175 port_map[node] = port
176 return port_map
177
178 def GetAllNodeNames(self):
179 return self._all_node_data.keys()
180
181 def GetAllPotentialMasterCandidateNodeNames(self):
182 return [name for name, node_info
183 in self._all_node_data.items()
184 if node_info.is_potential_master_candidate]
185
186 def GetAllMasterCandidateUuids(self):
187 return [node_info.uuid for node_info
188 in self._all_node_data.values() if node_info.is_master_candidate]
189
190 def GetAllPurePotentialMasterCandidates(self):
191 """Get the potential master candidates which are not master candidates."""
192 return [(name, node_info) for name, node_info
193 in self._all_node_data.items()
194 if node_info.is_potential_master_candidate and
195 not node_info.is_master_candidate]
196
197 def GetAllMasterCandidates(self):
198 return [(name, node_info) for name, node_info
199 in self._all_node_data.items() if node_info.is_master_candidate]
200
201 def GetAllNormalNodes(self):
202 return [(name, node_info) for name, node_info
203 in self._all_node_data.items() if not node_info.is_master_candidate
204 and not node_info.is_potential_master_candidate]
205
206 def GetAllNodesDiverse(self):
207 """This returns all nodes in a diverse order.
208
209 This will return all nodes, but makes sure that they are ordered so that
210 the list will contain in a round-robin fashion, a master candidate,
211 a potential master candidate, a normal node, then again a master
212 candidate, etc.
213
214 """
215 master_candidates = self.GetAllMasterCandidates()
216 potential_master_candidates = self.GetAllPurePotentialMasterCandidates()
217 normal_nodes = self.GetAllNormalNodes()
218
219 mixed_list = []
220
221 i = 0
222
223 assert (len(self._all_node_data) == len(master_candidates)
224 + len(potential_master_candidates) + len(normal_nodes))
225
226 while len(mixed_list) < len(self._all_node_data):
227 if i % 3 == 0:
228 if master_candidates:
229 mixed_list.append(master_candidates[0])
230 master_candidates = master_candidates[1:]
231 elif i % 3 == 1:
232 if potential_master_candidates:
233 mixed_list.append(potential_master_candidates[0])
234 potential_master_candidates = potential_master_candidates[1:]
235 else: # i % 3 == 2
236 if normal_nodes:
237 mixed_list.append(normal_nodes[0])
238 normal_nodes = normal_nodes[1:]
239 i += 1
240
241 return mixed_list
242
243 def GetPublicKeysOfNode(self, node):
244 return self._public_keys[node]
245
246 def GetAuthorizedKeysOfNode(self, node):
247 return self._authorized_keys[node]
248
249 def SetOrAddNode(self, name, uuid, key, pot_mc, mc, master):
250 """Adds a new node to the state of the file manager.
251
252 This is necessary when testing to add new nodes to the cluster. Otherwise
253 this new node's state would not be evaluated properly with the assertion
254 functions.
255
256 @type name: string
257 @param name: name of the new node
258 @type uuid: string
259 @param uuid: UUID of the new node
260 @type key: string
261 @param key: SSH key of the new node
262 @type pot_mc: boolean
263 @param pot_mc: whether the new node is a potential master candidate
264 @type mc: boolean
265 @param mc: whether the new node is a master candidate
266 @type master: boolean
267 @param master: whether the new node is the master
268
269 """
270 self._all_node_data[name] = self._NodeInfo(uuid, key, pot_mc, mc, master)
271 if name not in self._authorized_keys:
272 self._authorized_keys[name] = set()
273 if mc:
274 self._authorized_keys[name].add(key)
275 if name not in self._public_keys:
276 self._public_keys[name] = {}
277 self._AssertTypePublicKeys()
278 self._AssertTypeAuthorizedKeys()
279
280 def NodeHasPublicKey(self, file_node_name, key_node_uuid, key):
281 """Checks whether a node has another node's public key.
282
283 @type file_node_name: string
284 @param file_node_name: name of the node whose public key file is inspected
285 @type key_node_uuid: string
286 @param key_node_uuid: UUID of the node whose key is checked for
287 @rtype: boolean
288 @return: True if the key_node's UUID is found with the machting key 'key'
289
290 """
291 for (node_uuid, pub_keys) in self._public_keys[file_node_name].items():
292 if key in pub_keys and key_node_uuid == node_uuid:
293 return True
294 return False
295
296 def NodeHasAuthorizedKey(self, file_node_name, key):
297 """Checks whether a node has a particular key in its authorized_keys file.
298
299 @type file_node_name: string
300 @param file_node_name: name of the node whose authorized_key file is
301 inspected
302 @type key: string
303 @param key: key which is expected to be found in the node's authorized_key
304 file
305 @rtype: boolean
306 @return: True if the key is found in the node's authorized_key file
307
308 """
309 return key in self._authorized_keys[file_node_name]
310
311 def AssertNodeSetOnlyHasAuthorizedKey(self, node_set, query_node_key):
312 """Check if nodes in the given set only have a particular authorized key.
313
314 @type node_set: list of strings
315 @param node_set: list of nodes who are supposed to have the key
316 @type query_node_key: string
317 @param query_node_key: key which is looked for
318
319 """
320 assert isinstance(node_set, list)
321 for node_name in self._all_node_data.keys():
322 if node_name in node_set:
323 if not self.NodeHasAuthorizedKey(node_name, query_node_key):
324 raise Exception("Node '%s' does not have authorized key '%s'."
325 % (node_name, query_node_key))
326 else:
327 if self.NodeHasAuthorizedKey(node_name, query_node_key):
328 raise Exception("Node '%s' has authorized key '%s' although it"
329 " should not." % (node_name, query_node_key))
330
331 def AssertAllNodesHaveAuthorizedKey(self, key):
332 """Check if all nodes have a particular key in their auth. keys file.
333
334 @type key: string
335 @param key: key exptected to be present in all node's authorized_keys file
336 @raise Exception: if a node does not have the authorized key.
337
338 """
339 self.AssertNodeSetOnlyHasAuthorizedKey(self._all_node_data.keys(), key)
340
341 def AssertNoNodeHasAuthorizedKey(self, key):
342 """Check if none of the nodes has a particular key in their auth. keys file.
343
344 @type key: string
345 @param key: key exptected to be present in all node's authorized_keys file
346 @raise Exception: if a node *does* have the authorized key.
347
348 """
349 self.AssertNodeSetOnlyHasAuthorizedKey([], key)
350
351 def AssertNodeSetOnlyHasPublicKey(self, node_set, query_node_uuid,
352 query_node_key):
353 """Check if nodes in the given set only have a particular public key.
354
355 @type node_set: list of strings
356 @param node_set: list of nodes who are supposed to have the key
357 @type query_node_uuid: string
358 @param query_node_uuid: uuid of the node whose key is looked for
359 @type query_node_key: string
360 @param query_node_key: key which is looked for
361
362 """
363 for node_name in self._all_node_data.keys():
364 if node_name in node_set:
365 if not self.NodeHasPublicKey(node_name, query_node_uuid,
366 query_node_key):
367 raise Exception("Node '%s' does not have public key '%s' of node"
368 " '%s'." % (node_name, query_node_key,
369 query_node_uuid))
370 else:
371 if self.NodeHasPublicKey(node_name, query_node_uuid, query_node_key):
372 raise Exception("Node '%s' has public key '%s' of node"
373 " '%s' although it should not."
374 % (node_name, query_node_key, query_node_uuid))
375
376 def AssertNoNodeHasPublicKey(self, uuid, key):
377 """Check if none of the nodes have the given public key in their file.
378
379 @type uuid: string
380 @param uuid: UUID of the node whose key is looked for
381 @raise Exception: if a node *does* have the public key.
382
383 """
384 self.AssertNodeSetOnlyHasPublicKey([], uuid, key)
385
386 def AssertPotentialMasterCandidatesOnlyHavePublicKey(self, query_node_name):
387 """Checks if the node's key is on all potential master candidates only.
388
389 This ensures that the node's key is in all public key files of all
390 potential master candidates, and it also checks whether the key is
391 *not* in all other nodes's key files.
392
393 @param query_node_name: name of the node whose key is expected to be
394 in the public key file of all potential master
395 candidates
396 @type query_node_name: string
397 @raise Exception: when a potential master candidate does not have
398 the public key or a normal node *does* have a public key.
399
400 """
401 query_node_uuid, query_node_key, _, _, _ = \
402 self._all_node_data[query_node_name]
403 potential_master_candidates = self.GetAllPotentialMasterCandidateNodeNames()
404 self.AssertNodeSetOnlyHasPublicKey(
405 potential_master_candidates, query_node_uuid, query_node_key)
406
407 def _AssertTypePublicKeys(self):
408 """Asserts that the public key dictionary has the right types.
409
410 This is helpful as an invariant that shall not be violated during the
411 tests due to type errors.
412
413 """
414 assert isinstance(self._public_keys, dict)
415 for node_file, pub_keys in self._public_keys.items():
416 assert isinstance(node_file, str)
417 assert isinstance(pub_keys, dict)
418 for node_key, keys in pub_keys.items():
419 assert isinstance(node_key, str)
420 assert isinstance(keys, list)
421 for key in keys:
422 assert isinstance(key, str)
423
424 def _AssertTypeAuthorizedKeys(self):
425 """Asserts that the authorized keys dictionary has the right types.
426
427 This is useful to check as an invariant that is not supposed to be violated
428 during the tests.
429
430 """
431 assert isinstance(self._authorized_keys, dict)
432 for node_file, auth_keys in self._authorized_keys.items():
433 assert isinstance(node_file, str)
434 assert isinstance(auth_keys, set)
435 for key in auth_keys:
436 assert isinstance(key, str)
437
438 # Disabling a pylint warning about unused parameters. Those need
439 # to be here to properly mock the real methods.
440 # pylint: disable=W0613
441 def RunCommand(self, cluster_name, node, base_cmd, port, data,
442 debug=False, verbose=False, use_cluster_key=False,
443 ask_key=False, strict_host_check=False,
444 ensure_version=False):
445 """This emulates ssh.RunSshCmdWithStdin calling ssh_update.
446
447 While in real SSH operations, ssh.RunSshCmdWithStdin is called
448 with the command ssh_update to manipulate a remote node's SSH
449 key files (authorized_keys and ganeti_pub_key) file, this method
450 emulates the operation by manipulating only its internal dictionaries
451 of SSH keys. No actual key files of any node is touched.
452
453 """
454 if node in self._max_retries:
455 if node not in self._retries:
456 self._retries[node] = 0
457 self._retries[node] += 1
458 if self._retries[node] < self._max_retries[node]:
459 raise errors.OpExecError("(Fake) SSH connection to node '%s' failed."
460 % node)
461
462 assert base_cmd == pathutils.SSH_UPDATE
463
464 if constants.SSHS_SSH_AUTHORIZED_KEYS in data:
465 instructions_auth = data[constants.SSHS_SSH_AUTHORIZED_KEYS]
466 self._HandleAuthorizedKeys(instructions_auth, node)
467 if constants.SSHS_SSH_PUBLIC_KEYS in data:
468 instructions_pub = data[constants.SSHS_SSH_PUBLIC_KEYS]
469 self._HandlePublicKeys(instructions_pub, node)
470 # pylint: enable=W0613
471
472 def _EnsureAuthKeyFile(self, file_node_name):
473 if file_node_name not in self._authorized_keys:
474 self._authorized_keys[file_node_name] = set()
475 self._AssertTypePublicKeys()
476 self._AssertTypeAuthorizedKeys()
477
478 def _AddAuthorizedKeys(self, file_node_name, ssh_keys):
479 """Mocks adding the given keys to the authorized_keys file."""
480 assert isinstance(ssh_keys, list)
481 self._EnsureAuthKeyFile(file_node_name)
482 for key in ssh_keys:
483 self._authorized_keys[file_node_name].add(key)
484 self._AssertTypePublicKeys()
485 self._AssertTypeAuthorizedKeys()
486
487 def _RemoveAuthorizedKeys(self, file_node_name, keys):
488 """Mocks removing the keys from authorized_keys on the given node.
489
490 @param keys: list of ssh keys
491 @type keys: list of strings
492
493 """
494 self._EnsureAuthKeyFile(file_node_name)
495 self._authorized_keys[file_node_name] = \
496 set([k for k in self._authorized_keys[file_node_name] if k not in keys])
497 self._AssertTypeAuthorizedKeys()
498
499 def _HandleAuthorizedKeys(self, instructions, node):
500 (action, authorized_keys) = instructions
501 ssh_key_sets = authorized_keys.values()
502 if action == constants.SSHS_ADD:
503 for ssh_keys in ssh_key_sets:
504 self._AddAuthorizedKeys(node, ssh_keys)
505 elif action == constants.SSHS_REMOVE:
506 for ssh_keys in ssh_key_sets:
507 self._RemoveAuthorizedKeys(node, ssh_keys)
508 else:
509 raise Exception("Unsupported action: %s" % action)
510 self._AssertTypeAuthorizedKeys()
511
512 def _EnsurePublicKeyFile(self, file_node_name):
513 if file_node_name not in self._public_keys:
514 self._public_keys[file_node_name] = {}
515 self._AssertTypePublicKeys()
516
517 def _ClearPublicKeys(self, file_node_name):
518 self._public_keys[file_node_name] = {}
519 self._AssertTypePublicKeys()
520
521 def _OverridePublicKeys(self, ssh_keys, file_node_name):
522 assert isinstance(ssh_keys, dict)
523 self._ClearPublicKeys(file_node_name)
524 for key_node_uuid, node_keys in ssh_keys.items():
525 assert isinstance(node_keys, list)
526 if key_node_uuid in self._public_keys[file_node_name]:
527 raise Exception("Duplicate node in ssh_update data.")
528 self._public_keys[file_node_name][key_node_uuid] = node_keys
529 self._AssertTypePublicKeys()
530
531 def _ReplaceOrAddPublicKeys(self, public_keys, file_node_name):
532 assert isinstance(public_keys, dict)
533 self._EnsurePublicKeyFile(file_node_name)
534 for key_node_uuid, keys in public_keys.items():
535 assert isinstance(keys, list)
536 self._public_keys[file_node_name][key_node_uuid] = keys
537 self._AssertTypePublicKeys()
538
539 def _RemovePublicKeys(self, public_keys, file_node_name):
540 assert isinstance(public_keys, dict)
541 self._EnsurePublicKeyFile(file_node_name)
542 for key_node_uuid, _ in public_keys.items():
543 if key_node_uuid in self._public_keys[file_node_name]:
544 self._public_keys[file_node_name][key_node_uuid] = []
545 self._AssertTypePublicKeys()
546
547 def _HandlePublicKeys(self, instructions, node):
548 (action, public_keys) = instructions
549 if action == constants.SSHS_OVERRIDE:
550 self._OverridePublicKeys(public_keys, node)
551 elif action == constants.SSHS_ADD:
552 self._ReplaceOrAddPublicKeys(public_keys, node)
553 elif action == constants.SSHS_REPLACE_OR_ADD:
554 self._ReplaceOrAddPublicKeys(public_keys, node)
555 elif action == constants.SSHS_REMOVE:
556 self._RemovePublicKeys(public_keys, node)
557 elif action == constants.SSHS_CLEAR:
558 self._ClearPublicKeys(node)
559 else:
560 raise Exception("Unsupported action: %s." % action)
561 self._AssertTypePublicKeys()
562
563 # pylint: disable=W0613
564 def AddAuthorizedKeys(self, file_obj, keys):
565 """Emulates ssh.AddAuthorizedKeys on the master node.
566
567 Instead of actually mainpulating the authorized_keys file, this method
568 keeps the state of the file in a dictionary in memory.
569
570 @see: C{ssh.AddAuthorizedKeys}
571
572 """
573 assert isinstance(keys, list)
574 assert self._master_node_name
575 self._AddAuthorizedKeys(self._master_node_name, keys)
576 self._AssertTypeAuthorizedKeys()
577
578 def RemoveAuthorizedKeys(self, file_name, keys):
579 """Emulates ssh.RemoveAuthorizeKeys on the master node.
580
581 Instead of actually mainpulating the authorized_keys file, this method
582 keeps the state of the file in a dictionary in memory.
583
584 @see: C{ssh.RemoveAuthorizedKeys}
585
586 """
587 assert isinstance(keys, list)
588 assert self._master_node_name
589 self._RemoveAuthorizedKeys(self._master_node_name, keys)
590 self._AssertTypeAuthorizedKeys()
591
592 def AddPublicKey(self, new_uuid, new_key, **kwargs):
593 """Emulates ssh.AddPublicKey on the master node.
594
595 Instead of actually mainpulating the authorized_keys file, this method
596 keeps the state of the file in a dictionary in memory.
597
598 @see: C{ssh.AddPublicKey}
599
600 """
601 assert self._master_node_name
602 assert isinstance(new_key, str)
603 key_dict = {new_uuid: [new_key]}
604 self._ReplaceOrAddPublicKeys(key_dict, self._master_node_name)
605 self._AssertTypePublicKeys()
606
607 def RemovePublicKey(self, target_uuid, **kwargs):
608 """Emulates ssh.RemovePublicKey on the master node.
609
610 Instead of actually mainpulating the authorized_keys file, this method
611 keeps the state of the file in a dictionary in memory.
612
613 @see: {ssh.RemovePublicKey}
614
615 """
616 assert self._master_node_name
617 key_dict = {target_uuid: []}
618 self._RemovePublicKeys(key_dict, self._master_node_name)
619 self._AssertTypePublicKeys()
620
621 def QueryPubKeyFile(self, target_uuids, **kwargs):
622 """Emulates ssh.QueryPubKeyFile on the master node.
623
624 Instead of actually mainpulating the authorized_keys file, this method
625 keeps the state of the file in a dictionary in memory.
626
627 @see: C{ssh.QueryPubKey}
628
629 """
630 assert self._master_node_name
631 all_keys = target_uuids is None
632 if all_keys:
633 return self._public_keys[self._master_node_name]
634
635 if isinstance(target_uuids, str):
636 target_uuids = [target_uuids]
637 result_dict = {}
638 for key_node_uuid, keys in \
639 self._public_keys[self._master_node_name].items():
640 if key_node_uuid in target_uuids:
641 result_dict[key_node_uuid] = keys
642 self._AssertTypePublicKeys()
643 return result_dict
644
645 def ReplaceNameByUuid(self, node_uuid, node_name, **kwargs):
646 """Emulates ssh.ReplaceNameByUuid on the master node.
647
648 Instead of actually mainpulating the authorized_keys file, this method
649 keeps the state of the file in a dictionary in memory.
650
651 @see: C{ssh.ReplacenameByUuid}
652
653 """
654 assert isinstance(node_uuid, str)
655 assert isinstance(node_name, str)
656 assert self._master_node_name
657 if node_name in self._public_keys[self._master_node_name]:
658 self._public_keys[self._master_node_name][node_uuid] = \
659 self._public_keys[self._master_node_name][node_name][:]
660 del self._public_keys[self._master_node_name][node_name]
661 self._AssertTypePublicKeys()
662 # pylint: enable=W0613