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