1 | #!/usr/bin/python |
---|
2 | |
---|
3 | #---------------------------------------------------------------------- |
---|
4 | # Copyright (c) 2011 Raytheon BBN Technologies |
---|
5 | # |
---|
6 | # Permission is hereby granted, free of charge, to any person obtaining |
---|
7 | # a copy of this software and/or hardware specification (the "Work") to |
---|
8 | # deal in the Work without restriction, including without limitation the |
---|
9 | # rights to use, copy, modify, merge, publish, distribute, sublicense, |
---|
10 | # and/or sell copies of the Work, and to permit persons to whom the Work |
---|
11 | # is furnished to do so, subject to the following conditions: |
---|
12 | # |
---|
13 | # The above copyright notice and this permission notice shall be |
---|
14 | # included in all copies or substantial portions of the Work. |
---|
15 | # |
---|
16 | # THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
---|
17 | # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
---|
18 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
---|
19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
---|
20 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
---|
21 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
---|
22 | # OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS |
---|
23 | # IN THE WORK. |
---|
24 | #---------------------------------------------------------------------- |
---|
25 | |
---|
26 | import copy |
---|
27 | import string |
---|
28 | import sys |
---|
29 | import omni |
---|
30 | import os.path |
---|
31 | from optparse import OptionParser |
---|
32 | import omnilib.util.omnierror as oe |
---|
33 | import xml.etree.ElementTree as etree |
---|
34 | import re |
---|
35 | |
---|
36 | ################################################################################ |
---|
37 | # Requires that you have omni installed or the path to gcf/src in your |
---|
38 | # PYTHONPATH. |
---|
39 | # |
---|
40 | # For example put the following in your bashrc: |
---|
41 | # export PYTHONPATH=${PYTHONPATH}:path/to/gcf/src |
---|
42 | # |
---|
43 | ################################################################################ |
---|
44 | |
---|
45 | #Global variables |
---|
46 | options = None |
---|
47 | slicename = None |
---|
48 | config = None |
---|
49 | NSPrefix = None |
---|
50 | VALID_NS = ['{http://www.geni.net/resources/rspec/3}', |
---|
51 | '{http://www.protogeni.net/resources/rspec/2}' |
---|
52 | ] |
---|
53 | |
---|
54 | def setNSPrefix(prefix): |
---|
55 | ''' Helper function for parsing rspecs. It sets the global variabl NSPrefix to |
---|
56 | the currently parsed rspec namespace. |
---|
57 | ''' |
---|
58 | global NSPrefix |
---|
59 | if prefix not in VALID_NS: |
---|
60 | print "Listresources namespace %s is not valid. Exit!" |
---|
61 | sys.exit(-1) |
---|
62 | |
---|
63 | NSPrefix = prefix |
---|
64 | |
---|
65 | def tag(tag): |
---|
66 | ''' Helper function for parsing rspecs. It gets a tag and uses the global |
---|
67 | NSPrefix to return the full name |
---|
68 | ''' |
---|
69 | return "%s%s" %(NSPrefix,tag) |
---|
70 | |
---|
71 | def getInfoFromManifest(manifestStr): |
---|
72 | ''' Function that takes as input a manifest rspec in a string and parses the |
---|
73 | services tag to extract login information. |
---|
74 | This function returns a list of dictionaries, each dictionary contains |
---|
75 | login information |
---|
76 | ''' |
---|
77 | try: |
---|
78 | dom = etree.fromstring(manifestStr) |
---|
79 | except Exception, e: |
---|
80 | print "Couldn't parse the manifest RSpec." |
---|
81 | sys.exit(-1) |
---|
82 | |
---|
83 | setNSPrefix(re.findall(r'\{.*\}', dom.tag)[0]) |
---|
84 | loginInfo = [] |
---|
85 | for node_el in dom.findall(tag("node")): |
---|
86 | for serv_el in node_el.findall(tag("services")): |
---|
87 | try: |
---|
88 | loginInfo.append(serv_el.find(tag("login")).attrib) |
---|
89 | loginInfo[-1]["client_id"] = node_el.attrib["client_id"] |
---|
90 | except AttributeError: |
---|
91 | print "Couldn't get login information, maybe your sliver is not ready. Run sliverstatus." |
---|
92 | sys.exit(-1) |
---|
93 | |
---|
94 | return loginInfo |
---|
95 | |
---|
96 | def findUsersAndKeys( ): |
---|
97 | """Look in omni_config for user and key information of the public keys that |
---|
98 | are installed in the nodes. It uses the global variable config and returns |
---|
99 | keyList which is a dictionary of keyLists per user""" |
---|
100 | keyList = {} |
---|
101 | if not config.has_key('users'): |
---|
102 | print "Your omni_config is missing the 'users' attribute." |
---|
103 | return keyList |
---|
104 | |
---|
105 | for user in config['users']: |
---|
106 | # convert strings containing public keys (foo.pub) into |
---|
107 | # private keys (foo) |
---|
108 | username = user['urn'].split('+')[-1] |
---|
109 | keyList[username] = [] |
---|
110 | privuserkeys = string.replace(user['keys'].replace(" ",""), ".pub","") |
---|
111 | privuserkeys = privuserkeys.split(",") |
---|
112 | for key in privuserkeys: |
---|
113 | if not os.path.exists(os.path.expanduser(key)): |
---|
114 | print "Key file [%s] does NOT exist." % key |
---|
115 | else: |
---|
116 | keyList[username].append(key) |
---|
117 | return keyList |
---|
118 | |
---|
119 | def getInfoFromSliceManifest( amUrl ) : |
---|
120 | tmpoptions = copy.deepcopy(options) |
---|
121 | tmpoptions.aggregate = [amUrl] |
---|
122 | |
---|
123 | |
---|
124 | # Run the equivalent of 'omni.py listresources <slicename>' |
---|
125 | if tmpoptions.api_version >= 3: |
---|
126 | apicall = 'describe' |
---|
127 | else : |
---|
128 | apicall = 'listresources' |
---|
129 | |
---|
130 | argv = [apicall, slicename] |
---|
131 | try: |
---|
132 | text, apicallout = omni.call( argv, tmpoptions ) |
---|
133 | except (oe.AMAPIError, oe.OmniError) : |
---|
134 | print "ERROR: There was an error executing %s, review the logs." % apicall |
---|
135 | sys.exit(-1) |
---|
136 | |
---|
137 | key = amUrl |
---|
138 | if tmpoptions.api_version == 1: |
---|
139 | # Key is (urn,url) |
---|
140 | key = ("unspecified_AM_URN", amUrl) |
---|
141 | |
---|
142 | if not apicallout.has_key(key): |
---|
143 | print "ERROR: No manifest found from %s at %s; review the logs." % \ |
---|
144 | (apicall, amUrl) |
---|
145 | sys.exit(-1) |
---|
146 | |
---|
147 | if tmpoptions.api_version == 1: |
---|
148 | manifest = apicallout[key] |
---|
149 | else: |
---|
150 | if not apicallout [key].has_key("value"): |
---|
151 | print "ERROR: No value slot in return from %s from %s; review the logs."\ |
---|
152 | % (apicall, amUrl) |
---|
153 | sys.exit(-1) |
---|
154 | value = apicallout[key]["value"] |
---|
155 | |
---|
156 | if tmpoptions.api_version == 2: |
---|
157 | manifest = value |
---|
158 | else: |
---|
159 | if tmpoptions.api_version == 3: |
---|
160 | manifest = value['geni_rspec'] |
---|
161 | else: |
---|
162 | print "ERROR: API v%s not yet supported" %tmpoptions.api_version |
---|
163 | sys.exit(-1) |
---|
164 | |
---|
165 | return getInfoFromManifest(manifest) |
---|
166 | |
---|
167 | def getInfoFromSliverStatusPG( sliverStat ): |
---|
168 | |
---|
169 | loginInfo = [] |
---|
170 | pgKeyList = {} |
---|
171 | if not sliverStat: |
---|
172 | print "ERROR: empty sliver status!" |
---|
173 | return loginInfo |
---|
174 | |
---|
175 | if not sliverStat.has_key("users"): |
---|
176 | print "ERROR: No 'users' key in sliver status!" |
---|
177 | return loginInfo |
---|
178 | |
---|
179 | if not sliverStat.has_key('geni_resources'): |
---|
180 | print "ERROR: Sliver Status lists no resources" |
---|
181 | return loginInfo |
---|
182 | |
---|
183 | for userDict in sliverStat['users'] : |
---|
184 | if not userDict.has_key('login'): |
---|
185 | print "User entry had no 'login' key" |
---|
186 | continue |
---|
187 | pgKeyList[userDict['login']] = [] |
---|
188 | if not userDict.has_key("keys"): |
---|
189 | print "User entry for %s had no keys" % userDict['login'] |
---|
190 | continue |
---|
191 | for k in userDict['keys']: |
---|
192 | #XXX nriga Keep track of keys, in the future we can verify what key goes with |
---|
193 | # which private key |
---|
194 | pgKeyList[userDict['login']].append(k['key']) |
---|
195 | |
---|
196 | for resourceDict in sliverStat['geni_resources']: |
---|
197 | if not resourceDict.has_key("pg_manifest"): |
---|
198 | print "No pg_manifest in this entry" |
---|
199 | continue |
---|
200 | if not resourceDict['pg_manifest'].has_key('children'): |
---|
201 | print "pg_manifest entry has no children" |
---|
202 | continue |
---|
203 | for children1 in resourceDict['pg_manifest']['children']: |
---|
204 | if not children1.has_key('children'): |
---|
205 | continue |
---|
206 | for children2 in children1['children']: |
---|
207 | if not children2.has_key("attributes"): |
---|
208 | continue |
---|
209 | child = children2['attributes'] |
---|
210 | port = "" |
---|
211 | hostname = "" |
---|
212 | if child.has_key("hostname"): |
---|
213 | hostname = child["hostname"] |
---|
214 | else: |
---|
215 | continue |
---|
216 | if child.has_key("port"): |
---|
217 | port = child["port"] |
---|
218 | client_id = "" |
---|
219 | if resourceDict["pg_manifest"].has_key("attributes") and resourceDict["pg_manifest"]["attributes"].has_key("client_id"): |
---|
220 | client_id = resourceDict["pg_manifest"]["attributes"]["client_id"] |
---|
221 | geni_status = "" |
---|
222 | if resourceDict.has_key("geni_status"): |
---|
223 | geni_status = resourceDict["geni_status"] |
---|
224 | am_status = "" |
---|
225 | if resourceDict.has_key("pg_status"): |
---|
226 | am_status = resourceDict["pg_status"] |
---|
227 | for user, keys in pgKeyList.items(): |
---|
228 | loginInfo.append({'authentication':'ssh-keys', |
---|
229 | 'hostname':hostname, |
---|
230 | 'client_id': client_id, |
---|
231 | 'port':port, |
---|
232 | 'username':user, |
---|
233 | 'keys' : keys, |
---|
234 | 'geni_status':geni_status, |
---|
235 | 'am_status':am_status |
---|
236 | }) |
---|
237 | return loginInfo |
---|
238 | |
---|
239 | |
---|
240 | def getInfoFromSliverStatusPL( sliverStat ): |
---|
241 | |
---|
242 | loginInfo = [] |
---|
243 | if not sliverStat or not sliverStat.has_key('geni_resources'): |
---|
244 | print "ERROR: Empty Sliver Status, or no geni_resources listed" |
---|
245 | return loginInfo |
---|
246 | |
---|
247 | for resourceDict in sliverStat['geni_resources']: |
---|
248 | if (not sliverStat['pl_login']) or (not resourceDict['pl_hostname']): |
---|
249 | continue |
---|
250 | loginInfo.append({'authentication':'ssh-keys', |
---|
251 | 'hostname':resourceDict['pl_hostname'], |
---|
252 | 'client_id':resourceDict['pl_hostname'], |
---|
253 | 'port':'22', |
---|
254 | 'username':sliverStat['pl_login'], |
---|
255 | 'geni_status':resourceDict['geni_status'], |
---|
256 | 'am_status':resourceDict['pl_boot_state'] |
---|
257 | }) |
---|
258 | return loginInfo |
---|
259 | |
---|
260 | def getInfoFromSliverStatus( amUrl, amType ) : |
---|
261 | tmpoptions = copy.deepcopy(options) |
---|
262 | tmpoptions.aggregate = [amUrl] |
---|
263 | |
---|
264 | # Run equivalent of 'omni.py sliverstatus username' |
---|
265 | argv = ['sliverstatus', slicename] |
---|
266 | try: |
---|
267 | text, sliverStatus = omni.call( argv, tmpoptions ) |
---|
268 | except (oe.AMAPIError, oe.OmniError) : |
---|
269 | print "ERROR: There was an error executing sliverstatus, review the logs." |
---|
270 | sys.exit(-1) |
---|
271 | |
---|
272 | if not sliverStatus: |
---|
273 | print "ERROR: Got no SliverStatus for AM %s; check the logs. Message: %s" % (amUrl, text) |
---|
274 | sys.exit(-1) |
---|
275 | |
---|
276 | if not sliverStatus.has_key(amUrl): |
---|
277 | print "ERROR: Got no SliverStatus for AM %s; check the logs." % (amUrl) |
---|
278 | sys.exit(-1) |
---|
279 | |
---|
280 | if amType == 'sfa' : |
---|
281 | loginInfo = getInfoFromSliverStatusPL(sliverStatus[amUrl]) |
---|
282 | if amType == 'protogeni' : |
---|
283 | loginInfo = getInfoFromSliverStatusPG(sliverStatus[amUrl]) |
---|
284 | |
---|
285 | return loginInfo |
---|
286 | |
---|
287 | def getAMTypeFromGetVersionOut(amUrl, amOutput) : |
---|
288 | if amOutput.has_key("code") and amOutput["code"].has_key("am_type"): |
---|
289 | return amOutput["code"]["am_type"].strip() |
---|
290 | # Older version of SFA do not have the code field, do a hack and check if |
---|
291 | # testbed is there |
---|
292 | if amOutput.has_key("testbed"): |
---|
293 | return "sfa" |
---|
294 | # FOAM does not have a code field, use foam_version |
---|
295 | if amOutput.has_key("foam_version"): |
---|
296 | return "foam" |
---|
297 | # ORCA does not have a code field, use foam_version |
---|
298 | if amOutput.has_key("value") and amOutput["value"].has_key("orca_version"): |
---|
299 | return "orca" |
---|
300 | return None |
---|
301 | |
---|
302 | def parseArguments( argv=None ) : |
---|
303 | global slicename, options, config |
---|
304 | |
---|
305 | parser = omni.getParser() |
---|
306 | # Parse Options |
---|
307 | usage = "\n\tTypically: \treadyToLogin.py slicename" |
---|
308 | parser.set_usage(usage) |
---|
309 | |
---|
310 | parser.add_option("-x", "--xterm", dest="xterm", |
---|
311 | action="store_false", |
---|
312 | default=True, |
---|
313 | help="do NOT add xterm") |
---|
314 | parser.add_option( "--readyonly", dest="readyonly", |
---|
315 | action="store_true", |
---|
316 | default=False, |
---|
317 | help="Only print nodes in ready state") |
---|
318 | (options, args) = parser.parse_args() |
---|
319 | |
---|
320 | if len(args) > 0: |
---|
321 | slicename = args[0] |
---|
322 | else: |
---|
323 | sys.exit("Must pass in slicename as argument of script.\nRun '%s -h' for more information."%sys.argv[0]) |
---|
324 | |
---|
325 | |
---|
326 | def addNodeStatus(amUrl, amType, amLoginInfo): |
---|
327 | ''' This function is intended to get the node status from SliverStatus, in |
---|
328 | case the login information comes from the manifest rspec that does not contain |
---|
329 | status information |
---|
330 | ''' |
---|
331 | print "NOT IMPLEMENTED YET" |
---|
332 | |
---|
333 | def getKeysForUser( amType, username, keyList ): |
---|
334 | '''Returns a list of keys for the provided user based on the |
---|
335 | list from omni_config file that is saved at keyList |
---|
336 | ''' |
---|
337 | userKeyList = [] |
---|
338 | if not keyList: |
---|
339 | return userKeyList |
---|
340 | |
---|
341 | for user,keys in keyList.items() : |
---|
342 | #ProtoGENI actually creates accounts per user so check the username |
---|
343 | # before adding the key. ORCA and PL just add all the keys to one |
---|
344 | # user |
---|
345 | if amType == "protogeni" and user != username : |
---|
346 | continue |
---|
347 | for k in keys: |
---|
348 | userKeyList.append(k) |
---|
349 | |
---|
350 | return userKeyList |
---|
351 | |
---|
352 | def printLoginInfo( loginInfoDict, keyList ) : |
---|
353 | '''Prints the Login Information from all AMs, all Users and all hosts ''' |
---|
354 | for amUrl, amInfo in loginInfoDict.items() : |
---|
355 | print "" |
---|
356 | print "="*80 |
---|
357 | print "LOGIN INFO for AM: %s" % amUrl |
---|
358 | print "="*80 |
---|
359 | for item in amInfo["info"] : |
---|
360 | output = "" |
---|
361 | if options.readyonly : |
---|
362 | try: |
---|
363 | if item['geni_status'] != "ready" : |
---|
364 | continue |
---|
365 | except KeyError: |
---|
366 | print "There is no status information for node %s. Print login info." |
---|
367 | # If there are status info print it, if not just skip it |
---|
368 | try: |
---|
369 | output += "\n%s's geni_status is: %s (am_status:%s) \n" % (item['client_id'], item['geni_status'],item['am_status']) |
---|
370 | # Check if node is in ready state |
---|
371 | except KeyError: |
---|
372 | pass |
---|
373 | |
---|
374 | keys = getKeysForUser(amInfo["amType"], item["username"], keyList) |
---|
375 | |
---|
376 | output += "User %s logins to %s using:\n" % (item['username'], item['client_id']) |
---|
377 | for key in keys: |
---|
378 | output += "\t" |
---|
379 | if options.xterm : |
---|
380 | output += "xterm -e ssh" |
---|
381 | if str(item['port']) != '22' : |
---|
382 | output += " -p %s " % item['port'] |
---|
383 | output += " -i %s %s@%s" % ( key, item['username'], item['hostname']) |
---|
384 | if options.xterm : |
---|
385 | output += " &" |
---|
386 | output += "\n" |
---|
387 | print output |
---|
388 | |
---|
389 | |
---|
390 | def printSSHConfigInfo( loginInfoDict, keyList ) : |
---|
391 | '''Prints the SSH config Information from all AMs, all Users and all hosts ''' |
---|
392 | |
---|
393 | sshConfList={} |
---|
394 | for amUrl, amInfo in loginInfoDict.items() : |
---|
395 | for item in amInfo["info"] : |
---|
396 | output = "" |
---|
397 | if options.readyonly : |
---|
398 | try: |
---|
399 | if item['geni_status'] != "ready" : |
---|
400 | continue |
---|
401 | except KeyError: |
---|
402 | print "There is no status information for node %s. Print login info." |
---|
403 | # If there are status info print it, if not just skip it |
---|
404 | |
---|
405 | keys = getKeysForUser(amInfo["amType"], item["username"], keyList) |
---|
406 | |
---|
407 | output = """ |
---|
408 | Host %(client_id)s |
---|
409 | Port %(port)s |
---|
410 | HostName %(hostname)s |
---|
411 | User %(username)s """ % item |
---|
412 | |
---|
413 | for key in keys: |
---|
414 | output +=""" |
---|
415 | IdentityFile %s """ % key |
---|
416 | |
---|
417 | try: |
---|
418 | sshConfList[item["username"]].append(output) |
---|
419 | except KeyError: |
---|
420 | sshConfList[item["username"]] = [] |
---|
421 | sshConfList[item["username"]].append(output) |
---|
422 | |
---|
423 | for user, conf in sshConfList.items(): |
---|
424 | print "="*80 |
---|
425 | print "SSH CONFIGURATION INFO for User %s" % user |
---|
426 | print "="*80 |
---|
427 | for c in conf: |
---|
428 | print c |
---|
429 | print "\n" |
---|
430 | |
---|
431 | |
---|
432 | def main_no_print(argv=None): |
---|
433 | global slicename, options, config |
---|
434 | |
---|
435 | parseArguments(argv=argv) |
---|
436 | |
---|
437 | # Call omni.initialize so that we get the config structure that |
---|
438 | # contains the configuration parameters from the omni_config file |
---|
439 | # We need them to get the ssh keys per user |
---|
440 | framework, config, args, opts = omni.initialize( [], options ) |
---|
441 | |
---|
442 | keyList = findUsersAndKeys( ) |
---|
443 | if sum(len(val) for val in keyList.itervalues())== 0: |
---|
444 | output = "ERROR:There are no keys. You can not login to your nodes.\n" |
---|
445 | sys.exit(-1) |
---|
446 | |
---|
447 | # Run equivalent of 'omni.py getversion' |
---|
448 | argv = ['getversion'] |
---|
449 | try: |
---|
450 | text, getVersion = omni.call( argv, options ) |
---|
451 | except (oe.AMAPIError, oe.OmniError) : |
---|
452 | print "ERROR: There was an error executing getVersion, review the logs." |
---|
453 | sys.exit(-1) |
---|
454 | |
---|
455 | if not getVersion: |
---|
456 | print "ERROR: Got no GetVersion output; review the logs." |
---|
457 | sys.exit(-1) |
---|
458 | |
---|
459 | loginInfoDict = {} |
---|
460 | for amUrl, amOutput in getVersion.items() : |
---|
461 | if not amOutput : |
---|
462 | print "%s returned an error on getVersion, skip!" % amUrl |
---|
463 | continue |
---|
464 | amType = getAMTypeFromGetVersionOut(amUrl, amOutput) |
---|
465 | print amType |
---|
466 | |
---|
467 | if amType == "foam" : |
---|
468 | print "No login information for FOAM! Skip %s" %amUrl |
---|
469 | continue |
---|
470 | # XXX Although ProtoGENI returns the service tag in the manifest |
---|
471 | # it does not contain information for all the users, so we will |
---|
472 | # stick with the sliverstatus until this is fixed |
---|
473 | if amType == "sfa" or (amType == "protogeni" and options.api_version< 3) : |
---|
474 | amLoginInfo = getInfoFromSliverStatus(amUrl, amType) |
---|
475 | if len(amLoginInfo) > 0 : |
---|
476 | loginInfoDict[amUrl] = {'amType' : amType, |
---|
477 | 'info' : amLoginInfo |
---|
478 | } |
---|
479 | continue |
---|
480 | if amType == "orca" or (amType == "protogeni" and options.api_version>= 3): |
---|
481 | amLoginInfo = getInfoFromSliceManifest(amUrl) |
---|
482 | # Get the status only if we care |
---|
483 | if len(amLoginInfo) > 0 : |
---|
484 | if options.readyonly: |
---|
485 | amLoginInfo = addNodeStatus(amUrl, amType, amLoginInfo) |
---|
486 | loginInfoDict[amUrl] = {'amType':amType, |
---|
487 | 'info':amLoginInfo |
---|
488 | } |
---|
489 | return loginInfoDict, keyList |
---|
490 | |
---|
491 | |
---|
492 | def main(argv=None): |
---|
493 | loginInfoDict, keyList = main_no_print(argv=argv) |
---|
494 | printSSHConfigInfo(loginInfoDict, keyList) |
---|
495 | printLoginInfo(loginInfoDict, keyList) |
---|
496 | if not loginInfoDict: |
---|
497 | print "No login information found!!" |
---|
498 | if not keyList: |
---|
499 | print "No keys found!!" |
---|
500 | |
---|
501 | if __name__ == "__main__": |
---|
502 | sys.exit(main()) |
---|