Merge branch 'master' into senslab2
[sfa.git] / sfa / client / sfi.py
1
2 # sfi.py - basic SFA command-line client
3 # the actual binary in sfa/clientbin essentially runs main()
4 # this module is used in sfascan
5
6
7 import sys
8 sys.path.append('.')
9
10 import os, os.path
11 import socket
12 import datetime
13 import codecs
14 import pickle
15 from lxml import etree
16 from StringIO import StringIO
17 from optparse import OptionParser
18 from pprint import PrettyPrinter
19
20 from sfa.trust.certificate import Keypair, Certificate
21 from sfa.trust.gid import GID
22 from sfa.trust.credential import Credential
23 from sfa.trust.sfaticket import SfaTicket
24
25 from sfa.util.sfalogging import sfi_logger
26 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn
27 from sfa.util.config import Config
28 from sfa.util.version import version_core
29 from sfa.util.cache import Cache
30
31 from sfa.storage.record import SfaRecord, UserRecord, SliceRecord, NodeRecord, AuthorityRecord
32
33 from sfa.rspecs.rspec import RSpec
34 from sfa.rspecs.rspec_converter import RSpecConverter
35 from sfa.rspecs.version_manager import VersionManager
36
37 from sfa.client.sfaclientlib import SfaClientBootstrap
38 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
39 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
40 from sfa.client.return_value import ReturnValue
41
42 CM_PORT=12346
43
44 # utility methods here
45 # display methods
46 def display_rspec(rspec, format='rspec'):
47     if format in ['dns']:
48         tree = etree.parse(StringIO(rspec))
49         root = tree.getroot()
50         result = root.xpath("./network/site/node/hostname/text()")
51     elif format in ['ip']:
52         # The IP address is not yet part of the new RSpec
53         # so this doesn't do anything yet.
54         tree = etree.parse(StringIO(rspec))
55         root = tree.getroot()
56         result = root.xpath("./network/site/node/ipv4/text()")
57     else:
58         result = rspec
59
60     print result
61     return
62
63 def display_list(results):
64     for result in results:
65         print result
66
67 def display_records(recordList, dump=False):
68     ''' Print all fields in the record'''
69     for record in recordList:
70         display_record(record, dump)
71
72 def display_record(record, dump=False):
73     if dump:
74         record.dump()
75     else:
76         info = record.getdict()
77         print "%s (%s)" % (info['hrn'], info['type'])
78     return
79
80
81 def filter_records(type, records):
82     filtered_records = []
83     for record in records:
84         if (record['type'] == type) or (type == "all"):
85             filtered_records.append(record)
86     return filtered_records
87
88
89 # save methods
90 def save_variable_to_file(var, filename, format="text"):
91     f = open(filename, "w")
92     if format == "text":
93         f.write(str(var))
94     elif format == "pickled":
95         f.write(pickle.dumps(var))
96     else:
97         # this should never happen
98         print "unknown output format", format
99
100
101 def save_rspec_to_file(rspec, filename):
102     if not filename.endswith(".rspec"):
103         filename = filename + ".rspec"
104     f = open(filename, 'w')
105     f.write(rspec)
106     f.close()
107     return
108
109 def save_records_to_file(filename, recordList, format="xml"):
110     if format == "xml":
111         index = 0
112         for record in recordList:
113             if index > 0:
114                 save_record_to_file(filename + "." + str(index), record)
115             else:
116                 save_record_to_file(filename, record)
117             index = index + 1
118     elif format == "xmllist":
119         f = open(filename, "w")
120         f.write("<recordlist>\n")
121         for record in recordList:
122             record = SfaRecord(dict=record)
123             f.write('<record hrn="' + record.get_name() + '" type="' + record.get_type() + '" />\n')
124         f.write("</recordlist>\n")
125         f.close()
126     elif format == "hrnlist":
127         f = open(filename, "w")
128         for record in recordList:
129             record = SfaRecord(dict=record)
130             f.write(record.get_name() + "\n")
131         f.close()
132     else:
133         # this should never happen
134         print "unknown output format", format
135
136 def save_record_to_file(filename, record):
137     if record['type'] in ['user']:
138         record = UserRecord(dict=record)
139     elif record['type'] in ['slice']:
140         record = SliceRecord(dict=record)
141     elif record['type'] in ['node']:
142         record = NodeRecord(dict=record)
143     elif record['type'] in ['authority', 'ma', 'sa']:
144         record = AuthorityRecord(dict=record)
145     else:
146         record = SfaRecord(dict=record)
147     str = record.save_to_string()
148     f=codecs.open(filename, encoding='utf-8',mode="w")
149     f.write(str)
150     f.close()
151     return
152
153
154 # load methods
155 def load_record_from_file(filename):
156     f=codecs.open(filename, encoding="utf-8", mode="r")
157     str = f.read()
158     f.close()
159     record = SfaRecord(string=str)
160     return record
161
162
163 import uuid
164 def unique_call_id(): return uuid.uuid4().urn
165
166 class Sfi:
167     
168     required_options=['verbose',  'debug',  'registry',  'sm',  'auth',  'user']
169
170     @staticmethod
171     def default_sfi_dir ():
172         if os.path.isfile("./sfi_config"): 
173             return os.getcwd()
174         else:
175             return os.path.expanduser("~/.sfi/")
176
177     # dummy to meet Sfi's expectations for its 'options' field
178     # i.e. s/t we can do setattr on
179     class DummyOptions:
180         pass
181
182     def __init__ (self,options=None):
183         if options is None: options=Sfi.DummyOptions()
184         for opt in Sfi.required_options:
185             if not hasattr(options,opt): setattr(options,opt,None)
186         if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
187         self.options = options
188         self.user = None
189         self.authority = None
190         self.logger = sfi_logger
191         self.logger.enable_console()
192         self.available_names = [ tuple[0] for tuple in Sfi.available ]
193         self.available_dict = dict (Sfi.available)
194    
195     # tuples command-name expected-args in the order in which they should appear in the help
196     available = [ 
197         ("version", ""),  
198         ("list", "authority"),
199         ("show", "name"),
200         ("add", "record"),
201         ("update", "record"),
202         ("remove", "name"),
203         ("slices", ""),
204         ("resources", "[slice_hrn]"),
205         ("create", "slice_hrn rspec"),
206         ("delete", "slice_hrn"),
207         ("status", "slice_hrn"),
208         ("start", "slice_hrn"),
209         ("stop", "slice_hrn"),
210         ("reset", "slice_hrn"),
211         ("renew", "slice_hrn time"),
212         ("shutdown", "slice_hrn"),
213         ("get_ticket", "slice_hrn rspec"),
214         ("redeem_ticket", "ticket"),
215         ("delegate", "name"),
216         ("create_gid", "[name]"),
217         ("get_trusted_certs", "cred"),
218         ]
219
220     def print_command_help (self, options):
221         verbose=getattr(options,'verbose')
222         format3="%18s %-15s %s"
223         line=80*'-'
224         if not verbose:
225             print format3%("command","cmd_args","description")
226             print line
227         else:
228             print line
229             self.create_parser().print_help()
230         for command in self.available_names:
231             args=self.available_dict[command]
232             method=getattr(self,command,None)
233             doc=""
234             if method: doc=getattr(method,'__doc__',"")
235             if not doc: doc="*** no doc found ***"
236             doc=doc.strip(" \t\n")
237             doc=doc.replace("\n","\n"+35*' ')
238             if verbose:
239                 print line
240             print format3%(command,args,doc)
241             if verbose:
242                 self.create_command_parser(command).print_help()
243
244     def create_command_parser(self, command):
245         if command not in self.available_dict:
246             msg="Invalid command\n"
247             msg+="Commands: "
248             msg += ','.join(self.available_names)            
249             self.logger.critical(msg)
250             sys.exit(2)
251
252         parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
253                                      % (command, self.available_dict[command]))
254
255         # user specifies remote aggregate/sm/component                          
256         if command in ("resources", "slices", "create", "delete", "start", "stop", 
257                        "restart", "shutdown",  "get_ticket", "renew", "status"):
258             parser.add_option("-c", "--component", dest="component", default=None,
259                              help="component hrn")
260             parser.add_option("-d", "--delegate", dest="delegate", default=None, 
261                              action="store_true",
262                              help="Include a credential delegated to the user's root"+\
263                                   "authority in set of credentials for this call")
264
265         # registy filter option
266         if command in ("list", "show", "remove"):
267             parser.add_option("-t", "--type", dest="type", type="choice",
268                             help="type filter ([all]|user|slice|authority|node|aggregate)",
269                             choices=("all", "user", "slice", "authority", "node", "aggregate"),
270                             default="all")
271         # display formats
272         if command in ("resources"):
273             parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
274                               help="schema type and version of resulting RSpec")
275             parser.add_option("-f", "--format", dest="format", type="choice",
276                              help="display format ([xml]|dns|ip)", default="xml",
277                              choices=("xml", "dns", "ip"))
278             #panos: a new option to define the type of information about resources a user is interested in
279             parser.add_option("-i", "--info", dest="info",
280                                 help="optional component information", default=None)
281
282
283         # 'create' does return the new rspec, makes sense to save that too
284         if command in ("resources", "show", "list", "create_gid", 'create'):
285            parser.add_option("-o", "--output", dest="file",
286                             help="output XML to file", metavar="FILE", default=None)
287
288         if command in ("show", "list"):
289            parser.add_option("-f", "--format", dest="format", type="choice",
290                              help="display format ([text]|xml)", default="text",
291                              choices=("text", "xml"))
292
293            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
294                              help="output file format ([xml]|xmllist|hrnlist)", default="xml",
295                              choices=("xml", "xmllist", "hrnlist"))
296
297         if command in ("status", "version"):
298            parser.add_option("-o", "--output", dest="file",
299                             help="output dictionary to file", metavar="FILE", default=None)
300            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
301                              help="output file format ([text]|pickled)", default="text",
302                              choices=("text","pickled"))
303
304         if command in ("delegate"):
305            parser.add_option("-u", "--user",
306                             action="store_true", dest="delegate_user", default=False,
307                             help="delegate user credential")
308            parser.add_option("-s", "--slice", dest="delegate_slice",
309                             help="delegate slice credential", metavar="HRN", default=None)
310         
311         if command in ("version"):
312             parser.add_option("-R","--registry-version",
313                               action="store_true", dest="version_registry", default=False,
314                               help="probe registry version instead of sliceapi")
315             parser.add_option("-l","--local",
316                               action="store_true", dest="version_local", default=False,
317                               help="display version of the local client")
318
319         return parser
320
321         
322     def create_parser(self):
323
324         # Generate command line parser
325         parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
326                              description="Commands: %s"%(" ".join(self.available_names)))
327         parser.add_option("-r", "--registry", dest="registry",
328                          help="root registry", metavar="URL", default=None)
329         parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
330                          help="slice API - in general a SM URL, but can be used to talk to an aggregate")
331         parser.add_option("-d", "--dir", dest="sfi_dir",
332                          help="config & working directory - default is %default",
333                          metavar="PATH", default=Sfi.default_sfi_dir())
334         parser.add_option("-u", "--user", dest="user",
335                          help="user name", metavar="HRN", default=None)
336         parser.add_option("-a", "--auth", dest="auth",
337                          help="authority name", metavar="HRN", default=None)
338         parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
339                          help="verbose mode - cumulative")
340         parser.add_option("-D", "--debug",
341                           action="store_true", dest="debug", default=False,
342                           help="Debug (xml-rpc) protocol messages")
343         # would it make sense to use ~/.ssh/id_rsa as a default here ?
344         parser.add_option("-k", "--private-key",
345                          action="store", dest="user_private_key", default=None,
346                          help="point to the private key file to use if not yet installed in sfi_dir")
347         parser.add_option("-t", "--timeout", dest="timeout", default=None,
348                          help="Amout of time to wait before timing out the request")
349         parser.add_option("-?", "--commands", 
350                          action="store_true", dest="command_help", default=False,
351                          help="one page summary on commands & exit")
352         parser.disable_interspersed_args()
353
354         return parser
355         
356
357     def print_help (self):
358         self.sfi_parser.print_help()
359         self.command_parser.print_help()
360
361     #
362     # Main: parse arguments and dispatch to command
363     #
364     def dispatch(self, command, command_options, command_args):
365         return getattr(self, command)(command_options, command_args)
366
367     def main(self):
368         self.sfi_parser = self.create_parser()
369         (options, args) = self.sfi_parser.parse_args()
370         if options.command_help: 
371             self.print_command_help(options)
372             sys.exit(1)
373         self.options = options
374
375         self.logger.setLevelFromOptVerbose(self.options.verbose)
376
377         if len(args) <= 0:
378             self.logger.critical("No command given. Use -h for help.")
379             self.print_command_help(options)
380             return -1
381     
382         command = args[0]
383         self.command_parser = self.create_command_parser(command)
384         (command_options, command_args) = self.command_parser.parse_args(args[1:])
385         self.command_options = command_options
386
387         self.read_config () 
388         self.bootstrap ()
389         self.logger.info("Command=%s" % command)
390
391         try:
392             self.dispatch(command, command_options, command_args)
393         except KeyError:
394             self.logger.critical ("Unknown command %s"%command)
395             raise
396             sys.exit(1)
397     
398         return
399     
400     ####################
401     def read_config(self):
402         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
403         try:
404            config = Config (config_file)
405         except:
406            self.logger.critical("Failed to read configuration file %s"%config_file)
407            self.logger.info("Make sure to remove the export clauses and to add quotes")
408            if self.options.verbose==0:
409                self.logger.info("Re-run with -v for more details")
410            else:
411                self.logger.log_exc("Could not read config file %s"%config_file)
412            sys.exit(1)
413      
414         errors = 0
415         # Set SliceMgr URL
416         if (self.options.sm is not None):
417            self.sm_url = self.options.sm
418         elif hasattr(config, "SFI_SM"):
419            self.sm_url = config.SFI_SM
420         else:
421            self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
422            errors += 1 
423
424         # Set Registry URL
425         if (self.options.registry is not None):
426            self.reg_url = self.options.registry
427         elif hasattr(config, "SFI_REGISTRY"):
428            self.reg_url = config.SFI_REGISTRY
429         else:
430            self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
431            errors += 1 
432
433         # Set user HRN
434         if (self.options.user is not None):
435            self.user = self.options.user
436         elif hasattr(config, "SFI_USER"):
437            self.user = config.SFI_USER
438         else:
439            self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
440            errors += 1 
441
442         # Set authority HRN
443         if (self.options.auth is not None):
444            self.authority = self.options.auth
445         elif hasattr(config, "SFI_AUTH"):
446            self.authority = config.SFI_AUTH
447         else:
448            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
449            errors += 1 
450
451         if errors:
452            sys.exit(1)
453
454     #
455     # Get various credential and spec files
456     #
457     # Establishes limiting conventions
458     #   - conflates MAs and SAs
459     #   - assumes last token in slice name is unique
460     #
461     # Bootstraps credentials
462     #   - bootstrap user credential from self-signed certificate
463     #   - bootstrap authority credential from user credential
464     #   - bootstrap slice credential from user credential
465     #
466     
467     # init self-signed cert, user credentials and gid
468     def bootstrap (self):
469         bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
470         # if -k is provided, use this to initialize private key
471         if self.options.user_private_key:
472             bootstrap.init_private_key_if_missing (self.options.user_private_key)
473         else:
474             # trigger legacy compat code if needed 
475             # the name has changed from just <leaf>.pkey to <hrn>.pkey
476             if not os.path.isfile(bootstrap.private_key_filename()):
477                 self.logger.info ("private key not found, trying legacy name")
478                 try:
479                     legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
480                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
481                     bootstrap.init_private_key_if_missing (legacy_private_key)
482                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
483                 except:
484                     self.logger.log_exc("Can't find private key ")
485                     sys.exit(1)
486             
487         # make it bootstrap
488         bootstrap.bootstrap_my_gid()
489         # extract what's needed
490         self.private_key = bootstrap.private_key()
491         self.my_credential_string = bootstrap.my_credential_string ()
492         self.my_gid = bootstrap.my_gid ()
493         self.bootstrap = bootstrap
494
495
496     def my_authority_credential_string(self):
497         if not self.authority:
498             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
499             sys.exit(-1)
500         return self.bootstrap.authority_credential_string (self.authority)
501
502     def slice_credential_string(self, name):
503         return self.bootstrap.slice_credential_string (name)
504
505     # xxx should be supported by sfaclientbootstrap as well
506     def delegate_cred(self, object_cred, hrn, type='authority'):
507         # the gid and hrn of the object we are delegating
508         if isinstance(object_cred, str):
509             object_cred = Credential(string=object_cred) 
510         object_gid = object_cred.get_gid_object()
511         object_hrn = object_gid.get_hrn()
512     
513         if not object_cred.get_privileges().get_all_delegate():
514             self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
515             return
516
517         # the delegating user's gid
518         caller_gidfile = self.my_gid()
519   
520         # the gid of the user who will be delegated to
521         delegee_gid = self.bootstrap.gid(hrn,type)
522         delegee_hrn = delegee_gid.get_hrn()
523         dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
524         return dcred.save_to_string(save_parents=True)
525      
526     #
527     # Management of the servers
528     # 
529
530     def registry (self):
531         # cache the result
532         if not hasattr (self, 'registry_proxy'):
533             self.logger.info("Contacting Registry at: %s"%self.reg_url)
534             self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
535                                                  timeout=self.options.timeout, verbose=self.options.debug)  
536         return self.registry_proxy
537
538     def sliceapi (self):
539         # cache the result
540         if not hasattr (self, 'sliceapi_proxy'):
541             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
542             if hasattr(self.command_options,'component') and self.command_options.component:
543                 # resolve the hrn at the registry
544                 node_hrn = self.command_options.component
545                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
546                 records = filter_records('node', records)
547                 if not records:
548                     self.logger.warning("No such component:%r"% opts.component)
549                 record = records[0]
550                 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
551                 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
552             else:
553                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
554                 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
555                 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
556                                                      timeout=self.options.timeout, verbose=self.options.debug)  
557         return self.sliceapi_proxy
558
559     def get_cached_server_version(self, server):
560         # check local cache first
561         cache = None
562         version = None 
563         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
564         cache_key = server.url + "-version"
565         try:
566             cache = Cache(cache_file)
567         except IOError:
568             cache = Cache()
569             self.logger.info("Local cache not found at: %s" % cache_file)
570
571         if cache:
572             version = cache.get(cache_key)
573
574         if not version: 
575             result = server.GetVersion()
576             version= ReturnValue.get_value(result)
577             # cache version for 20 minutes
578             cache.add(cache_key, version, ttl= 60*20)
579             self.logger.info("Updating cache file %s" % cache_file)
580             cache.save_to_file(cache_file)
581
582         return version   
583         
584     ### resurrect this temporarily so we can support V1 aggregates for a while
585     def server_supports_options_arg(self, server):
586         """
587         Returns true if server support the optional call_id arg, false otherwise. 
588         """
589         server_version = self.get_cached_server_version(server)
590         result = False
591         # xxx need to rewrite this 
592         if int(server_version.get('geni_api')) >= 2:
593             result = True
594         return result
595
596     def server_supports_call_id_arg(self, server):
597         server_version = self.get_cached_server_version(server)
598         result = False      
599         if 'sfa' in server_version and 'code_tag' in server_version:
600             code_tag = server_version['code_tag']
601             code_tag_parts = code_tag.split("-")
602             version_parts = code_tag_parts[0].split(".")
603             major, minor = version_parts[0], version_parts[1]
604             rev = code_tag_parts[1]
605             if int(major) == 1 and minor == 0 and build >= 22:
606                 result = True
607         return result                 
608
609     ### ois = options if supported
610     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
611     def ois (self, server, option_dict):
612         if self.server_supports_options_arg (server): 
613             return [option_dict]
614         elif self.server_supports_call_id_arg (server):
615             return [ unique_call_id () ]
616         else: 
617             return []
618
619     ### cis = call_id if supported - like ois
620     def cis (self, server):
621         if self.server_supports_call_id_arg (server):
622             return [ unique_call_id ]
623         else:
624             return []
625
626     ######################################## miscell utilities
627     def get_rspec_file(self, rspec):
628        if (os.path.isabs(rspec)):
629           file = rspec
630        else:
631           file = os.path.join(self.options.sfi_dir, rspec)
632        if (os.path.isfile(file)):
633           return file
634        else:
635           self.logger.critical("No such rspec file %s"%rspec)
636           sys.exit(1)
637     
638     def get_record_file(self, record):
639        if (os.path.isabs(record)):
640           file = record
641        else:
642           file = os.path.join(self.options.sfi_dir, record)
643        if (os.path.isfile(file)):
644           return file
645        else:
646           self.logger.critical("No such registry record file %s"%record)
647           sys.exit(1)
648     
649
650     #==========================================================================
651     # Following functions implement the commands
652     #
653     # Registry-related commands
654     #==========================================================================
655   
656     def version(self, options, args):
657         """
658         display an SFA server version (GetVersion) 
659 or version information about sfi itself
660         """
661         if options.version_local:
662             version=version_core()
663         else:
664             if options.version_registry:
665                 server=self.registry()
666             else:
667                 server = self.sliceapi()
668             result = server.GetVersion()
669             version = ReturnValue.get_value(result)
670         pprinter = PrettyPrinter(indent=4)
671         pprinter.pprint(version)
672         if options.file:
673             save_variable_to_file(version, options.file, options.fileformat)
674
675     def list(self, options, args):
676         """
677         list entries in named authority registry (List)
678         """
679         if len(args)!= 1:
680             self.print_help()
681             sys.exit(1)
682         hrn = args[0]
683         try:
684             list = self.registry().List(hrn, self.my_credential_string)
685         except IndexError:
686             raise Exception, "Not enough parameters for the 'list' command"
687
688         # filter on person, slice, site, node, etc.
689         # THis really should be in the self.filter_records funct def comment...
690         list = filter_records(options.type, list)
691         for record in list:
692             print "%s (%s)" % (record['hrn'], record['type'])
693         if options.file:
694             save_records_to_file(options.file, list, options.fileformat)
695         return
696     
697     def show(self, options, args):
698         """
699         show details about named registry record (Resolve)
700         """
701         if len(args)!= 1:
702             self.print_help()
703             sys.exit(1)
704         hrn = args[0]
705         records = self.registry().Resolve(hrn, self.my_credential_string)
706         records = filter_records(options.type, records)
707         if not records:
708             self.logger.error("No record of type %s"% options.type)
709         for record in records:
710             if record['type'] in ['user']:
711                 record = UserRecord(dict=record)
712             elif record['type'] in ['slice']:
713                 record = SliceRecord(dict=record)
714             elif record['type'] in ['node']:
715                 record = NodeRecord(dict=record)
716             elif record['type'].startswith('authority'):
717                 record = AuthorityRecord(dict=record)
718             else:
719                 record = SfaRecord(dict=record)
720             if (options.format == "text"): 
721                 record.dump()  
722             else:
723                 print record.save_to_string() 
724         if options.file:
725             save_records_to_file(options.file, records, options.fileformat)
726         return
727     
728     def add(self, options, args):
729         "add record into registry from xml file (Register)"
730         auth_cred = self.my_authority_credential_string()
731         if len(args)!=1:
732             self.print_help()
733             sys.exit(1)
734         record_filepath = args[0]
735         rec_file = self.get_record_file(record_filepath)
736         record = load_record_from_file(rec_file).as_dict()
737         return self.registry().Register(record, auth_cred)
738     
739     def update(self, options, args):
740         "update record into registry from xml file (Update)"
741         if len(args)!=1:
742             self.print_help()
743             sys.exit(1)
744         rec_file = self.get_record_file(args[0])
745         record = load_record_from_file(rec_file)
746         if record['type'] == "user":
747             if record.get_name() == self.user:
748                 cred = self.my_credential_string
749             else:
750                 cred = self.my_authority_credential_string()
751         elif record['type'] in ["slice"]:
752             try:
753                 cred = self.slice_credential_string(record.get_name())
754             except ServerException, e:
755                # XXX smbaker -- once we have better error return codes, update this
756                # to do something better than a string compare
757                if "Permission error" in e.args[0]:
758                    cred = self.my_authority_credential_string()
759                else:
760                    raise
761         elif record.get_type() in ["authority"]:
762             cred = self.my_authority_credential_string()
763         elif record.get_type() == 'node':
764             cred = self.my_authority_credential_string()
765         else:
766             raise "unknown record type" + record.get_type()
767         record = record.as_dict()
768         return self.registry().Update(record, cred)
769   
770     def remove(self, options, args):
771         "remove registry record by name (Remove)"
772         auth_cred = self.my_authority_credential_string()
773         if len(args)!=1:
774             self.print_help()
775             sys.exit(1)
776         hrn = args[0]
777         type = options.type 
778         if type in ['all']:
779             type = '*'
780         return self.registry().Remove(hrn, auth_cred, type)
781     
782     # ==================================================================
783     # Slice-related commands
784     # ==================================================================
785
786     def slices(self, options, args):
787         "list instantiated slices (ListSlices) - returns urn's"
788         server = self.sliceapi()
789         # creds
790         creds = [self.my_credential_string]
791         if options.delegate:
792             delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
793             creds.append(delegated_cred)  
794         # options and call_id when supported
795         api_options = {}
796         api_options['call_id']=unique_call_id()
797         result = server.ListSlices(creds, *self.ois(server,api_options))
798         value = ReturnValue.get_value(result)
799         display_list(value)
800         return
801     
802     # show rspec for named slice
803     def resources(self, options, args):
804         """
805         with no arg, discover available resources, (ListResources)
806 or with an slice hrn, shows currently provisioned resources
807         """
808         server = self.sliceapi()
809
810         # set creds
811         creds = []
812         if args:
813             creds.append(self.slice_credential_string(args[0]))
814         else:
815             creds.append(self.my_credential_string)
816         if options.delegate:
817             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
818         
819         # V2 API
820         if self.server_supports_options_arg(server):
821             # with v2 everything goes in options inclusing the subject slice
822             api_options = {}
823             if args:
824                 hrn = args[0]
825                 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
826             if options.info:
827                 api_options['info'] = options.info
828             if options.rspec_version:
829                 version_manager = VersionManager()
830                 server_version = self.get_cached_server_version(server)
831                 if 'sfa' in server_version:
832                     # just request the version the client wants
833                     api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
834                 else:
835                     # this must be a protogeni aggregate. We should request a v2 ad rspec
836                     # regardless of what the client user requested
837                     api_options['geni_rspec_version'] = version_manager.get_version('ProtoGENI 2').to_dict() 
838             else:
839                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}    
840             # always send call_id to v2 servers
841             api_options ['call_id'] = unique_call_id()
842             # the V2 form
843             result = server.ListResources (creds, api_options)
844         # V1
845         else:
846             # with an argument
847             if args:
848                 hrn = args[0]
849                 # xxx looks like we can pass a hrn and not a urn here ??
850                 # last arg. is a raw call_id when supported
851                 result = server.ListResources (creds, hrn, *self.cis(server))
852             else:
853                 result = server.ListResources (creds, *self.cis(server))
854         value = ReturnValue.get_value(result)
855         if options.file is None:
856             display_rspec(value, options.format)
857         else:
858             save_rspec_to_file(value, options.file)
859         return
860
861     def create(self, options, args):
862         """
863         create or update named slice with given rspec
864         """
865         server = self.sliceapi()
866
867         # xxx do we need to check usage (len(args)) ?
868         # slice urn
869         slice_hrn = args[0]
870         slice_urn = hrn_to_urn(slice_hrn, 'slice')
871
872         # credentials
873         creds = [self.slice_credential_string(slice_hrn)]
874         delegated_cred = None
875         server_version = self.get_cached_server_version(server)
876         if server_version.get('interface') == 'slicemgr':
877             # delegate our cred to the slice manager
878             # do not delegate cred to slicemgr...not working at the moment
879             pass
880             #if server_version.get('hrn'):
881             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
882             #elif server_version.get('urn'):
883             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
884                 
885         # rspec 
886         rspec_file = self.get_rspec_file(args[1])
887         rspec = open(rspec_file).read()
888
889         # users
890         # need to pass along user keys to the aggregate.
891         # users = [
892         #  { urn: urn:publicid:IDN+emulab.net+user+alice
893         #    keys: [<ssh key A>, <ssh key B>]
894         #  }]
895         users = []
896         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
897         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
898             slice_record = slice_records[0]
899             user_hrns = slice_record['researcher']
900             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
901             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
902
903             if 'sfa' not in server_version:
904                 users = pg_users_arg(user_records)
905                 rspec = RSpec(rspec)
906                 rspec.filter({'component_manager_id': server_version['urn']})
907                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
908             else:
909                 print >>sys.stderr, "\r\n \r\n \r\n WOOOOOO"
910                 users = sfa_users_arg(user_records, slice_record)
911         
912         # do not append users, keys, or slice tags. Anything 
913         # not contained in this request will be removed from the slice
914
915         # CreateSliver has supported the options argument for a while now so it should
916         # be safe to assume this server support it
917         api_options = {}
918         api_options ['append'] = False
919         api_options ['call_id'] = unique_call_id()
920
921         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
922         value = ReturnValue.get_value(result)
923         if options.file is None:
924             print value
925         else:
926             save_rspec_to_file (value, options.file)
927         return value
928
929     def delete(self, options, args):
930         """
931         delete named slice (DeleteSliver)
932         """
933         server = self.sliceapi()
934
935         # slice urn
936         slice_hrn = args[0]
937         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
938
939         # creds
940         slice_cred = self.slice_credential_string(slice_hrn)
941         creds = [slice_cred]
942         if options.delegate:
943             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
944             creds.append(delegated_cred)
945         
946         # options and call_id when supported
947         api_options = {}
948         api_options ['call_id'] = unique_call_id()
949         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
950         # xxx no ReturnValue ??
951         return result
952   
953     def status(self, options, args):
954         """
955         retrieve slice status (SliverStatus)
956         """
957         server = self.sliceapi()
958
959         # slice urn
960         slice_hrn = args[0]
961         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
962
963         # creds 
964         slice_cred = self.slice_credential_string(slice_hrn)
965         creds = [slice_cred]
966         if options.delegate:
967             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
968             creds.append(delegated_cred)
969
970         # options and call_id when supported
971         api_options = {}
972         api_options['call_id']=unique_call_id()
973         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
974         value = ReturnValue.get_value(result)
975         print value
976         if options.file:
977             save_variable_to_file(value, options.file, options.fileformat)
978
979     def start(self, options, args):
980         """
981         start named slice (Start)
982         """
983         server = self.sliceapi()
984
985         # the slice urn
986         slice_hrn = args[0]
987         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
988         
989         # cred
990         slice_cred = self.slice_credential_string(args[0])
991         creds = [slice_cred]
992         if options.delegate:
993             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
994             creds.append(delegated_cred)
995         # xxx Thierry - does this not need an api_options as well ?
996         return server.Start(slice_urn, creds)
997     
998     def stop(self, options, args):
999         """
1000         stop named slice (Stop)
1001         """
1002         server = self.sliceapi()
1003         # slice urn
1004         slice_hrn = args[0]
1005         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1006         # cred
1007         slice_cred = self.slice_credential_string(args[0])
1008         creds = [slice_cred]
1009         if options.delegate:
1010             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1011             creds.append(delegated_cred)
1012         return server.Stop(slice_urn, creds)
1013     
1014     # reset named slice
1015     def reset(self, options, args):
1016         """
1017         reset named slice (reset_slice)
1018         """
1019         server = self.sliceapi()
1020         # slice urn
1021         slice_hrn = args[0]
1022         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1023         # cred
1024         slice_cred = self.slice_credential_string(args[0])
1025         creds = [slice_cred]
1026         if options.delegate:
1027             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1028             creds.append(delegated_cred)
1029         return server.reset_slice(creds, slice_urn)
1030
1031     def renew(self, options, args):
1032         """
1033         renew slice (RenewSliver)
1034         """
1035         server = self.sliceapi()
1036         # slice urn    
1037         slice_hrn = args[0]
1038         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1039         # creds
1040         slice_cred = self.slice_credential_string(args[0])
1041         creds = [slice_cred]
1042         if options.delegate:
1043             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1044             creds.append(delegated_cred)
1045         # time
1046         time = args[1]
1047         # options and call_id when supported
1048         api_options = {}
1049         api_options['call_id']=unique_call_id()
1050         result =  server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1051         value = ReturnValue.get_value(result)
1052         return value
1053
1054
1055     def shutdown(self, options, args):
1056         """
1057         shutdown named slice (Shutdown)
1058         """
1059         server = self.sliceapi()
1060         # slice urn
1061         slice_hrn = args[0]
1062         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1063         # creds
1064         slice_cred = self.slice_credential_string(slice_hrn)
1065         creds = [slice_cred]
1066         if options.delegate:
1067             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1068             creds.append(delegated_cred)
1069         return server.Shutdown(slice_urn, creds)         
1070     
1071
1072     def get_ticket(self, options, args):
1073         """
1074         get a ticket for the specified slice
1075         """
1076         server = self.sliceapi()
1077         # slice urn
1078         slice_hrn, rspec_path = args[0], args[1]
1079         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1080         # creds
1081         slice_cred = self.slice_credential_string(slice_hrn)
1082         creds = [slice_cred]
1083         if options.delegate:
1084             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1085             creds.append(delegated_cred)
1086         # rspec
1087         rspec_file = self.get_rspec_file(rspec_path) 
1088         rspec = open(rspec_file).read()
1089         # options and call_id when supported
1090         api_options = {}
1091         api_options['call_id']=unique_call_id()
1092         # get ticket at the server
1093         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1094         # save
1095         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1096         self.logger.info("writing ticket to %s"%file)
1097         ticket = SfaTicket(string=ticket_string)
1098         ticket.save_to_file(filename=file, save_parents=True)
1099
1100     def redeem_ticket(self, options, args):
1101         """
1102         Connects to nodes in a slice and redeems a ticket
1103 (slice hrn is retrieved from the ticket)
1104         """
1105         ticket_file = args[0]
1106         
1107         # get slice hrn from the ticket
1108         # use this to get the right slice credential 
1109         ticket = SfaTicket(filename=ticket_file)
1110         ticket.decode()
1111         ticket_string = ticket.save_to_string(save_parents=True)
1112
1113         slice_hrn = ticket.gidObject.get_hrn()
1114         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1115         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1116         slice_cred = self.slice_credential_string(slice_hrn)
1117         
1118         # get a list of node hostnames from the RSpec 
1119         tree = etree.parse(StringIO(ticket.rspec))
1120         root = tree.getroot()
1121         hostnames = root.xpath("./network/site/node/hostname/text()")
1122         
1123         # create an xmlrpc connection to the component manager at each of these
1124         # components and gall redeem_ticket
1125         connections = {}
1126         for hostname in hostnames:
1127             try:
1128                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1129                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1130                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1131                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1132                                            timeout=self.options.timeout, verbose=self.options.debug)
1133                 server.RedeemTicket(ticket_string, slice_cred)
1134                 self.logger.info("Success")
1135             except socket.gaierror:
1136                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1137             except Exception, e:
1138                 self.logger.log_exc(e.message)
1139         return
1140
1141     def create_gid(self, options, args):
1142         """
1143         Create a GID (CreateGid)
1144         """
1145         if len(args) < 1:
1146             self.print_help()
1147             sys.exit(1)
1148         target_hrn = args[0]
1149         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1150         if options.file:
1151             filename = options.file
1152         else:
1153             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1154         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1155         GID(string=gid).save_to_file(filename)
1156          
1157
1158     def delegate(self, options, args):
1159         """
1160         (locally) create delegate credential for use by given hrn
1161         """
1162         delegee_hrn = args[0]
1163         if options.delegate_user:
1164             cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1165         elif options.delegate_slice:
1166             slice_cred = self.slice_credential_string(options.delegate_slice)
1167             cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1168         else:
1169             self.logger.warning("Must specify either --user or --slice <hrn>")
1170             return
1171         delegated_cred = Credential(string=cred)
1172         object_hrn = delegated_cred.get_gid_object().get_hrn()
1173         if options.delegate_user:
1174             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1175                                   + get_leaf(object_hrn) + ".cred")
1176         elif options.delegate_slice:
1177             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1178                                   + get_leaf(object_hrn) + ".cred")
1179
1180         delegated_cred.save_to_file(dest_fn, save_parents=True)
1181
1182         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1183     
1184     def get_trusted_certs(self, options, args):
1185         """
1186         return uhe trusted certs at this interface (get_trusted_certs)
1187         """ 
1188         trusted_certs = self.registry().get_trusted_certs()
1189         for trusted_cert in trusted_certs:
1190             gid = GID(string=trusted_cert)
1191             gid.dump()
1192             cert = Certificate(string=trusted_cert)
1193             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1194         return 
1195