Add more documentation to testutils_ssh.py
[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 """Returns all node names of the cluster.
180
181 @rtype: list of str
182 @returns: list of all node names
183 """
184 return self._all_node_data.keys()
185
186 def GetAllPotentialMasterCandidateNodeNames(self):
187 return [name for name, node_info
188 in self._all_node_data.items()
189 if node_info.is_potential_master_candidate]
190
191 def GetAllMasterCandidateUuids(self):
192 return [node_info.uuid for node_info
193 in self._all_node_data.values() if node_info.is_master_candidate]
194
195 def GetAllPurePotentialMasterCandidates(self):
196 """Get the potential master candidates which are not master candidates.
197
198 @rtype: list of tuples (string, C{_NodeInfo})
199 @returns: list of tuples of node name and node information of nodes
200 which are potential master candidates but not master
201 candidates
202 """
203 return [(name, node_info) for name, node_info
204 in self._all_node_data.items()
205 if node_info.is_potential_master_candidate and
206 not node_info.is_master_candidate]
207
208 def GetAllMasterCandidates(self):
209 """Get all master candidate nodes.
210
211 @rtype: list of tuples (string, C{_NodeInfo})
212 @returns: list of tuples of node name and node information of master
213 candidate nodes.
214 """
215 return [(name, node_info) for name, node_info
216 in self._all_node_data.items() if node_info.is_master_candidate]
217
218 def GetAllNormalNodes(self):
219 """Get all normal nodes.
220
221 Normal nodes are nodes that are neither master, master candidate nor
222 potential master candidate.
223
224 @rtype: list of tuples (string, C{_NodeInfo})
225 @returns: list of tuples of node name and node information of normal
226 nodes
227 """
228 return [(name, node_info) for name, node_info
229 in self._all_node_data.items() if not node_info.is_master_candidate
230 and not node_info.is_potential_master_candidate]
231
232 def GetAllNodesDiverse(self):
233 """This returns all nodes in a diverse order.
234
235 This will return all nodes, but makes sure that they are ordered so that
236 the list will contain in a round-robin fashion, a master candidate,
237 a potential master candidate, a normal node, then again a master
238 candidate, etc.
239
240 @rtype: list of tuples (string, C{_NodeInfo})
241 @returns: list of tuples of node name and node information
242
243 """
244 master_candidates = self.GetAllMasterCandidates()
245 potential_master_candidates = self.GetAllPurePotentialMasterCandidates()
246 normal_nodes = self.GetAllNormalNodes()
247
248 mixed_list = []
249
250 i = 0
251
252 assert (len(self._all_node_data) == len(master_candidates)
253 + len(potential_master_candidates) + len(normal_nodes))
254
255 while len(mixed_list) < len(self._all_node_data):
256 if i % 3 == 0:
257 if master_candidates:
258 mixed_list.append(master_candidates[0])
259 master_candidates = master_candidates[1:]
260 elif i % 3 == 1:
261 if potential_master_candidates:
262 mixed_list.append(potential_master_candidates[0])
263 potential_master_candidates = potential_master_candidates[1:]
264 else: # i % 3 == 2
265 if normal_nodes:
266 mixed_list.append(normal_nodes[0])
267 normal_nodes = normal_nodes[1:]
268 i += 1
269
270 return mixed_list
271
272 def GetPublicKeysOfNode(self, node):
273 """Returns the public keys that are stored on the given node.
274
275 @rtype: dict of str to list of str
276 @returns: a mapping of node names to a list of public keys
277
278 """
279 return self._public_keys[node]
280
281 def GetAuthorizedKeysOfNode(self, node):
282 """Returns the authorized keys of the given node.
283
284 @rtype: list of str
285 @returns: a list of authorized keys that are stored on that node
286
287 """
288 return self._authorized_keys[node]
289
290 def SetOrAddNode(self, name, uuid, key, pot_mc, mc, master):
291 """Adds a new node to the state of the file manager.
292
293 This is necessary when testing to add new nodes to the cluster. Otherwise
294 this new node's state would not be evaluated properly with the assertion
295 functions.
296
297 @type name: string
298 @param name: name of the new node
299 @type uuid: string
300 @param uuid: UUID of the new node
301 @type key: string
302 @param key: SSH key of the new node
303 @type pot_mc: boolean
304 @param pot_mc: whether the new node is a potential master candidate
305 @type mc: boolean
306 @param mc: whether the new node is a master candidate
307 @type master: boolean
308 @param master: whether the new node is the master
309
310 """
311 self._all_node_data[name] = self._NodeInfo(uuid, key, pot_mc, mc, master)
312 if name not in self._authorized_keys:
313 self._authorized_keys[name] = set()
314 if mc:
315 self._authorized_keys[name].add(key)
316 if name not in self._public_keys:
317 self._public_keys[name] = {}
318 self._AssertTypePublicKeys()
319 self._AssertTypeAuthorizedKeys()
320
321 def NodeHasPublicKey(self, file_node_name, key_node_uuid, key):
322 """Checks whether a node has another node's public key.
323
324 @type file_node_name: string
325 @param file_node_name: name of the node whose public key file is inspected
326 @type key_node_uuid: string
327 @param key_node_uuid: UUID of the node whose key is checked for
328 @rtype: boolean
329 @return: True if the key_node's UUID is found with the machting key 'key'
330
331 """
332 for (node_uuid, pub_keys) in self._public_keys[file_node_name].items():
333 if key in pub_keys and key_node_uuid == node_uuid:
334 return True
335 return False
336
337 def NodeHasAuthorizedKey(self, file_node_name, key):
338 """Checks whether a node has a particular key in its authorized_keys file.
339
340 @type file_node_name: string
341 @param file_node_name: name of the node whose authorized_key file is
342 inspected
343 @type key: string
344 @param key: key which is expected to be found in the node's authorized_key
345 file
346 @rtype: boolean
347 @return: True if the key is found in the node's authorized_key file
348
349 """
350 return key in self._authorized_keys[file_node_name]
351
352 def AssertNodeSetOnlyHasAuthorizedKey(self, node_set, query_node_key):
353 """Check if nodes in the given set only have a particular authorized 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_key: string
358 @param query_node_key: key which is looked for
359
360 """
361 assert isinstance(node_set, list)
362 for node_name in self._all_node_data.keys():
363 if node_name in node_set:
364 if not self.NodeHasAuthorizedKey(node_name, query_node_key):
365 raise Exception("Node '%s' does not have authorized key '%s'."
366 % (node_name, query_node_key))
367 else:
368 if self.NodeHasAuthorizedKey(node_name, query_node_key):
369 raise Exception("Node '%s' has authorized key '%s' although it"
370 " should not." % (node_name, query_node_key))
371
372 def AssertAllNodesHaveAuthorizedKey(self, key):
373 """Check if all nodes have a particular key in their auth. keys file.
374
375 @type key: string
376 @param key: key exptected to be present in all node's authorized_keys file
377 @raise Exception: if a node does not have the authorized key.
378
379 """
380 self.AssertNodeSetOnlyHasAuthorizedKey(self._all_node_data.keys(), key)
381
382 def AssertNoNodeHasAuthorizedKey(self, key):
383 """Check if none of the nodes has a particular key in their auth. keys file.
384
385 @type key: string
386 @param key: key exptected to be present in all node's authorized_keys file
387 @raise Exception: if a node *does* have the authorized key.
388
389 """
390 self.AssertNodeSetOnlyHasAuthorizedKey([], key)
391
392 def AssertNodeSetOnlyHasPublicKey(self, node_set, query_node_uuid,
393 query_node_key):
394 """Check if nodes in the given set only have a particular public key.
395
396 @type node_set: list of strings
397 @param node_set: list of nodes who are supposed to have the key
398 @type query_node_uuid: string
399 @param query_node_uuid: uuid of the node whose key is looked for
400 @type query_node_key: string
401 @param query_node_key: key which is looked for
402
403 """
404 for node_name in self._all_node_data.keys():
405 if node_name in node_set:
406 if not self.NodeHasPublicKey(node_name, query_node_uuid,
407 query_node_key):
408 raise Exception("Node '%s' does not have public key '%s' of node"
409 " '%s'." % (node_name, query_node_key,
410 query_node_uuid))
411 else:
412 if self.NodeHasPublicKey(node_name, query_node_uuid, query_node_key):
413 raise Exception("Node '%s' has public key '%s' of node"
414 " '%s' although it should not."
415 % (node_name, query_node_key, query_node_uuid))
416
417 def AssertNoNodeHasPublicKey(self, uuid, key):
418 """Check if none of the nodes have the given public key in their file.
419
420 @type uuid: string
421 @param uuid: UUID of the node whose key is looked for
422 @raise Exception: if a node *does* have the public key.
423
424 """
425 self.AssertNodeSetOnlyHasPublicKey([], uuid, key)
426
427 def AssertPotentialMasterCandidatesOnlyHavePublicKey(self, query_node_name):
428 """Checks if the node's key is on all potential master candidates only.
429
430 This ensures that the node's key is in all public key files of all
431 potential master candidates, and it also checks whether the key is
432 *not* in all other nodes's key files.
433
434 @param query_node_name: name of the node whose key is expected to be
435 in the public key file of all potential master
436 candidates
437 @type query_node_name: string
438 @raise Exception: when a potential master candidate does not have
439 the public key or a normal node *does* have a public key.
440
441 """
442 query_node_uuid, query_node_key, _, _, _ = \
443 self._all_node_data[query_node_name]
444 potential_master_candidates = self.GetAllPotentialMasterCandidateNodeNames()
445 self.AssertNodeSetOnlyHasPublicKey(
446 potential_master_candidates, query_node_uuid, query_node_key)
447
448 def _AssertTypePublicKeys(self):
449 """Asserts that the public key dictionary has the right types.
450
451 This is helpful as an invariant that shall not be violated during the
452 tests due to type errors.
453
454 """
455 assert isinstance(self._public_keys, dict)
456 for node_file, pub_keys in self._public_keys.items():
457 assert isinstance(node_file, str)
458 assert isinstance(pub_keys, dict)
459 for node_key, keys in pub_keys.items():
460 assert isinstance(node_key, str)
461 assert isinstance(keys, list)
462 for key in keys:
463 assert isinstance(key, str)
464
465 def _AssertTypeAuthorizedKeys(self):
466 """Asserts that the authorized keys dictionary has the right types.
467
468 This is useful to check as an invariant that is not supposed to be violated
469 during the tests.
470
471 """
472 assert isinstance(self._authorized_keys, dict)
473 for node_file, auth_keys in self._authorized_keys.items():
474 assert isinstance(node_file, str)
475 assert isinstance(auth_keys, set)
476 for key in auth_keys:
477 assert isinstance(key, str)
478
479 # Disabling a pylint warning about unused parameters. Those need
480 # to be here to properly mock the real methods.
481 # pylint: disable=W0613
482 def RunCommand(self, cluster_name, node, base_cmd, port, data,
483 debug=False, verbose=False, use_cluster_key=False,
484 ask_key=False, strict_host_check=False,
485 ensure_version=False):
486 """This emulates ssh.RunSshCmdWithStdin calling ssh_update.
487
488 While in real SSH operations, ssh.RunSshCmdWithStdin is called
489 with the command ssh_update to manipulate a remote node's SSH
490 key files (authorized_keys and ganeti_pub_key) file, this method
491 emulates the operation by manipulating only its internal dictionaries
492 of SSH keys. No actual key files of any node is touched.
493
494 """
495 if node in self._max_retries:
496 if node not in self._retries:
497 self._retries[node] = 0
498 self._retries[node] += 1
499 if self._retries[node] < self._max_retries[node]:
500 raise errors.OpExecError("(Fake) SSH connection to node '%s' failed."
501 % node)
502
503 assert base_cmd == pathutils.SSH_UPDATE
504
505 if constants.SSHS_SSH_AUTHORIZED_KEYS in data:
506 instructions_auth = data[constants.SSHS_SSH_AUTHORIZED_KEYS]
507 self._HandleAuthorizedKeys(instructions_auth, node)
508 if constants.SSHS_SSH_PUBLIC_KEYS in data:
509 instructions_pub = data[constants.SSHS_SSH_PUBLIC_KEYS]
510 self._HandlePublicKeys(instructions_pub, node)
511 # pylint: enable=W0613
512
513 def _EnsureAuthKeyFile(self, file_node_name):
514 if file_node_name not in self._authorized_keys:
515 self._authorized_keys[file_node_name] = set()
516 self._AssertTypePublicKeys()
517 self._AssertTypeAuthorizedKeys()
518
519 def _AddAuthorizedKeys(self, file_node_name, ssh_keys):
520 """Mocks adding the given keys to the authorized_keys file."""
521 assert isinstance(ssh_keys, list)
522 self._EnsureAuthKeyFile(file_node_name)
523 for key in ssh_keys:
524 self._authorized_keys[file_node_name].add(key)
525 self._AssertTypePublicKeys()
526 self._AssertTypeAuthorizedKeys()
527
528 def _RemoveAuthorizedKeys(self, file_node_name, keys):
529 """Mocks removing the keys from authorized_keys on the given node.
530
531 @param keys: list of ssh keys
532 @type keys: list of strings
533
534 """
535 self._EnsureAuthKeyFile(file_node_name)
536 self._authorized_keys[file_node_name] = \
537 set([k for k in self._authorized_keys[file_node_name] if k not in keys])
538 self._AssertTypeAuthorizedKeys()
539
540 def _HandleAuthorizedKeys(self, instructions, node):
541 (action, authorized_keys) = instructions
542 ssh_key_sets = authorized_keys.values()
543 if action == constants.SSHS_ADD:
544 for ssh_keys in ssh_key_sets:
545 self._AddAuthorizedKeys(node, ssh_keys)
546 elif action == constants.SSHS_REMOVE:
547 for ssh_keys in ssh_key_sets:
548 self._RemoveAuthorizedKeys(node, ssh_keys)
549 else:
550 raise Exception("Unsupported action: %s" % action)
551 self._AssertTypeAuthorizedKeys()
552
553 def _EnsurePublicKeyFile(self, file_node_name):
554 if file_node_name not in self._public_keys:
555 self._public_keys[file_node_name] = {}
556 self._AssertTypePublicKeys()
557
558 def _ClearPublicKeys(self, file_node_name):
559 self._public_keys[file_node_name] = {}
560 self._AssertTypePublicKeys()
561
562 def _OverridePublicKeys(self, ssh_keys, file_node_name):
563 assert isinstance(ssh_keys, dict)
564 self._ClearPublicKeys(file_node_name)
565 for key_node_uuid, node_keys in ssh_keys.items():
566 assert isinstance(node_keys, list)
567 if key_node_uuid in self._public_keys[file_node_name]:
568 raise Exception("Duplicate node in ssh_update data.")
569 self._public_keys[file_node_name][key_node_uuid] = node_keys
570 self._AssertTypePublicKeys()
571
572 def _ReplaceOrAddPublicKeys(self, public_keys, file_node_name):
573 assert isinstance(public_keys, dict)
574 self._EnsurePublicKeyFile(file_node_name)
575 for key_node_uuid, keys in public_keys.items():
576 assert isinstance(keys, list)
577 self._public_keys[file_node_name][key_node_uuid] = keys
578 self._AssertTypePublicKeys()
579
580 def _RemovePublicKeys(self, public_keys, file_node_name):
581 assert isinstance(public_keys, dict)
582 self._EnsurePublicKeyFile(file_node_name)
583 for key_node_uuid, _ in public_keys.items():
584 if key_node_uuid in self._public_keys[file_node_name]:
585 self._public_keys[file_node_name][key_node_uuid] = []
586 self._AssertTypePublicKeys()
587
588 def _HandlePublicKeys(self, instructions, node):
589 (action, public_keys) = instructions
590 if action == constants.SSHS_OVERRIDE:
591 self._OverridePublicKeys(public_keys, node)
592 elif action == constants.SSHS_ADD:
593 self._ReplaceOrAddPublicKeys(public_keys, node)
594 elif action == constants.SSHS_REPLACE_OR_ADD:
595 self._ReplaceOrAddPublicKeys(public_keys, node)
596 elif action == constants.SSHS_REMOVE:
597 self._RemovePublicKeys(public_keys, node)
598 elif action == constants.SSHS_CLEAR:
599 self._ClearPublicKeys(node)
600 else:
601 raise Exception("Unsupported action: %s." % action)
602 self._AssertTypePublicKeys()
603
604 # pylint: disable=W0613
605 def AddAuthorizedKeys(self, file_obj, keys):
606 """Emulates ssh.AddAuthorizedKeys on the master node.
607
608 Instead of actually mainpulating the authorized_keys file, this method
609 keeps the state of the file in a dictionary in memory.
610
611 @see: C{ssh.AddAuthorizedKeys}
612
613 """
614 assert isinstance(keys, list)
615 assert self._master_node_name
616 self._AddAuthorizedKeys(self._master_node_name, keys)
617 self._AssertTypeAuthorizedKeys()
618
619 def RemoveAuthorizedKeys(self, file_name, keys):
620 """Emulates ssh.RemoveAuthorizeKeys on the master node.
621
622 Instead of actually mainpulating the authorized_keys file, this method
623 keeps the state of the file in a dictionary in memory.
624
625 @see: C{ssh.RemoveAuthorizedKeys}
626
627 """
628 assert isinstance(keys, list)
629 assert self._master_node_name
630 self._RemoveAuthorizedKeys(self._master_node_name, keys)
631 self._AssertTypeAuthorizedKeys()
632
633 def AddPublicKey(self, new_uuid, new_key, **kwargs):
634 """Emulates ssh.AddPublicKey on the master node.
635
636 Instead of actually mainpulating the authorized_keys file, this method
637 keeps the state of the file in a dictionary in memory.
638
639 @see: C{ssh.AddPublicKey}
640
641 """
642 assert self._master_node_name
643 assert isinstance(new_key, str)
644 key_dict = {new_uuid: [new_key]}
645 self._ReplaceOrAddPublicKeys(key_dict, self._master_node_name)
646 self._AssertTypePublicKeys()
647
648 def RemovePublicKey(self, target_uuid, **kwargs):
649 """Emulates ssh.RemovePublicKey on the master node.
650
651 Instead of actually mainpulating the authorized_keys file, this method
652 keeps the state of the file in a dictionary in memory.
653
654 @see: {ssh.RemovePublicKey}
655
656 """
657 assert self._master_node_name
658 key_dict = {target_uuid: []}
659 self._RemovePublicKeys(key_dict, self._master_node_name)
660 self._AssertTypePublicKeys()
661
662 def QueryPubKeyFile(self, target_uuids, **kwargs):
663 """Emulates ssh.QueryPubKeyFile on the master node.
664
665 Instead of actually mainpulating the authorized_keys file, this method
666 keeps the state of the file in a dictionary in memory.
667
668 @see: C{ssh.QueryPubKey}
669
670 """
671 assert self._master_node_name
672 all_keys = target_uuids is None
673 if all_keys:
674 return self._public_keys[self._master_node_name]
675
676 if isinstance(target_uuids, str):
677 target_uuids = [target_uuids]
678 result_dict = {}
679 for key_node_uuid, keys in \
680 self._public_keys[self._master_node_name].items():
681 if key_node_uuid in target_uuids:
682 result_dict[key_node_uuid] = keys
683 self._AssertTypePublicKeys()
684 return result_dict
685
686 def ReplaceNameByUuid(self, node_uuid, node_name, **kwargs):
687 """Emulates ssh.ReplaceNameByUuid on the master node.
688
689 Instead of actually mainpulating the authorized_keys file, this method
690 keeps the state of the file in a dictionary in memory.
691
692 @see: C{ssh.ReplacenameByUuid}
693
694 """
695 assert isinstance(node_uuid, str)
696 assert isinstance(node_name, str)
697 assert self._master_node_name
698 if node_name in self._public_keys[self._master_node_name]:
699 self._public_keys[self._master_node_name][node_uuid] = \
700 self._public_keys[self._master_node_name][node_name][:]
701 del self._public_keys[self._master_node_name][node_name]
702 self._AssertTypePublicKeys()
703 # pylint: enable=W0613