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