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