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