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