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