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