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