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