shuffle/unexpose commands so that myslice comes last
[sfa.git] / sfa / client / sfi.py
1 #
2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
4 #
5
6 import sys
7 sys.path.append('.')
8
9 import os, os.path
10 import socket
11 import re
12 import datetime
13 import codecs
14 import pickle
15 import json
16 import shutil
17 from lxml import etree
18 from StringIO import StringIO
19 from optparse import OptionParser
20 from pprint import PrettyPrinter
21 from tempfile import mkstemp
22
23 from sfa.trust.certificate import Keypair, Certificate
24 from sfa.trust.gid import GID
25 from sfa.trust.credential import Credential
26 from sfa.trust.sfaticket import SfaTicket
27
28 from sfa.util.faults import SfaInvalidArgument
29 from sfa.util.sfalogging import sfi_logger
30 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
31 from sfa.util.config import Config
32 from sfa.util.version import version_core
33 from sfa.util.cache import Cache
34
35 from sfa.storage.record import Record
36
37 from sfa.rspecs.rspec import RSpec
38 from sfa.rspecs.rspec_converter import RSpecConverter
39 from sfa.rspecs.version_manager import VersionManager
40
41 from sfa.client.sfaclientlib import SfaClientBootstrap
42 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
43 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
44 from sfa.client.return_value import ReturnValue
45 from sfa.client.candidates import Candidates
46 from sfa.client.manifolduploader import ManifoldUploader
47
48 CM_PORT=12346
49
50 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
51     terminal_render, filter_records 
52
53 # display methods
54 def display_rspec(rspec, format='rspec'):
55     if format in ['dns']:
56         tree = etree.parse(StringIO(rspec))
57         root = tree.getroot()
58         result = root.xpath("./network/site/node/hostname/text()")
59     elif format in ['ip']:
60         # The IP address is not yet part of the new RSpec
61         # so this doesn't do anything yet.
62         tree = etree.parse(StringIO(rspec))
63         root = tree.getroot()
64         result = root.xpath("./network/site/node/ipv4/text()")
65     else:
66         result = rspec
67
68     print result
69     return
70
71 def display_list(results):
72     for result in results:
73         print result
74
75 def display_records(recordList, dump=False):
76     ''' Print all fields in the record'''
77     for record in recordList:
78         display_record(record, dump)
79
80 def display_record(record, dump=False):
81     if dump:
82         record.dump(sort=True)
83     else:
84         info = record.getdict()
85         print "%s (%s)" % (info['hrn'], info['type'])
86     return
87
88
89 def credential_printable (credential_string):
90     credential=Credential(string=credential_string)
91     result=""
92     result += credential.get_summary_tostring()
93     result += "\n"
94     rights = credential.get_privileges()
95     result += "rights=%s"%rights
96     result += "\n"
97     return result
98
99 def show_credentials (cred_s):
100     if not isinstance (cred_s,list): cred_s = [cred_s]
101     for cred in cred_s:
102         print "Using Credential %s"%credential_printable(cred)
103
104 # save methods
105 def save_raw_to_file(var, filename, format="text", banner=None):
106     if filename == "-":
107         # if filename is "-", send it to stdout
108         f = sys.stdout
109     else:
110         f = open(filename, "w")
111     if banner:
112         f.write(banner+"\n")
113     if format == "text":
114         f.write(str(var))
115     elif format == "pickled":
116         f.write(pickle.dumps(var))
117     elif format == "json":
118         if hasattr(json, "dumps"):
119             f.write(json.dumps(var))   # python 2.6
120         else:
121             f.write(json.write(var))   # python 2.5
122     else:
123         # this should never happen
124         print "unknown output format", format
125     if banner:
126         f.write('\n'+banner+"\n")
127
128 def save_rspec_to_file(rspec, filename):
129     if not filename.endswith(".rspec"):
130         filename = filename + ".rspec"
131     f = open(filename, 'w')
132     f.write(rspec)
133     f.close()
134     return
135
136 def save_records_to_file(filename, record_dicts, format="xml"):
137     if format == "xml":
138         index = 0
139         for record_dict in record_dicts:
140             if index > 0:
141                 save_record_to_file(filename + "." + str(index), record_dict)
142             else:
143                 save_record_to_file(filename, record_dict)
144             index = index + 1
145     elif format == "xmllist":
146         f = open(filename, "w")
147         f.write("<recordlist>\n")
148         for record_dict in record_dicts:
149             record_obj=Record(dict=record_dict)
150             f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
151         f.write("</recordlist>\n")
152         f.close()
153     elif format == "hrnlist":
154         f = open(filename, "w")
155         for record_dict in record_dicts:
156             record_obj=Record(dict=record_dict)
157             f.write(record_obj.hrn + "\n")
158         f.close()
159     else:
160         # this should never happen
161         print "unknown output format", format
162
163 def save_record_to_file(filename, record_dict):
164     record = Record(dict=record_dict)
165     xml = record.save_as_xml()
166     f=codecs.open(filename, encoding='utf-8',mode="w")
167     f.write(xml)
168     f.close()
169     return
170
171 # minimally check a key argument
172 def check_ssh_key (key):
173     good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
174     return re.match(good_ssh_key, key, re.IGNORECASE)
175
176 # load methods
177 def load_record_from_opts(options):
178     record_dict = {}
179     if hasattr(options, 'xrn') and options.xrn:
180         if hasattr(options, 'type') and options.type:
181             xrn = Xrn(options.xrn, options.type)
182         else:
183             xrn = Xrn(options.xrn)
184         record_dict['urn'] = xrn.get_urn()
185         record_dict['hrn'] = xrn.get_hrn()
186         record_dict['type'] = xrn.get_type()
187     if hasattr(options, 'key') and options.key:
188         try:
189             pubkey = open(options.key, 'r').read()
190         except IOError:
191             pubkey = options.key
192         if not check_ssh_key (pubkey):
193             raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
194         record_dict['keys'] = [pubkey]
195     if hasattr(options, 'slices') and options.slices:
196         record_dict['slices'] = options.slices
197     if hasattr(options, 'researchers') and options.researchers:
198         record_dict['researcher'] = options.researchers
199     if hasattr(options, 'email') and options.email:
200         record_dict['email'] = options.email
201     if hasattr(options, 'pis') and options.pis:
202         record_dict['pi'] = options.pis
203
204     # handle extra settings
205     record_dict.update(options.extras)
206     
207     return Record(dict=record_dict)
208
209 def load_record_from_file(filename):
210     f=codecs.open(filename, encoding="utf-8", mode="r")
211     xml_string = f.read()
212     f.close()
213     return Record(xml=xml_string)
214
215
216 import uuid
217 def unique_call_id(): return uuid.uuid4().urn
218
219 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
220 # essentially for the methods that implement a subcommand like sfi list
221 # we need to keep track of
222 # (*) doc         a few lines that tell what it does, still located in __doc__
223 # (*) args_string a simple one-liner that describes mandatory arguments
224 # (*) example     well, one or several releant examples
225
226 # since __doc__ only accounts for one, we use this simple mechanism below
227 # however we keep doc in place for easier migration
228
229 from functools import wraps
230
231 # we use a list as well as a dict so we can keep track of the order
232 commands_list=[]
233 commands_dict={}
234
235 def register_command (args_string, example):
236     def wrap(m): 
237         name=getattr(m,'__name__')
238         doc=getattr(m,'__doc__',"-- missing doc --")
239         doc=doc.strip(" \t\n")
240         commands_list.append(name)
241         commands_dict[name]=(doc, args_string, example)
242         @wraps(m)
243         def new_method (*args, **kwds): return m(*args, **kwds)
244         return new_method
245     return wrap
246
247 ##########
248
249 class Sfi:
250     
251     # dirty hack to make this class usable from the outside
252     required_options=['verbose',  'debug',  'registry',  'sm',  'auth',  'user', 'user_private_key']
253
254     @staticmethod
255     def default_sfi_dir ():
256         if os.path.isfile("./sfi_config"): 
257             return os.getcwd()
258         else:
259             return os.path.expanduser("~/.sfi/")
260
261     # dummy to meet Sfi's expectations for its 'options' field
262     # i.e. s/t we can do setattr on
263     class DummyOptions:
264         pass
265
266     def __init__ (self,options=None):
267         if options is None: options=Sfi.DummyOptions()
268         for opt in Sfi.required_options:
269             if not hasattr(options,opt): setattr(options,opt,None)
270         if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
271         self.options = options
272         self.user = None
273         self.authority = None
274         self.logger = sfi_logger
275         self.logger.enable_console()
276         self.command=None
277         self.config=None
278         self.config_file=None
279
280     ### suitable if no reasonable command has been provided
281     def print_commands_help (self, options):
282         verbose=getattr(options,'verbose')
283         format3="%18s %-15s %s"
284         line=80*'-'
285         if not verbose:
286             print format3%("command","cmd_args","description")
287             print line
288         else:
289             print line
290             self.create_parser().print_help()
291         # preserve order from the code
292         for command in commands_list:
293             (doc, args_string, example) = commands_dict[command]
294             if verbose:
295                 print line
296             doc=doc.replace("\n","\n"+35*' ')
297             print format3%(command,args_string,doc)
298             if verbose:
299                 self.create_command_parser(command).print_help()
300             
301     ### now if a known command was found we can be more verbose on that one
302     def print_help (self):
303         print "==================== Generic sfi usage"
304         self.sfi_parser.print_help()
305         (doc,_,example)=commands_dict[self.command]
306         print "\n==================== Purpose of %s"%self.command
307         print doc
308         print "\n==================== Specific usage for %s"%self.command
309         self.command_parser.print_help()
310         if example:
311             print "\n==================== %s example(s)"%self.command
312             print example
313
314     def create_command_parser(self, command):
315         if command not in commands_dict:
316             msg="Invalid command\n"
317             msg+="Commands: "
318             msg += ','.join(commands_list)            
319             self.logger.critical(msg)
320             sys.exit(2)
321
322         # retrieve args_string
323         (_, args_string, __) = commands_dict[command]
324
325         parser = OptionParser(add_help_option=False,
326                               usage="sfi [sfi_options] %s [cmd_options] %s"
327                               % (command, args_string))
328         parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
329                            help="Summary of one command usage")
330
331         if command in ("add", "update"):
332             parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
333             parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
334             parser.add_option('-e', '--email', dest='email', default="",  help="email (mandatory for users)") 
335             parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file', 
336                               default=None)
337             parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
338                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
339             parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>', 
340                               help='Set/replace slice researchers', default='', type="str", action='callback', 
341                               callback=optparse_listvalue_callback)
342             parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
343                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
344             parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
345                                action="callback", callback=optparse_dictvalue_callback, nargs=1,
346                                help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
347
348         # show_credential option
349         if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
350             parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
351                               help="show credential(s) used in human-readable form")
352         # registy filter option
353         if command in ("list", "show", "remove"):
354             parser.add_option("-t", "--type", dest="type", type="choice",
355                             help="type filter ([all]|user|slice|authority|node|aggregate)",
356                             choices=("all", "user", "slice", "authority", "node", "aggregate"),
357                             default="all")
358         if command in ("show"):
359             parser.add_option("-k","--key",dest="keys",action="append",default=[],
360                               help="specify specific keys to be displayed from record")
361         if command in ("resources"):
362             # rspec version
363             parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
364                               help="schema type and version of resulting RSpec")
365             # disable/enable cached rspecs
366             parser.add_option("-c", "--current", dest="current", default=False,
367                               action="store_true",  
368                               help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
369             # display formats
370             parser.add_option("-f", "--format", dest="format", type="choice",
371                              help="display format ([xml]|dns|ip)", default="xml",
372                              choices=("xml", "dns", "ip"))
373             #panos: a new option to define the type of information about resources a user is interested in
374             parser.add_option("-i", "--info", dest="info",
375                                 help="optional component information", default=None)
376             # a new option to retreive or not reservation-oriented RSpecs (leases)
377             parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
378                                 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
379                                 choices=("all", "resources", "leases"), default="resources")
380
381
382         # 'create' does return the new rspec, makes sense to save that too
383         if command in ("resources", "show", "list", "gid", 'create'):
384            parser.add_option("-o", "--output", dest="file",
385                             help="output XML to file", metavar="FILE", default=None)
386
387         if command in ("show", "list"):
388            parser.add_option("-f", "--format", dest="format", type="choice",
389                              help="display format ([text]|xml)", default="text",
390                              choices=("text", "xml"))
391
392            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
393                              help="output file format ([xml]|xmllist|hrnlist)", default="xml",
394                              choices=("xml", "xmllist", "hrnlist"))
395         if command == 'list':
396            parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
397                              help="list all child records", default=False)
398            parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
399                              help="gives details, like user keys", default=False)
400         if command in ("delegate"):
401            parser.add_option("-u", "--user",
402                              action="store_true", dest="delegate_user", default=False,
403                              help="delegate your own credentials; default if no other option is provided")
404            parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
405                              metavar="slice_hrn", help="delegate cred. for slice HRN")
406            parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
407                              metavar='auth_hrn', help="delegate cred for auth HRN")
408            # this primarily is a shorthand for -a my_hrn^
409            parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
410                              help="delegate your PI credentials, so s.t. like -a your_hrn^")
411            parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
412                              help="""by default the mandatory argument is expected to be a user, 
413 use this if you mean an authority instead""")
414         
415         if command in ("version"):
416             parser.add_option("-R","--registry-version",
417                               action="store_true", dest="version_registry", default=False,
418                               help="probe registry version instead of sliceapi")
419             parser.add_option("-l","--local",
420                               action="store_true", dest="version_local", default=False,
421                               help="display version of the local client")
422
423         return parser
424
425         
426     def create_parser(self):
427
428         # Generate command line parser
429         parser = OptionParser(add_help_option=False,
430                               usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
431                               description="Commands: %s"%(" ".join(commands_list)))
432         parser.add_option("-r", "--registry", dest="registry",
433                          help="root registry", metavar="URL", default=None)
434         parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
435                          help="slice API - in general a SM URL, but can be used to talk to an aggregate")
436         parser.add_option("-R", "--raw", dest="raw", default=None,
437                           help="Save raw, unparsed server response to a file")
438         parser.add_option("", "--rawformat", dest="rawformat", type="choice",
439                           help="raw file format ([text]|pickled|json)", default="text",
440                           choices=("text","pickled","json"))
441         parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
442                           help="text string to write before and after raw output")
443         parser.add_option("-d", "--dir", dest="sfi_dir",
444                          help="config & working directory - default is %default",
445                          metavar="PATH", default=Sfi.default_sfi_dir())
446         parser.add_option("-u", "--user", dest="user",
447                          help="user name", metavar="HRN", default=None)
448         parser.add_option("-a", "--auth", dest="auth",
449                          help="authority name", metavar="HRN", default=None)
450         parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
451                          help="verbose mode - cumulative")
452         parser.add_option("-D", "--debug",
453                           action="store_true", dest="debug", default=False,
454                           help="Debug (xml-rpc) protocol messages")
455         # would it make sense to use ~/.ssh/id_rsa as a default here ?
456         parser.add_option("-k", "--private-key",
457                          action="store", dest="user_private_key", default=None,
458                          help="point to the private key file to use if not yet installed in sfi_dir")
459         parser.add_option("-t", "--timeout", dest="timeout", default=None,
460                          help="Amout of time to wait before timing out the request")
461         parser.add_option("-h", "--help", 
462                          action="store_true", dest="help", default=False,
463                          help="one page summary on commands & exit")
464         parser.disable_interspersed_args()
465
466         return parser
467         
468
469     #
470     # Main: parse arguments and dispatch to command
471     #
472     def dispatch(self, command, command_options, command_args):
473         method=getattr(self, command, None)
474         if not method:
475             print "Unknown command %s"%command
476             return
477         return method(command_options, command_args)
478
479     def main(self):
480         self.sfi_parser = self.create_parser()
481         (options, args) = self.sfi_parser.parse_args()
482         if options.help: 
483             self.print_commands_help(options)
484             sys.exit(1)
485         self.options = options
486
487         self.logger.setLevelFromOptVerbose(self.options.verbose)
488
489         if len(args) <= 0:
490             self.logger.critical("No command given. Use -h for help.")
491             self.print_commands_help(options)
492             return -1
493     
494         # complete / find unique match with command set
495         command_candidates = Candidates (commands_list)
496         input = args[0]
497         command = command_candidates.only_match(input)
498         if not command:
499             self.print_commands_help(options)
500             sys.exit(1)
501         # second pass options parsing
502         self.command=command
503         self.command_parser = self.create_command_parser(command)
504         (command_options, command_args) = self.command_parser.parse_args(args[1:])
505         if command_options.help:
506             self.print_help()
507             sys.exit(1)
508         self.command_options = command_options
509
510         self.read_config () 
511         self.bootstrap ()
512         self.logger.debug("Command=%s" % self.command)
513
514         try:
515             self.dispatch(command, command_options, command_args)
516         except SystemExit:
517             return 1
518         except:
519             self.logger.log_exc ("sfi command %s failed"%command)
520             return 1
521
522         return 0
523     
524     ####################
525     def read_config(self):
526         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
527         shell_config_file  = os.path.join(self.options.sfi_dir,"sfi_config.sh")
528         try:
529             if Config.is_ini(config_file):
530                 config = Config (config_file)
531             else:
532                 # try upgrading from shell config format
533                 fp, fn = mkstemp(suffix='sfi_config', text=True)  
534                 config = Config(fn)
535                 # we need to preload the sections we want parsed 
536                 # from the shell config
537                 config.add_section('sfi')
538                 # sface users should be able to use this same file to configure their stuff
539                 config.add_section('sface')
540                 # manifold users should be able to specify the details 
541                 # of their backend server here for 'sfi myslice'
542                 config.add_section('myslice')
543                 config.load(config_file)
544                 # back up old config
545                 shutil.move(config_file, shell_config_file)
546                 # write new config
547                 config.save(config_file)
548                  
549         except:
550             self.logger.critical("Failed to read configuration file %s"%config_file)
551             self.logger.info("Make sure to remove the export clauses and to add quotes")
552             if self.options.verbose==0:
553                 self.logger.info("Re-run with -v for more details")
554             else:
555                 self.logger.log_exc("Could not read config file %s"%config_file)
556             sys.exit(1)
557      
558         self.config=config
559         errors = 0
560         # Set SliceMgr URL
561         if (self.options.sm is not None):
562            self.sm_url = self.options.sm
563         elif hasattr(config, "SFI_SM"):
564            self.sm_url = config.SFI_SM
565         else:
566            self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
567            errors += 1 
568
569         # Set Registry URL
570         if (self.options.registry is not None):
571            self.reg_url = self.options.registry
572         elif hasattr(config, "SFI_REGISTRY"):
573            self.reg_url = config.SFI_REGISTRY
574         else:
575            self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
576            errors += 1 
577
578         # Set user HRN
579         if (self.options.user is not None):
580            self.user = self.options.user
581         elif hasattr(config, "SFI_USER"):
582            self.user = config.SFI_USER
583         else:
584            self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
585            errors += 1 
586
587         # Set authority HRN
588         if (self.options.auth is not None):
589            self.authority = self.options.auth
590         elif hasattr(config, "SFI_AUTH"):
591            self.authority = config.SFI_AUTH
592         else:
593            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
594            errors += 1 
595
596         self.config_file=config_file
597         if errors:
598            sys.exit(1)
599
600     def show_config (self):
601         print "From configuration file %s"%self.config_file
602         flags=[ 
603             ('SFI_USER','user'),
604             ('SFI_AUTH','authority'),
605             ('SFI_SM','sm_url'),
606             ('SFI_REGISTRY','reg_url'),
607             ]
608         for (external_name, internal_name) in flags:
609             print "%s='%s'"%(external_name,getattr(self,internal_name))
610
611     #
612     # Get various credential and spec files
613     #
614     # Establishes limiting conventions
615     #   - conflates MAs and SAs
616     #   - assumes last token in slice name is unique
617     #
618     # Bootstraps credentials
619     #   - bootstrap user credential from self-signed certificate
620     #   - bootstrap authority credential from user credential
621     #   - bootstrap slice credential from user credential
622     #
623     
624     # init self-signed cert, user credentials and gid
625     def bootstrap (self):
626         client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
627                                                logger=self.logger)
628         # if -k is provided, use this to initialize private key
629         if self.options.user_private_key:
630             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
631         else:
632             # trigger legacy compat code if needed 
633             # the name has changed from just <leaf>.pkey to <hrn>.pkey
634             if not os.path.isfile(client_bootstrap.private_key_filename()):
635                 self.logger.info ("private key not found, trying legacy name")
636                 try:
637                     legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
638                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
639                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
640                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
641                 except:
642                     self.logger.log_exc("Can't find private key ")
643                     sys.exit(1)
644             
645         # make it bootstrap
646         client_bootstrap.bootstrap_my_gid()
647         # extract what's needed
648         self.private_key = client_bootstrap.private_key()
649         self.my_credential_string = client_bootstrap.my_credential_string ()
650         self.my_gid = client_bootstrap.my_gid ()
651         self.client_bootstrap = client_bootstrap
652
653
654     def my_authority_credential_string(self):
655         if not self.authority:
656             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
657             sys.exit(-1)
658         return self.client_bootstrap.authority_credential_string (self.authority)
659
660     def authority_credential_string(self, auth_hrn):
661         return self.client_bootstrap.authority_credential_string (auth_hrn)
662
663     def slice_credential_string(self, name):
664         return self.client_bootstrap.slice_credential_string (name)
665
666     #
667     # Management of the servers
668     # 
669
670     def registry (self):
671         # cache the result
672         if not hasattr (self, 'registry_proxy'):
673             self.logger.info("Contacting Registry at: %s"%self.reg_url)
674             self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
675                                                  timeout=self.options.timeout, verbose=self.options.debug)  
676         return self.registry_proxy
677
678     def sliceapi (self):
679         # cache the result
680         if not hasattr (self, 'sliceapi_proxy'):
681             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
682             if hasattr(self.command_options,'component') and self.command_options.component:
683                 # resolve the hrn at the registry
684                 node_hrn = self.command_options.component
685                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
686                 records = filter_records('node', records)
687                 if not records:
688                     self.logger.warning("No such component:%r"% opts.component)
689                 record = records[0]
690                 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
691                 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
692             else:
693                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
694                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
695                     self.sm_url = 'http://' + self.sm_url
696                 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
697                 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
698                                                      timeout=self.options.timeout, verbose=self.options.debug)  
699         return self.sliceapi_proxy
700
701     def get_cached_server_version(self, server):
702         # check local cache first
703         cache = None
704         version = None 
705         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
706         cache_key = server.url + "-version"
707         try:
708             cache = Cache(cache_file)
709         except IOError:
710             cache = Cache()
711             self.logger.info("Local cache not found at: %s" % cache_file)
712
713         if cache:
714             version = cache.get(cache_key)
715
716         if not version: 
717             result = server.GetVersion()
718             version= ReturnValue.get_value(result)
719             # cache version for 20 minutes
720             cache.add(cache_key, version, ttl= 60*20)
721             self.logger.info("Updating cache file %s" % cache_file)
722             cache.save_to_file(cache_file)
723
724         return version   
725         
726     ### resurrect this temporarily so we can support V1 aggregates for a while
727     def server_supports_options_arg(self, server):
728         """
729         Returns true if server support the optional call_id arg, false otherwise. 
730         """
731         server_version = self.get_cached_server_version(server)
732         result = False
733         # xxx need to rewrite this 
734         if int(server_version.get('geni_api')) >= 2:
735             result = True
736         return result
737
738     def server_supports_call_id_arg(self, server):
739         server_version = self.get_cached_server_version(server)
740         result = False      
741         if 'sfa' in server_version and 'code_tag' in server_version:
742             code_tag = server_version['code_tag']
743             code_tag_parts = code_tag.split("-")
744             version_parts = code_tag_parts[0].split(".")
745             major, minor = version_parts[0], version_parts[1]
746             rev = code_tag_parts[1]
747             if int(major) == 1 and minor == 0 and build >= 22:
748                 result = True
749         return result                 
750
751     ### ois = options if supported
752     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
753     def ois (self, server, option_dict):
754         if self.server_supports_options_arg (server): 
755             return [option_dict]
756         elif self.server_supports_call_id_arg (server):
757             return [ unique_call_id () ]
758         else: 
759             return []
760
761     ### cis = call_id if supported - like ois
762     def cis (self, server):
763         if self.server_supports_call_id_arg (server):
764             return [ unique_call_id ]
765         else:
766             return []
767
768     ######################################## miscell utilities
769     def get_rspec_file(self, rspec):
770        if (os.path.isabs(rspec)):
771           file = rspec
772        else:
773           file = os.path.join(self.options.sfi_dir, rspec)
774        if (os.path.isfile(file)):
775           return file
776        else:
777           self.logger.critical("No such rspec file %s"%rspec)
778           sys.exit(1)
779     
780     def get_record_file(self, record):
781        if (os.path.isabs(record)):
782           file = record
783        else:
784           file = os.path.join(self.options.sfi_dir, record)
785        if (os.path.isfile(file)):
786           return file
787        else:
788           self.logger.critical("No such registry record file %s"%record)
789           sys.exit(1)
790
791
792     #==========================================================================
793     # Following functions implement the commands
794     #
795     # Registry-related commands
796     #==========================================================================
797
798     @register_command("","")
799     def config (self, options, args):
800         "Display contents of current config"
801         self.show_config()
802
803     @register_command("","")
804     def version(self, options, args):
805         """
806         display an SFA server version (GetVersion)
807   or version information about sfi itself
808         """
809         if options.version_local:
810             version=version_core()
811         else:
812             if options.version_registry:
813                 server=self.registry()
814             else:
815                 server = self.sliceapi()
816             result = server.GetVersion()
817             version = ReturnValue.get_value(result)
818         if self.options.raw:
819             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
820         else:
821             pprinter = PrettyPrinter(indent=4)
822             pprinter.pprint(version)
823
824     @register_command("authority","")
825     def list(self, options, args):
826         """
827         list entries in named authority registry (List)
828         """
829         if len(args)!= 1:
830             self.print_help()
831             sys.exit(1)
832         hrn = args[0]
833         opts = {}
834         if options.recursive:
835             opts['recursive'] = options.recursive
836         
837         if options.show_credential:
838             show_credentials(self.my_credential_string)
839         try:
840             list = self.registry().List(hrn, self.my_credential_string, options)
841         except IndexError:
842             raise Exception, "Not enough parameters for the 'list' command"
843
844         # filter on person, slice, site, node, etc.
845         # This really should be in the self.filter_records funct def comment...
846         list = filter_records(options.type, list)
847         terminal_render (list, options)
848         if options.file:
849             save_records_to_file(options.file, list, options.fileformat)
850         return
851     
852     @register_command("name","")
853     def show(self, options, args):
854         """
855         show details about named registry record (Resolve)
856         """
857         if len(args)!= 1:
858             self.print_help()
859             sys.exit(1)
860         hrn = args[0]
861         # explicitly require Resolve to run in details mode
862         record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
863         record_dicts = filter_records(options.type, record_dicts)
864         if not record_dicts:
865             self.logger.error("No record of type %s"% options.type)
866             return
867         # user has required to focus on some keys
868         if options.keys:
869             def project (record):
870                 projected={}
871                 for key in options.keys:
872                     try: projected[key]=record[key]
873                     except: pass
874                 return projected
875             record_dicts = [ project (record) for record in record_dicts ]
876         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
877         for record in records:
878             if (options.format == "text"):      record.dump(sort=True)  
879             else:                               print record.save_as_xml() 
880         if options.file:
881             save_records_to_file(options.file, record_dicts, options.fileformat)
882         return
883     
884     @register_command("[xml-filename]","")
885     def add(self, options, args):
886         """add record into registry (Register) 
887   from command line options (recommended) 
888   old-school method involving an xml file still supported"""
889
890         auth_cred = self.my_authority_credential_string()
891         if options.show_credential:
892             show_credentials(auth_cred)
893         record_dict = {}
894         if len(args) > 1:
895             self.print_help()
896             sys.exit(1)
897         if len(args)==1:
898             try:
899                 record_filepath = args[0]
900                 rec_file = self.get_record_file(record_filepath)
901                 record_dict.update(load_record_from_file(rec_file).todict())
902             except:
903                 print "Cannot load record file %s"%record_filepath
904                 sys.exit(1)
905         if options:
906             record_dict.update(load_record_from_opts(options).todict())
907         # we should have a type by now
908         if 'type' not in record_dict :
909             self.print_help()
910             sys.exit(1)
911         # this is still planetlab dependent.. as plc will whine without that
912         # also, it's only for adding
913         if record_dict['type'] == 'user':
914             if not 'first_name' in record_dict:
915                 record_dict['first_name'] = record_dict['hrn']
916             if 'last_name' not in record_dict:
917                 record_dict['last_name'] = record_dict['hrn'] 
918         return self.registry().Register(record_dict, auth_cred)
919     
920     @register_command("[xml-filename]","")
921     def update(self, options, args):
922         """update record into registry (Update) 
923   from command line options (recommended) 
924   old-school method involving an xml file still supported"""
925         record_dict = {}
926         if len(args) > 0:
927             record_filepath = args[0]
928             rec_file = self.get_record_file(record_filepath)
929             record_dict.update(load_record_from_file(rec_file).todict())
930         if options:
931             record_dict.update(load_record_from_opts(options).todict())
932         # at the very least we need 'type' here
933         if 'type' not in record_dict:
934             self.print_help()
935             sys.exit(1)
936
937         # don't translate into an object, as this would possibly distort
938         # user-provided data; e.g. add an 'email' field to Users
939         if record_dict['type'] == "user":
940             if record_dict['hrn'] == self.user:
941                 cred = self.my_credential_string
942             else:
943                 cred = self.my_authority_credential_string()
944         elif record_dict['type'] in ["slice"]:
945             try:
946                 cred = self.slice_credential_string(record_dict['hrn'])
947             except ServerException, e:
948                # XXX smbaker -- once we have better error return codes, update this
949                # to do something better than a string compare
950                if "Permission error" in e.args[0]:
951                    cred = self.my_authority_credential_string()
952                else:
953                    raise
954         elif record_dict['type'] in ["authority"]:
955             cred = self.my_authority_credential_string()
956         elif record_dict['type'] == 'node':
957             cred = self.my_authority_credential_string()
958         else:
959             raise "unknown record type" + record_dict['type']
960         if options.show_credential:
961             show_credentials(cred)
962         return self.registry().Update(record_dict, cred)
963   
964     @register_command("name","")
965     def remove(self, options, args):
966         "remove registry record by name (Remove)"
967         auth_cred = self.my_authority_credential_string()
968         if len(args)!=1:
969             self.print_help()
970             sys.exit(1)
971         hrn = args[0]
972         type = options.type 
973         if type in ['all']:
974             type = '*'
975         if options.show_credential:
976             show_credentials(auth_cred)
977         return self.registry().Remove(hrn, auth_cred, type)
978     
979     # ==================================================================
980     # Slice-related commands
981     # ==================================================================
982
983     @register_command("","")
984     def slices(self, options, args):
985         "list instantiated slices (ListSlices) - returns urn's"
986         server = self.sliceapi()
987         # creds
988         creds = [self.my_credential_string]
989         # options and call_id when supported
990         api_options = {}
991         api_options['call_id']=unique_call_id()
992         if options.show_credential:
993             show_credentials(creds)
994         result = server.ListSlices(creds, *self.ois(server,api_options))
995         value = ReturnValue.get_value(result)
996         if self.options.raw:
997             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
998         else:
999             display_list(value)
1000         return
1001
1002     # show rspec for named slice
1003     @register_command("[slice_hrn]","")
1004     def resources(self, options, args):
1005         """
1006         with no arg, discover available resources, (ListResources)
1007   or with an slice hrn, shows currently provisioned resources
1008         """
1009         server = self.sliceapi()
1010
1011         # set creds
1012         creds = []
1013         if args:
1014             the_credential=self.slice_credential_string(args[0])
1015             creds.append(the_credential)
1016         else:
1017             the_credential=self.my_credential_string
1018             creds.append(the_credential)
1019         if options.show_credential:
1020             show_credentials(creds)
1021
1022         # no need to check if server accepts the options argument since the options has
1023         # been a required argument since v1 API
1024         api_options = {}
1025         # always send call_id to v2 servers
1026         api_options ['call_id'] = unique_call_id()
1027         # ask for cached value if available
1028         api_options ['cached'] = True
1029         if args:
1030             hrn = args[0]
1031             api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
1032         if options.info:
1033             api_options['info'] = options.info
1034         if options.list_leases:
1035             api_options['list_leases'] = options.list_leases
1036         if options.current:
1037             if options.current == True:
1038                 api_options['cached'] = False
1039             else:
1040                 api_options['cached'] = True
1041         if options.rspec_version:
1042             version_manager = VersionManager()
1043             server_version = self.get_cached_server_version(server)
1044             if 'sfa' in server_version:
1045                 # just request the version the client wants
1046                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1047             else:
1048                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1049         else:
1050             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1051         result = server.ListResources (creds, api_options)
1052         value = ReturnValue.get_value(result)
1053         if self.options.raw:
1054             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1055         if options.file is not None:
1056             save_rspec_to_file(value, options.file)
1057         if (self.options.raw is None) and (options.file is None):
1058             display_rspec(value, options.format)
1059
1060         return
1061
1062     @register_command("slice_hrn rspec","")
1063     def create(self, options, args):
1064         """
1065         create or update named slice with given rspec (CreateSliver)
1066         """
1067         server = self.sliceapi()
1068
1069         # xxx do we need to check usage (len(args)) ?
1070         # slice urn
1071         slice_hrn = args[0]
1072         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1073
1074         # credentials
1075         creds = [self.slice_credential_string(slice_hrn)]
1076
1077         delegated_cred = None
1078         server_version = self.get_cached_server_version(server)
1079         if server_version.get('interface') == 'slicemgr':
1080             # delegate our cred to the slice manager
1081             # do not delegate cred to slicemgr...not working at the moment
1082             pass
1083             #if server_version.get('hrn'):
1084             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1085             #elif server_version.get('urn'):
1086             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1087
1088         if options.show_credential:
1089             show_credentials(creds)
1090
1091         # rspec
1092         rspec_file = self.get_rspec_file(args[1])
1093         rspec = open(rspec_file).read()
1094
1095         # users
1096         # need to pass along user keys to the aggregate.
1097         # users = [
1098         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1099         #    keys: [<ssh key A>, <ssh key B>]
1100         #  }]
1101         users = []
1102         # xxx Thierry 2012 sept. 21
1103         # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here
1104         # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again
1105         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1106         # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True})
1107         if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']:
1108             slice_record = slice_records[0]
1109             user_hrns = slice_record['reg-researchers']
1110             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1111             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1112
1113             if 'sfa' not in server_version:
1114                 users = pg_users_arg(user_records)
1115                 rspec = RSpec(rspec)
1116                 rspec.filter({'component_manager_id': server_version['urn']})
1117                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1118             else:
1119                 users = sfa_users_arg(user_records, slice_record)
1120
1121         # do not append users, keys, or slice tags. Anything
1122         # not contained in this request will be removed from the slice
1123
1124         # CreateSliver has supported the options argument for a while now so it should
1125         # be safe to assume this server support it
1126         api_options = {}
1127         api_options ['append'] = False
1128         api_options ['call_id'] = unique_call_id()
1129         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1130         value = ReturnValue.get_value(result)
1131         if self.options.raw:
1132             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1133         if options.file is not None:
1134             save_rspec_to_file (value, options.file)
1135         if (self.options.raw is None) and (options.file is None):
1136             print value
1137
1138         return value
1139
1140     @register_command("slice_hrn","")
1141     def delete(self, options, args):
1142         """
1143         delete named slice (DeleteSliver)
1144         """
1145         server = self.sliceapi()
1146
1147         # slice urn
1148         slice_hrn = args[0]
1149         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1150
1151         # creds
1152         slice_cred = self.slice_credential_string(slice_hrn)
1153         creds = [slice_cred]
1154         
1155         # options and call_id when supported
1156         api_options = {}
1157         api_options ['call_id'] = unique_call_id()
1158         if options.show_credential:
1159             show_credentials(creds)
1160         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1161         value = ReturnValue.get_value(result)
1162         if self.options.raw:
1163             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1164         else:
1165             print value
1166         return value 
1167   
1168     @register_command("slice_hrn","")
1169     def status(self, options, args):
1170         """
1171         retrieve slice status (SliverStatus)
1172         """
1173         server = self.sliceapi()
1174
1175         # slice urn
1176         slice_hrn = args[0]
1177         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1178
1179         # creds 
1180         slice_cred = self.slice_credential_string(slice_hrn)
1181         creds = [slice_cred]
1182
1183         # options and call_id when supported
1184         api_options = {}
1185         api_options['call_id']=unique_call_id()
1186         if options.show_credential:
1187             show_credentials(creds)
1188         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1189         value = ReturnValue.get_value(result)
1190         if self.options.raw:
1191             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1192         else:
1193             print value
1194
1195     @register_command("slice_hrn","")
1196     def start(self, options, args):
1197         """
1198         start named slice (Start)
1199         """
1200         server = self.sliceapi()
1201
1202         # the slice urn
1203         slice_hrn = args[0]
1204         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1205         
1206         # cred
1207         slice_cred = self.slice_credential_string(args[0])
1208         creds = [slice_cred]
1209         # xxx Thierry - does this not need an api_options as well ?
1210         result = server.Start(slice_urn, creds)
1211         value = ReturnValue.get_value(result)
1212         if self.options.raw:
1213             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1214         else:
1215             print value
1216         return value
1217     
1218     @register_command("slice_hrn","")
1219     def stop(self, options, args):
1220         """
1221         stop named slice (Stop)
1222         """
1223         server = self.sliceapi()
1224         # slice urn
1225         slice_hrn = args[0]
1226         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1227         # cred
1228         slice_cred = self.slice_credential_string(args[0])
1229         creds = [slice_cred]
1230         result =  server.Stop(slice_urn, creds)
1231         value = ReturnValue.get_value(result)
1232         if self.options.raw:
1233             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1234         else:
1235             print value
1236         return value
1237     
1238     # reset named slice
1239     @register_command("slice_hrn","")
1240     def reset(self, options, args):
1241         """
1242         reset named slice (reset_slice)
1243         """
1244         server = self.sliceapi()
1245         # slice urn
1246         slice_hrn = args[0]
1247         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1248         # cred
1249         slice_cred = self.slice_credential_string(args[0])
1250         creds = [slice_cred]
1251         result = server.reset_slice(creds, slice_urn)
1252         value = ReturnValue.get_value(result)
1253         if self.options.raw:
1254             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1255         else:
1256             print value
1257         return value
1258
1259     @register_command("slice_hrn time","")
1260     def renew(self, options, args):
1261         """
1262         renew slice (RenewSliver)
1263         """
1264         server = self.sliceapi()
1265         if len(args) != 2:
1266             self.print_help()
1267             sys.exit(1)
1268         [ slice_hrn, input_time ] = args
1269         # slice urn    
1270         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1271         # time: don't try to be smart on the time format, server-side will
1272         # creds
1273         slice_cred = self.slice_credential_string(args[0])
1274         creds = [slice_cred]
1275         # options and call_id when supported
1276         api_options = {}
1277         api_options['call_id']=unique_call_id()
1278         if options.show_credential:
1279             show_credentials(creds)
1280         result =  server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1281         value = ReturnValue.get_value(result)
1282         if self.options.raw:
1283             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1284         else:
1285             print value
1286         return value
1287
1288
1289     @register_command("slice_hrn","")
1290     def shutdown(self, options, args):
1291         """
1292         shutdown named slice (Shutdown)
1293         """
1294         server = self.sliceapi()
1295         # slice urn
1296         slice_hrn = args[0]
1297         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1298         # creds
1299         slice_cred = self.slice_credential_string(slice_hrn)
1300         creds = [slice_cred]
1301         result = server.Shutdown(slice_urn, creds)
1302         value = ReturnValue.get_value(result)
1303         if self.options.raw:
1304             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1305         else:
1306             print value
1307         return value         
1308     
1309
1310     @register_command("slice_hrn rspec","")
1311     def get_ticket(self, options, args):
1312         """
1313         get a ticket for the specified slice
1314         """
1315         server = self.sliceapi()
1316         # slice urn
1317         slice_hrn, rspec_path = args[0], args[1]
1318         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1319         # creds
1320         slice_cred = self.slice_credential_string(slice_hrn)
1321         creds = [slice_cred]
1322         # rspec
1323         rspec_file = self.get_rspec_file(rspec_path) 
1324         rspec = open(rspec_file).read()
1325         # options and call_id when supported
1326         api_options = {}
1327         api_options['call_id']=unique_call_id()
1328         # get ticket at the server
1329         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1330         # save
1331         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1332         self.logger.info("writing ticket to %s"%file)
1333         ticket = SfaTicket(string=ticket_string)
1334         ticket.save_to_file(filename=file, save_parents=True)
1335
1336     @register_command("ticket","")
1337     def redeem_ticket(self, options, args):
1338         """
1339         Connects to nodes in a slice and redeems a ticket
1340   (slice hrn is retrieved from the ticket)
1341         """
1342         ticket_file = args[0]
1343         
1344         # get slice hrn from the ticket
1345         # use this to get the right slice credential 
1346         ticket = SfaTicket(filename=ticket_file)
1347         ticket.decode()
1348         ticket_string = ticket.save_to_string(save_parents=True)
1349
1350         slice_hrn = ticket.gidObject.get_hrn()
1351         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1352         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1353         slice_cred = self.slice_credential_string(slice_hrn)
1354         
1355         # get a list of node hostnames from the RSpec 
1356         tree = etree.parse(StringIO(ticket.rspec))
1357         root = tree.getroot()
1358         hostnames = root.xpath("./network/site/node/hostname/text()")
1359         
1360         # create an xmlrpc connection to the component manager at each of these
1361         # components and gall redeem_ticket
1362         connections = {}
1363         for hostname in hostnames:
1364             try:
1365                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1366                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1367                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1368                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1369                                            timeout=self.options.timeout, verbose=self.options.debug)
1370                 server.RedeemTicket(ticket_string, slice_cred)
1371                 self.logger.info("Success")
1372             except socket.gaierror:
1373                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1374             except Exception, e:
1375                 self.logger.log_exc(e.message)
1376         return
1377
1378     @register_command("[name]","")
1379     def gid(self, options, args):
1380         """
1381         Create a GID (CreateGid)
1382         """
1383         if len(args) < 1:
1384             self.print_help()
1385             sys.exit(1)
1386         target_hrn = args[0]
1387         my_gid_string = open(self.client_bootstrap.my_gid()).read() 
1388         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1389         if options.file:
1390             filename = options.file
1391         else:
1392             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1393         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1394         GID(string=gid).save_to_file(filename)
1395          
1396
1397     @register_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1398
1399   will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1400   the set of credentials in the scope for this call would be
1401   (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1402       as per -u/--user
1403   (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1404       as per -p/--pi
1405   (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1406   (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1407       because of the two -s options
1408
1409 """)
1410     def delegate (self, options, args):
1411         """
1412         (locally) create delegate credential for use by given hrn
1413   make sure to check for 'sfi myslice' instead if you plan
1414   on using MySlice
1415         """
1416         if len(args) != 1:
1417             self.print_help()
1418             sys.exit(1)
1419         to_hrn = args[0]
1420         # support for several delegations in the same call
1421         # so first we gather the things to do
1422         tuples=[]
1423         for slice_hrn in options.delegate_slices:
1424             message="%s.slice"%slice_hrn
1425             original = self.slice_credential_string(slice_hrn)
1426             tuples.append ( (message, original,) )
1427         if options.delegate_pi:
1428             my_authority=self.authority
1429             message="%s.pi"%my_authority
1430             original = self.my_authority_credential_string()
1431             tuples.append ( (message, original,) )
1432         for auth_hrn in options.delegate_auths:
1433             message="%s.auth"%auth_hrn
1434             original=self.authority_credential_string(auth_hrn)
1435             tuples.append ( (message, original, ) )
1436         # if nothing was specified at all at this point, let's assume -u
1437         if not tuples: options.delegate_user=True
1438         # this user cred
1439         if options.delegate_user:
1440             message="%s.user"%self.user
1441             original = self.my_credential_string
1442             tuples.append ( (message, original, ) )
1443
1444         # default type for beneficial is user unless -A
1445         if options.delegate_to_authority:       to_type='authority'
1446         else:                                   to_type='user'
1447
1448         # let's now handle all this
1449         # it's all in the filenaming scheme
1450         for (message,original) in tuples:
1451             delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1452             delegated_credential = Credential (string=delegated_string)
1453             filename = os.path.join ( self.options.sfi_dir,
1454                                       "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1455             delegated_credential.save_to_file(filename, save_parents=True)
1456             self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1457     
1458     ####################
1459     @register_command("","""$ less +/myslice sfi_config
1460 [myslice]
1461 backend  = 'http://manifold.pl.sophia.inria.fr:7080'
1462 # the HRN that myslice uses, so that we are delegating to
1463 delegate = 'ple.upmc.slicebrowser'
1464 # platform - this is a myslice concept
1465 platform = 'ple'
1466 # username - as of this writing (May 2013) a simple login name
1467 user     = 'thierry'
1468
1469 $ sfi myslice
1470
1471   will first collect the slices that you are part of, then make sure
1472   all your credentials are up-to-date (read: refresh expired ones)
1473   then compute delegated credentials for user 'ple.upmc.slicebrowser'
1474   and upload them all on myslice backend, using 'platform' and 'user'.
1475   A password will be prompted for the upload part.
1476 """
1477 ) # register_command
1478     def myslice (self, options, args):
1479
1480         """ This helper is for refreshing your credentials at myslice; it will
1481   * compute all the slices that you currently have credentials on
1482   * refresh all your credentials (you as a user and pi, your slices)
1483   * upload them to the manifold backend server
1484   for last phase, sfi_config is read to look for the [myslice] section, 
1485   and namely the 'backend', 'delegate' and 'user' settings"""
1486
1487         ##########
1488         if len(args)>0:
1489             self.print_help()
1490             sys.exit(1)
1491         # ManifoldUploader
1492         pass
1493
1494 # Thierry: I'm turning this off, no idea what it's used for
1495 #    @register_command("cred","")
1496     def trusted(self, options, args):
1497         """
1498         return the trusted certs at this interface (get_trusted_certs)
1499         """ 
1500         trusted_certs = self.registry().get_trusted_certs()
1501         for trusted_cert in trusted_certs:
1502             gid = GID(string=trusted_cert)
1503             gid.dump()
1504             cert = Certificate(string=trusted_cert)
1505             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1506         return 
1507