deprecate former 'available' list that was manually maintained
[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(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         commands_list.append(name)
249         commands_dict[name]=(doc, args_string, example)
250         @wraps(m)
251         def new_method (*args, **kwds): return m(*args, **kwds)
252         return new_method
253     return wrap
254
255 ##########
256
257 class Sfi:
258     
259     # dirty hack to make this class usable from the outside
260     required_options=['verbose',  'debug',  'registry',  'sm',  'auth',  'user', 'user_private_key']
261
262     @staticmethod
263     def default_sfi_dir ():
264         if os.path.isfile("./sfi_config"): 
265             return os.getcwd()
266         else:
267             return os.path.expanduser("~/.sfi/")
268
269     # dummy to meet Sfi's expectations for its 'options' field
270     # i.e. s/t we can do setattr on
271     class DummyOptions:
272         pass
273
274     def __init__ (self,options=None):
275         if options is None: options=Sfi.DummyOptions()
276         for opt in Sfi.required_options:
277             if not hasattr(options,opt): setattr(options,opt,None)
278         if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
279         self.options = options
280         self.user = None
281         self.authority = None
282         self.logger = sfi_logger
283         self.logger.enable_console()
284
285     def print_commands_help (self, options):
286         verbose=getattr(options,'verbose')
287         format3="%18s %-15s %s"
288         line=80*'-'
289         if not verbose:
290             print format3%("command","cmd_args","description")
291             print line
292         else:
293             print line
294             self.create_parser().print_help()
295         for (command, (doc, args_string, example)) in commands_dict.iteritems():
296             if verbose:
297                 print line
298             doc=doc.strip(" \t\n")
299             doc=doc.replace("\n","\n"+35*' ')
300             print format3%(command,args_string,doc)
301             if verbose:
302                 self.create_command_parser(command).print_help()
303
304     def create_command_parser(self, command):
305         if command not in commands_dict:
306             msg="Invalid command\n"
307             msg+="Commands: "
308             msg += ','.join(commands_list)            
309             self.logger.critical(msg)
310             sys.exit(2)
311
312         # retrieve args_string
313         (_, args_string, __) = commands_dict[command]
314
315         parser = OptionParser(add_help_option=False,
316                               usage="sfi [sfi_options] %s [cmd_options] %s"
317                               % (command, args_string))
318         parser.add_option ("-h","--help",dest='command_help',action='store_true',default=False,
319                            help="Summary of one command usage")
320
321         if command in ("add", "update"):
322             parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
323             parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
324             parser.add_option('-e', '--email', dest='email', default="",  help="email (mandatory for users)") 
325             parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file', 
326                               default=None)
327             parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
328                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
329             parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>', 
330                               help='Set/replace slice researchers', default='', type="str", action='callback', 
331                               callback=optparse_listvalue_callback)
332             parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
333                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
334             parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
335                                action="callback", callback=optparse_dictvalue_callback, nargs=1,
336                                help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
337
338         # user specifies remote aggregate/sm/component                          
339         if command in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision", 
340                        "action", "shutdown", "renew", "status"):
341             parser.add_option("-d", "--delegate", dest="delegate", default=None, 
342                              action="store_true",
343                              help="Include a credential delegated to the user's root"+\
344                                   "authority in set of credentials for this call")
345
346         # show_credential option
347         if command in ("list","resources", "describe", "provision", "allocate", "add","update","remove","slices","delete","status","renew"):
348             parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
349                               help="show credential(s) used in human-readable form")
350         # registy filter option
351         if command in ("list", "show", "remove"):
352             parser.add_option("-t", "--type", dest="type", type="choice",
353                             help="type filter ([all]|user|slice|authority|node|aggregate)",
354                             choices=("all", "user", "slice", "authority", "node", "aggregate"),
355                             default="all")
356         if command in ("show"):
357             parser.add_option("-k","--key",dest="keys",action="append",default=[],
358                               help="specify specific keys to be displayed from record")
359         if command in ("resources", "describe"):
360             # rspec version
361             parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
362                               help="schema type and version of resulting RSpec")
363             # disable/enable cached rspecs
364             parser.add_option("-c", "--current", dest="current", default=False,
365                               action="store_true",  
366                               help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
367             # display formats
368             parser.add_option("-f", "--format", dest="format", type="choice",
369                              help="display format ([xml]|dns|ip)", default="xml",
370                              choices=("xml", "dns", "ip"))
371             #panos: a new option to define the type of information about resources a user is interested in
372             parser.add_option("-i", "--info", dest="info",
373                                 help="optional component information", default=None)
374             # a new option to retreive or not reservation-oriented RSpecs (leases)
375             parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
376                                 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
377                                 choices=("all", "resources", "leases"), default="resources")
378
379
380         if command in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
381            parser.add_option("-o", "--output", dest="file",
382                             help="output XML to file", metavar="FILE", default=None)
383
384         if command in ("show", "list"):
385            parser.add_option("-f", "--format", dest="format", type="choice",
386                              help="display format ([text]|xml)", default="text",
387                              choices=("text", "xml"))
388
389            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
390                              help="output file format ([xml]|xmllist|hrnlist)", default="xml",
391                              choices=("xml", "xmllist", "hrnlist"))
392         if command == 'list':
393            parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
394                              help="list all child records", default=False)
395            parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
396                              help="gives details, like user keys", default=False)
397         if command in ("delegate"):
398            parser.add_option("-u", "--user",
399                              action="store_true", dest="delegate_user", default=False,
400                              help="delegate your own credentials; default if no other option is provided")
401            parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
402                              metavar="slice_hrn", help="delegate cred. for slice HRN")
403            parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
404                              metavar='auth_hrn', help="delegate cred for auth HRN")
405            # this primarily is a shorthand for -a my_hrn^
406            parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
407                              help="delegate your PI credentials, so s.t. like -a your_hrn^")
408            parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
409                              help="""by default the mandatory argument is expected to be a user, 
410 use this if you mean an authority instead""")
411         
412         if command in ("version"):
413             parser.add_option("-R","--registry-version",
414                               action="store_true", dest="version_registry", default=False,
415                               help="probe registry version instead of sliceapi")
416             parser.add_option("-l","--local",
417                               action="store_true", dest="version_local", default=False,
418                               help="display version of the local client")
419
420         return parser
421
422         
423     def create_parser(self):
424
425         # Generate command line parser
426         parser = OptionParser(add_help_option=False,
427                               usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
428                               description="Commands: %s"%(" ".join(commands_list)))
429         parser.add_option("-r", "--registry", dest="registry",
430                          help="root registry", metavar="URL", default=None)
431         parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
432                          help="slice API - in general a SM URL, but can be used to talk to an aggregate")
433         parser.add_option("-R", "--raw", dest="raw", default=None,
434                           help="Save raw, unparsed server response to a file")
435         parser.add_option("", "--rawformat", dest="rawformat", type="choice",
436                           help="raw file format ([text]|pickled|json)", default="text",
437                           choices=("text","pickled","json"))
438         parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
439                           help="text string to write before and after raw output")
440         parser.add_option("-d", "--dir", dest="sfi_dir",
441                          help="config & working directory - default is %default",
442                          metavar="PATH", default=Sfi.default_sfi_dir())
443         parser.add_option("-u", "--user", dest="user",
444                          help="user name", metavar="HRN", default=None)
445         parser.add_option("-a", "--auth", dest="auth",
446                          help="authority name", metavar="HRN", default=None)
447         parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
448                          help="verbose mode - cumulative")
449         parser.add_option("-D", "--debug",
450                           action="store_true", dest="debug", default=False,
451                           help="Debug (xml-rpc) protocol messages")
452         # would it make sense to use ~/.ssh/id_rsa as a default here ?
453         parser.add_option("-k", "--private-key",
454                          action="store", dest="user_private_key", default=None,
455                          help="point to the private key file to use if not yet installed in sfi_dir")
456         parser.add_option("-t", "--timeout", dest="timeout", default=None,
457                          help="Amout of time to wait before timing out the request")
458         parser.add_option("-h", "--help", 
459                          action="store_true", dest="commands_help", default=False,
460                          help="one page summary on commands & exit")
461         parser.disable_interspersed_args()
462
463         return parser
464         
465
466     def print_help (self):
467         print "==================== Generic sfi usage"
468         self.sfi_parser.print_help()
469         print "\n==================== Specific usage for %s"%self.command
470         self.command_parser.print_help()
471         (_,__,example)=commands_dict[self.command]
472         if example:
473             print "\n==================== %s example"%self.command
474             print example
475
476     #
477     # Main: parse arguments and dispatch to command
478     #
479     def dispatch(self, command, command_options, command_args):
480         method=getattr(self, command, None)
481         if not method:
482             print "Unknown command %s"%command
483             return
484         return method(command_options, command_args)
485
486     def main(self):
487         self.sfi_parser = self.create_parser()
488         (options, args) = self.sfi_parser.parse_args()
489         if options.commands_help: 
490             self.print_commands_help(options)
491             sys.exit(1)
492         self.options = options
493
494         self.logger.setLevelFromOptVerbose(self.options.verbose)
495
496         if len(args) <= 0:
497             self.logger.critical("No command given. Use -h for help.")
498             self.print_commands_help(options)
499             return -1
500     
501         # complete / find unique match with command set
502         command_candidates = Candidates (commands_list)
503         input = args[0]
504         command = command_candidates.only_match(input)
505         if not command:
506             self.print_commands_help(options)
507             sys.exit(1)
508         # second pass options parsing
509         self.command=command
510         self.command_parser = self.create_command_parser(command)
511         (command_options, command_args) = self.command_parser.parse_args(args[1:])
512         self.command_options = command_options
513
514         self.read_config () 
515         self.bootstrap ()
516         self.logger.debug("Command=%s" % self.command)
517
518         try:
519             self.dispatch(command, command_options, command_args)
520         except SystemExit:
521             return 1
522         except:
523             self.logger.log_exc ("sfi command %s failed"%command)
524             return 1
525
526         return 0
527     
528     ####################
529     def read_config(self):
530         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
531         shell_config_file  = os.path.join(self.options.sfi_dir,"sfi_config.sh")
532         try:
533             if Config.is_ini(config_file):
534                 config = Config (config_file)
535             else:
536                 # try upgrading from shell config format
537                 fp, fn = mkstemp(suffix='sfi_config', text=True)  
538                 config = Config(fn)
539                 # we need to preload the sections we want parsed 
540                 # from the shell config
541                 config.add_section('sfi')
542                 # sface users should be able to use this same file to configure their stuff
543                 config.add_section('sface')
544                 # manifold users should be able to specify their backend server here for sfi delegate
545                 config.add_section('myslice')
546                 config.load(config_file)
547                 # back up old config
548                 shutil.move(config_file, shell_config_file)
549                 # write new config
550                 config.save(config_file)
551                  
552         except:
553             self.logger.critical("Failed to read configuration file %s"%config_file)
554             self.logger.info("Make sure to remove the export clauses and to add quotes")
555             if self.options.verbose==0:
556                 self.logger.info("Re-run with -v for more details")
557             else:
558                 self.logger.log_exc("Could not read config file %s"%config_file)
559             sys.exit(1)
560      
561         errors = 0
562         # Set SliceMgr URL
563         if (self.options.sm is not None):
564            self.sm_url = self.options.sm
565         elif hasattr(config, "SFI_SM"):
566            self.sm_url = config.SFI_SM
567         else:
568            self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
569            errors += 1 
570
571         # Set Registry URL
572         if (self.options.registry is not None):
573            self.reg_url = self.options.registry
574         elif hasattr(config, "SFI_REGISTRY"):
575            self.reg_url = config.SFI_REGISTRY
576         else:
577            self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
578            errors += 1 
579
580         # Set user HRN
581         if (self.options.user is not None):
582            self.user = self.options.user
583         elif hasattr(config, "SFI_USER"):
584            self.user = config.SFI_USER
585         else:
586            self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
587            errors += 1 
588
589         # Set authority HRN
590         if (self.options.auth is not None):
591            self.authority = self.options.auth
592         elif hasattr(config, "SFI_AUTH"):
593            self.authority = config.SFI_AUTH
594         else:
595            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
596            errors += 1 
597
598         self.config_file=config_file
599         if errors:
600            sys.exit(1)
601
602     def show_config (self):
603         print "From configuration file %s"%self.config_file
604         flags=[ 
605             ('SFI_USER','user'),
606             ('SFI_AUTH','authority'),
607             ('SFI_SM','sm_url'),
608             ('SFI_REGISTRY','reg_url'),
609             ]
610         for (external_name, internal_name) in flags:
611             print "%s='%s'"%(external_name,getattr(self,internal_name))
612
613     #
614     # Get various credential and spec files
615     #
616     # Establishes limiting conventions
617     #   - conflates MAs and SAs
618     #   - assumes last token in slice name is unique
619     #
620     # Bootstraps credentials
621     #   - bootstrap user credential from self-signed certificate
622     #   - bootstrap authority credential from user credential
623     #   - bootstrap slice credential from user credential
624     #
625     
626     # init self-signed cert, user credentials and gid
627     def bootstrap (self):
628         client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
629                                                logger=self.logger)
630         # if -k is provided, use this to initialize private key
631         if self.options.user_private_key:
632             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
633         else:
634             # trigger legacy compat code if needed 
635             # the name has changed from just <leaf>.pkey to <hrn>.pkey
636             if not os.path.isfile(client_bootstrap.private_key_filename()):
637                 self.logger.info ("private key not found, trying legacy name")
638                 try:
639                     legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
640                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
641                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
642                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
643                 except:
644                     self.logger.log_exc("Can't find private key ")
645                     sys.exit(1)
646             
647         # make it bootstrap
648         client_bootstrap.bootstrap_my_gid()
649         # extract what's needed
650         self.private_key = client_bootstrap.private_key()
651         self.my_credential_string = client_bootstrap.my_credential_string ()
652         self.my_credential = {'geni_type': 'geni_sfa',
653                               'geni_version': '3.0', 
654                               'geni_value': self.my_credential_string}
655         self.my_gid = client_bootstrap.my_gid ()
656         self.client_bootstrap = client_bootstrap
657
658
659     def my_authority_credential_string(self):
660         if not self.authority:
661             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
662             sys.exit(-1)
663         return self.client_bootstrap.authority_credential_string (self.authority)
664
665     def authority_credential_string(self, auth_hrn):
666         return self.client_bootstrap.authority_credential_string (auth_hrn)
667
668     def slice_credential_string(self, name):
669         return self.client_bootstrap.slice_credential_string (name)
670
671     def slice_credential(self, name):
672         return {'geni_type': 'geni_sfa',
673                 'geni_version': '3.0',
674                 'geni_value': self.slice_credential_string(name)}    
675
676     # xxx should be supported by sfaclientbootstrap as well
677     def delegate_cred(self, object_cred, hrn, type='authority'):
678         # the gid and hrn of the object we are delegating
679         if isinstance(object_cred, str):
680             object_cred = Credential(string=object_cred) 
681         object_gid = object_cred.get_gid_object()
682         object_hrn = object_gid.get_hrn()
683     
684         if not object_cred.get_privileges().get_all_delegate():
685             self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
686             return
687
688         # the delegating user's gid
689         caller_gidfile = self.my_gid()
690   
691         # the gid of the user who will be delegated to
692         delegee_gid = self.client_bootstrap.gid(hrn,type)
693         delegee_hrn = delegee_gid.get_hrn()
694         dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
695         return dcred.save_to_string(save_parents=True)
696      
697     #
698     # Management of the servers
699     # 
700
701     def registry (self):
702         # cache the result
703         if not hasattr (self, 'registry_proxy'):
704             self.logger.info("Contacting Registry at: %s"%self.reg_url)
705             self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
706                                                  timeout=self.options.timeout, verbose=self.options.debug)  
707         return self.registry_proxy
708
709     def sliceapi (self):
710         # cache the result
711         if not hasattr (self, 'sliceapi_proxy'):
712             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
713             if hasattr(self.command_options,'component') and self.command_options.component:
714                 # resolve the hrn at the registry
715                 node_hrn = self.command_options.component
716                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
717                 records = filter_records('node', records)
718                 if not records:
719                     self.logger.warning("No such component:%r"% opts.component)
720                 record = records[0]
721                 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
722                 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
723             else:
724                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
725                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
726                     self.sm_url = 'http://' + self.sm_url
727                 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
728                 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
729                                                      timeout=self.options.timeout, verbose=self.options.debug)  
730         return self.sliceapi_proxy
731
732     def get_cached_server_version(self, server):
733         # check local cache first
734         cache = None
735         version = None 
736         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
737         cache_key = server.url + "-version"
738         try:
739             cache = Cache(cache_file)
740         except IOError:
741             cache = Cache()
742             self.logger.info("Local cache not found at: %s" % cache_file)
743
744         if cache:
745             version = cache.get(cache_key)
746
747         if not version: 
748             result = server.GetVersion()
749             version= ReturnValue.get_value(result)
750             # cache version for 20 minutes
751             cache.add(cache_key, version, ttl= 60*20)
752             self.logger.info("Updating cache file %s" % cache_file)
753             cache.save_to_file(cache_file)
754
755         return version   
756         
757     ### resurrect this temporarily so we can support V1 aggregates for a while
758     def server_supports_options_arg(self, server):
759         """
760         Returns true if server support the optional call_id arg, false otherwise. 
761         """
762         server_version = self.get_cached_server_version(server)
763         result = False
764         # xxx need to rewrite this 
765         if int(server_version.get('geni_api')) >= 2:
766             result = True
767         return result
768
769     def server_supports_call_id_arg(self, server):
770         server_version = self.get_cached_server_version(server)
771         result = False      
772         if 'sfa' in server_version and 'code_tag' in server_version:
773             code_tag = server_version['code_tag']
774             code_tag_parts = code_tag.split("-")
775             version_parts = code_tag_parts[0].split(".")
776             major, minor = version_parts[0], version_parts[1]
777             rev = code_tag_parts[1]
778             if int(major) == 1 and minor == 0 and build >= 22:
779                 result = True
780         return result                 
781
782     ### ois = options if supported
783     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
784     def ois (self, server, option_dict):
785         if self.server_supports_options_arg (server): 
786             return [option_dict]
787         elif self.server_supports_call_id_arg (server):
788             return [ unique_call_id () ]
789         else: 
790             return []
791
792     ### cis = call_id if supported - like ois
793     def cis (self, server):
794         if self.server_supports_call_id_arg (server):
795             return [ unique_call_id ]
796         else:
797             return []
798
799     ######################################## miscell utilities
800     def get_rspec_file(self, rspec):
801        if (os.path.isabs(rspec)):
802           file = rspec
803        else:
804           file = os.path.join(self.options.sfi_dir, rspec)
805        if (os.path.isfile(file)):
806           return file
807        else:
808           self.logger.critical("No such rspec file %s"%rspec)
809           sys.exit(1)
810     
811     def get_record_file(self, record):
812        if (os.path.isabs(record)):
813           file = record
814        else:
815           file = os.path.join(self.options.sfi_dir, record)
816        if (os.path.isfile(file)):
817           return file
818        else:
819           self.logger.critical("No such registry record file %s"%record)
820           sys.exit(1)
821
822
823     #==========================================================================
824     # Following functions implement the commands
825     #
826     # Registry-related commands
827     #==========================================================================
828
829     @register_command("","")
830     def version(self, options, args):
831         """
832         display an SFA server version (GetVersion)
833 or version information about sfi itself
834         """
835         if options.version_local:
836             version=version_core()
837         else:
838             if options.version_registry:
839                 server=self.registry()
840             else:
841                 server = self.sliceapi()
842             result = server.GetVersion()
843             version = ReturnValue.get_value(result)
844         if self.options.raw:
845             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
846         else:
847             pprinter = PrettyPrinter(indent=4)
848             pprinter.pprint(version)
849
850     @register_command("authority","")
851     def list(self, options, args):
852         """
853         list entries in named authority registry (List)
854         """
855         if len(args)!= 1:
856             self.print_help()
857             sys.exit(1)
858         hrn = args[0]
859         opts = {}
860         if options.recursive:
861             opts['recursive'] = options.recursive
862         
863         if options.show_credential:
864             show_credentials(self.my_credential_string)
865         try:
866             list = self.registry().List(hrn, self.my_credential_string, options)
867         except IndexError:
868             raise Exception, "Not enough parameters for the 'list' command"
869
870         # filter on person, slice, site, node, etc.
871         # This really should be in the self.filter_records funct def comment...
872         list = filter_records(options.type, list)
873         terminal_render (list, options)
874         if options.file:
875             save_records_to_file(options.file, list, options.fileformat)
876         return
877     
878     @register_command("name","")
879     def show(self, options, args):
880         """
881         show details about named registry record (Resolve)
882         """
883         if len(args)!= 1:
884             self.print_help()
885             sys.exit(1)
886         hrn = args[0]
887         # explicitly require Resolve to run in details mode
888         record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
889         record_dicts = filter_records(options.type, record_dicts)
890         if not record_dicts:
891             self.logger.error("No record of type %s"% options.type)
892             return
893         # user has required to focus on some keys
894         if options.keys:
895             def project (record):
896                 projected={}
897                 for key in options.keys:
898                     try: projected[key]=record[key]
899                     except: pass
900                 return projected
901             record_dicts = [ project (record) for record in record_dicts ]
902         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
903         for record in records:
904             if (options.format == "text"):      record.dump(sort=True)  
905             else:                               print record.save_as_xml() 
906         if options.file:
907             save_records_to_file(options.file, record_dicts, options.fileformat)
908         return
909     
910     @register_command("[record]","")
911     def add(self, options, args):
912         "add record into registry by using the command options (Recommended) or from xml file (Register)"
913         auth_cred = self.my_authority_credential_string()
914         if options.show_credential:
915             show_credentials(auth_cred)
916         record_dict = {}
917         if len(args) > 1:
918             self.print_help()
919             sys.exit(1)
920         if len(args)==1:
921             try:
922                 record_filepath = args[0]
923                 rec_file = self.get_record_file(record_filepath)
924                 record_dict.update(load_record_from_file(rec_file).todict())
925             except:
926                 print "Cannot load record file %s"%record_filepath
927                 sys.exit(1)
928         if options:
929             record_dict.update(load_record_from_opts(options).todict())
930         # we should have a type by now
931         if 'type' not in record_dict :
932             self.print_help()
933             sys.exit(1)
934         # this is still planetlab dependent.. as plc will whine without that
935         # also, it's only for adding
936         if record_dict['type'] == 'user':
937             if not 'first_name' in record_dict:
938                 record_dict['first_name'] = record_dict['hrn']
939             if 'last_name' not in record_dict:
940                 record_dict['last_name'] = record_dict['hrn'] 
941         return self.registry().Register(record_dict, auth_cred)
942     
943     @register_command("[record]","")
944     def update(self, options, args):
945         "update record into registry by using the command options (Recommended) or from xml file (Update)"
946         record_dict = {}
947         if len(args) > 0:
948             record_filepath = args[0]
949             rec_file = self.get_record_file(record_filepath)
950             record_dict.update(load_record_from_file(rec_file).todict())
951         if options:
952             record_dict.update(load_record_from_opts(options).todict())
953         # at the very least we need 'type' here
954         if 'type' not in record_dict:
955             self.print_help()
956             sys.exit(1)
957
958         # don't translate into an object, as this would possibly distort
959         # user-provided data; e.g. add an 'email' field to Users
960         if record_dict['type'] == "user":
961             if record_dict['hrn'] == self.user:
962                 cred = self.my_credential_string
963             else:
964                 cred = self.my_authority_credential_string()
965         elif record_dict['type'] in ["slice"]:
966             try:
967                 cred = self.slice_credential_string(record_dict['hrn'])
968             except ServerException, e:
969                # XXX smbaker -- once we have better error return codes, update this
970                # to do something better than a string compare
971                if "Permission error" in e.args[0]:
972                    cred = self.my_authority_credential_string()
973                else:
974                    raise
975         elif record_dict['type'] in ["authority"]:
976             cred = self.my_authority_credential_string()
977         elif record_dict['type'] == 'node':
978             cred = self.my_authority_credential_string()
979         else:
980             raise "unknown record type" + record_dict['type']
981         if options.show_credential:
982             show_credentials(cred)
983         return self.registry().Update(record_dict, cred)
984   
985     @register_command("name","")
986     def remove(self, options, args):
987         "remove registry record by name (Remove)"
988         auth_cred = self.my_authority_credential_string()
989         if len(args)!=1:
990             self.print_help()
991             sys.exit(1)
992         hrn = args[0]
993         type = options.type 
994         if type in ['all']:
995             type = '*'
996         if options.show_credential:
997             show_credentials(auth_cred)
998         return self.registry().Remove(hrn, auth_cred, type)
999     
1000     # ==================================================================
1001     # Slice-related commands
1002     # ==================================================================
1003
1004     @register_command("","")
1005     def slices(self, options, args):
1006         "list instantiated slices (ListSlices) - returns urn's"
1007         server = self.sliceapi()
1008         # creds
1009         creds = [self.my_credential_string]
1010         # options and call_id when supported
1011         api_options = {}
1012         api_options['call_id']=unique_call_id()
1013         if options.show_credential:
1014             show_credentials(creds)
1015         result = server.ListSlices(creds, *self.ois(server,api_options))
1016         value = ReturnValue.get_value(result)
1017         if self.options.raw:
1018             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1019         else:
1020             display_list(value)
1021         return
1022
1023     # show rspec for named slice
1024     @register_command("[slice_hrn]","")
1025     def resources(self, options, args):
1026         """
1027         discover available resources (ListResources)
1028         """
1029         server = self.sliceapi()
1030
1031         # set creds
1032         creds = [self.my_credential]
1033         if options.delegate:
1034             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1035         if options.show_credential:
1036             show_credentials(creds)
1037
1038         # no need to check if server accepts the options argument since the options has
1039         # been a required argument since v1 API
1040         api_options = {}
1041         # always send call_id to v2 servers
1042         api_options ['call_id'] = unique_call_id()
1043         # ask for cached value if available
1044         api_options ['cached'] = True
1045         if options.info:
1046             api_options['info'] = options.info
1047         if options.list_leases:
1048             api_options['list_leases'] = options.list_leases
1049         if options.current:
1050             if options.current == True:
1051                 api_options['cached'] = False
1052             else:
1053                 api_options['cached'] = True
1054         if options.rspec_version:
1055             version_manager = VersionManager()
1056             server_version = self.get_cached_server_version(server)
1057             if 'sfa' in server_version:
1058                 # just request the version the client wants
1059                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1060             else:
1061                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1062         else:
1063             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1064         result = server.ListResources (creds, api_options)
1065         value = ReturnValue.get_value(result)
1066         if self.options.raw:
1067             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1068         if options.file is not None:
1069             save_rspec_to_file(value, options.file)
1070         if (self.options.raw is None) and (options.file is None):
1071             display_rspec(value, options.format)
1072
1073         return
1074
1075     @register_command("slice_hrn","")
1076     def describe(self, options, args):
1077         """
1078         shows currently allocated/provisioned resources of the named slice or set of slivers (Describe) 
1079         """
1080         server = self.sliceapi()
1081
1082         # set creds
1083         creds = [self.slice_credential(args[0])]
1084         if options.delegate:
1085             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1086         if options.show_credential:
1087             show_credentials(creds)
1088
1089         api_options = {'call_id': unique_call_id(),
1090                        'cached': True,
1091                        'info': options.info,
1092                        'list_leases': options.list_leases,
1093                        'geni_rspec_version': {'type': 'geni', 'version': '3.0'},
1094                       }
1095         if options.rspec_version:
1096             version_manager = VersionManager()
1097             server_version = self.get_cached_server_version(server)
1098             if 'sfa' in server_version:
1099                 # just request the version the client wants
1100                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1101             else:
1102                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1103         urn = Xrn(args[0], type='slice').get_urn()        
1104         result = server.Describe([urn], creds, api_options)
1105         value = ReturnValue.get_value(result)
1106         if self.options.raw:
1107             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1108         if options.file is not None:
1109             save_rspec_to_file(value, options.file)
1110         if (self.options.raw is None) and (options.file is None):
1111             display_rspec(value, options.format)
1112
1113         return 
1114
1115     @register_command("slice_hrn","")
1116     def delete(self, options, args):
1117         """
1118         de-allocate and de-provision all or named slivers of the slice (Delete)
1119         """
1120         server = self.sliceapi()
1121
1122         # slice urn
1123         slice_hrn = args[0]
1124         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1125
1126         # creds
1127         slice_cred = self.slice_credential(slice_hrn)
1128         creds = [slice_cred]
1129         
1130         # options and call_id when supported
1131         api_options = {}
1132         api_options ['call_id'] = unique_call_id()
1133         if options.show_credential:
1134             show_credentials(creds)
1135         result = server.Delete([slice_urn], creds, *self.ois(server, api_options ) )
1136         value = ReturnValue.get_value(result)
1137         if self.options.raw:
1138             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1139         else:
1140             print value
1141         return value
1142
1143     @register_command("slice_hrn rspec","")
1144     def allocate(self, options, args):
1145         """
1146          allocate resources to the named slice (Allocate)
1147         """
1148         server = self.sliceapi()
1149         server_version = self.get_cached_server_version(server)
1150         slice_hrn = args[0]
1151         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1152
1153         # credentials
1154         creds = [self.slice_credential(slice_hrn)]
1155
1156         delegated_cred = None
1157         if server_version.get('interface') == 'slicemgr':
1158             # delegate our cred to the slice manager
1159             # do not delegate cred to slicemgr...not working at the moment
1160             pass
1161             #if server_version.get('hrn'):
1162             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1163             #elif server_version.get('urn'):
1164             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1165
1166         if options.show_credential:
1167             show_credentials(creds)
1168
1169         # rspec
1170         rspec_file = self.get_rspec_file(args[1])
1171         rspec = open(rspec_file).read()
1172         api_options = {}
1173         api_options ['call_id'] = unique_call_id()
1174         result = server.Allocate(slice_urn, creds, rspec, api_options)
1175         value = ReturnValue.get_value(result)
1176         if self.options.raw:
1177             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1178         if options.file is not None:
1179             save_rspec_to_file (value, options.file)
1180         if (self.options.raw is None) and (options.file is None):
1181             print value
1182         return value
1183         
1184
1185     @register_command("slice_hrn","")
1186     def provision(self, options, args):
1187         """
1188         provision already allocated resources of named slice (Provision)
1189         """
1190         server = self.sliceapi()
1191         server_version = self.get_cached_server_version(server)
1192         slice_hrn = args[0]
1193         slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1194
1195         # credentials
1196         creds = [self.slice_credential(slice_hrn)]
1197         delegated_cred = None
1198         if server_version.get('interface') == 'slicemgr':
1199             # delegate our cred to the slice manager
1200             # do not delegate cred to slicemgr...not working at the moment
1201             pass
1202             #if server_version.get('hrn'):
1203             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1204             #elif server_version.get('urn'):
1205             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1206
1207         if options.show_credential:
1208             show_credentials(creds)
1209
1210         api_options = {}
1211         api_options ['call_id'] = unique_call_id()
1212
1213         # set the requtested rspec version
1214         version_manager = VersionManager()
1215         rspec_version = version_manager._get_version('geni', '3.0').to_dict()
1216         api_options['geni_rspec_version'] = rspec_version
1217
1218         # users
1219         # need to pass along user keys to the aggregate.
1220         # users = [
1221         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1222         #    keys: [<ssh key A>, <ssh key B>]
1223         #  }]
1224         users = []
1225         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1226         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1227             slice_record = slice_records[0]
1228             user_hrns = slice_record['researcher']
1229             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1230             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1231             users = pg_users_arg(user_records)
1232         
1233         api_options['geni_users'] = users
1234         result = server.Provision([slice_urn], creds, api_options)
1235         value = ReturnValue.get_value(result)
1236         if self.options.raw:
1237             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1238         if options.file is not None:
1239             save_rspec_to_file (value, options.file)
1240         if (self.options.raw is None) and (options.file is None):
1241             print value
1242         return value     
1243
1244     @register_command("slice_hrn","")
1245     def status(self, options, args):
1246         """
1247         retrieve the status of the slivers belonging to tne named slice (Status)
1248         """
1249         server = self.sliceapi()
1250
1251         # slice urn
1252         slice_hrn = args[0]
1253         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1254
1255         # creds 
1256         slice_cred = self.slice_credential(slice_hrn)
1257         creds = [slice_cred]
1258
1259         # options and call_id when supported
1260         api_options = {}
1261         api_options['call_id']=unique_call_id()
1262         if options.show_credential:
1263             show_credentials(creds)
1264         result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1265         value = ReturnValue.get_value(result)
1266         if self.options.raw:
1267             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1268         else:
1269             print value
1270         # Thierry: seemed to be missing
1271         return value
1272
1273     @register_command("slice_hrn action","")
1274     def action(self, options, args):
1275         """
1276         Perform the named operational action on these slivers
1277         """
1278         server = self.sliceapi()
1279         api_options = {}
1280         # slice urn
1281         slice_hrn = args[0]
1282         action = args[1]
1283         slice_urn = Xrn(slice_hrn, type='slice').get_urn() 
1284         # cred
1285         slice_cred = self.slice_credential(args[0])
1286         creds = [slice_cred]
1287         if options.delegate:
1288             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1289             creds.append(delegated_cred)
1290         
1291         result = server.PerformOperationalAction([slice_urn], creds, action , api_options)
1292         value = ReturnValue.get_value(result)
1293         if self.options.raw:
1294             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1295         else:
1296             print value
1297         return value
1298
1299     @register_command("slice_hrn time","")
1300     def renew(self, options, args):
1301         """
1302         renew slice (RenewSliver)
1303         """
1304         server = self.sliceapi()
1305         if len(args) != 2:
1306             self.print_help()
1307             sys.exit(1)
1308         [ slice_hrn, input_time ] = args
1309         # slice urn    
1310         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1311         # time: don't try to be smart on the time format, server-side will
1312         # creds
1313         slice_cred = self.slice_credential(args[0])
1314         creds = [slice_cred]
1315         # options and call_id when supported
1316         api_options = {}
1317         api_options['call_id']=unique_call_id()
1318         if options.show_credential:
1319             show_credentials(creds)
1320         result =  server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
1321         value = ReturnValue.get_value(result)
1322         if self.options.raw:
1323             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1324         else:
1325             print value
1326         return value
1327
1328
1329     @register_command("slice_hrn","")
1330     def shutdown(self, options, args):
1331         """
1332         shutdown named slice (Shutdown)
1333         """
1334         server = self.sliceapi()
1335         # slice urn
1336         slice_hrn = args[0]
1337         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1338         # creds
1339         slice_cred = self.slice_credential(slice_hrn)
1340         creds = [slice_cred]
1341         result = server.Shutdown(slice_urn, creds)
1342         value = ReturnValue.get_value(result)
1343         if self.options.raw:
1344             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1345         else:
1346             print value
1347         return value         
1348     
1349
1350     @register_command("[name]","")
1351     def gid(self, options, args):
1352         """
1353         Create a GID (CreateGid)
1354         """
1355         if len(args) < 1:
1356             self.print_help()
1357             sys.exit(1)
1358         target_hrn = args[0]
1359         my_gid_string = open(self.client_bootstrap.my_gid()).read() 
1360         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1361         if options.file:
1362             filename = options.file
1363         else:
1364             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1365         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1366         GID(string=gid).save_to_file(filename)
1367          
1368
1369     @register_command("to_hrn","")
1370     def delegate (self, options, args):
1371         """
1372         (locally) create delegate credential for use by given hrn
1373         """
1374         if len(args) != 1:
1375             self.print_help()
1376             sys.exit(1)
1377         to_hrn = args[0]
1378         # support for several delegations in the same call
1379         # so first we gather the things to do
1380         tuples=[]
1381         for slice_hrn in options.delegate_slices:
1382             message="%s.slice"%slice_hrn
1383             original = self.slice_credential_string(slice_hrn)
1384             tuples.append ( (message, original,) )
1385         if options.delegate_pi:
1386             my_authority=self.authority
1387             message="%s.pi"%my_authority
1388             original = self.my_authority_credential_string()
1389             tuples.append ( (message, original,) )
1390         for auth_hrn in options.delegate_auths:
1391             message="%s.auth"%auth_hrn
1392             original=self.authority_credential_string(auth_hrn)
1393             tuples.append ( (message, original, ) )
1394         # if nothing was specified at all at this point, let's assume -u
1395         if not tuples: options.delegate_user=True
1396         # this user cred
1397         if options.delegate_user:
1398             message="%s.user"%self.user
1399             original = self.my_credential_string
1400             tuples.append ( (message, original, ) )
1401
1402         # default type for beneficial is user unless -A
1403         if options.delegate_to_authority:       to_type='authority'
1404         else:                                   to_type='user'
1405
1406         # let's now handle all this
1407         # it's all in the filenaming scheme
1408         for (message,original) in tuples:
1409             delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1410             delegated_credential = Credential (string=delegated_string)
1411             filename = os.path.join ( self.options.sfi_dir,
1412                                       "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1413             delegated_credential.save_to_file(filename, save_parents=True)
1414             self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1415     
1416     ####################
1417     @register_command("","""$ less +/myslice myslice sfi_config
1418 [myslice]
1419 backend  = 'http://manifold.pl.sophia.inria.fr:7080'
1420 delegate = 'ple.upmc.slicebrowser'
1421 user     = 'thierry'
1422
1423 $ sfi myslice
1424
1425   will first collect the slices that you are part of, then make sure
1426   all your credentials are up-to-date (that is: refresh expired ones)
1427   then compute delegated credentials for user 'ple.upmc.slicebrowser'
1428   and upload them all on myslice backend, using manifold id as
1429   specified in 'user'
1430 """
1431 )
1432     def myslice (self, options, args):
1433
1434         """ This helper is for refreshing your credentials at myslice; it will
1435   * compute all the slices that you currently have credentials on
1436   * refresh all your credentials (you as a user and pi, your slices)
1437   * upload them to the manifold backend server
1438   for last phase, sfi_config is read to look for the [myslice] section, 
1439   and namely the 'backend', 'delegate' and 'user' settings"""
1440
1441         ##########
1442         if len(args)>0:
1443             self.print_help()
1444             sys.exit(1)
1445         # ManifoldUploader
1446         pass
1447
1448     @register_command("cred","")
1449     def trusted(self, options, args):
1450         """
1451         return the trusted certs at this interface (get_trusted_certs)
1452         """ 
1453         trusted_certs = self.registry().get_trusted_certs()
1454         for trusted_cert in trusted_certs:
1455             gid = GID(string=trusted_cert)
1456             gid.dump()
1457             cert = Certificate(string=trusted_cert)
1458             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1459         return 
1460
1461     @register_command("","")
1462     def config (self, options, args):
1463         "Display contents of current config"
1464         self.show_config()