GEC15Agenda/WednesdayPlenary/RACEDemo: readyToLogin.py

File readyToLogin.py, 17.0 KB (added by nriga@bbn.com, 7 years ago)
Line 
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
26import copy
27import string
28import sys
29import omni
30import os.path
31from optparse import OptionParser
32import omnilib.util.omnierror as oe
33import xml.etree.ElementTree as etree
34import 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
46options = None
47slicename = None
48config = None
49NSPrefix = None
50VALID_NS = ['{http://www.geni.net/resources/rspec/3}',
51            '{http://www.protogeni.net/resources/rspec/2}'
52           ]
53
54def 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
65def 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
71def 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
96def 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
119def 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
167def 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
240def 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
260def 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
287def 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 
302def 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
326def 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
333def 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   
352def 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
390def 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 = """
408Host %(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
432def 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
492def 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
501if __name__ == "__main__":
502    sys.exit(main())