first step towards retro-adding support for v1 aggregates in client
[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     ### resurrect this temporarily
618     def server_supports_options_arg(self, server):
619         """
620         Returns true if server support the optional call_id arg, false otherwise. 
621         """
622         server_version = self.get_cached_server_version(server)
623         return True
624         # need to rewrite this 
625         if 'sfa' in server_version and 'code_tag' in server_version:
626             code_tag = server_version['code_tag']
627             code_tag_parts = code_tag.split("-")
628             
629             version_parts = code_tag_parts[0].split(".")
630             major, minor = version_parts[0], version_parts[1]
631             rev = code_tag_parts[1]
632             if int(major) >= 1:
633                 if int(minor) >= 2:
634                     return True
635         return False                
636
637     ### ois = options if supported
638     def ois (self, server, option_dict):
639         if self.server_supports_options_arg (server) : return [option_dict]
640         else: return []
641
642     ######################################## miscell utilities
643     def get_rspec_file(self, rspec):
644        if (os.path.isabs(rspec)):
645           file = rspec
646        else:
647           file = os.path.join(self.options.sfi_dir, rspec)
648        if (os.path.isfile(file)):
649           return file
650        else:
651           self.logger.critical("No such rspec file %s"%rspec)
652           sys.exit(1)
653     
654     def get_record_file(self, record):
655        if (os.path.isabs(record)):
656           file = record
657        else:
658           file = os.path.join(self.options.sfi_dir, record)
659        if (os.path.isfile(file)):
660           return file
661        else:
662           self.logger.critical("No such registry record file %s"%record)
663           sys.exit(1)
664     
665
666     #==========================================================================
667     # Following functions implement the commands
668     #
669     # Registry-related commands
670     #==========================================================================
671   
672     def version(self, opts, args):
673         """
674         display an SFA server version (GetVersion) 
675 or version information about sfi itself
676         """
677         if opts.version_local:
678             version=version_core()
679         else:
680             if opts.version_registry:
681                 server=self.registry()
682             else:
683                 server = self.server_proxy_from_opts(opts)
684             result = server.GetVersion()
685             version = ReturnValue.get_value(result)
686         for (k,v) in version.iteritems():
687             print "%-20s: %s"%(k,v)
688         if opts.file:
689             save_variable_to_file(version, opts.file, opts.fileformat)
690
691     def list(self, opts, args):
692         """
693         list entries in named authority registry (List)
694         """
695         if len(args)!= 1:
696             self.print_help()
697             sys.exit(1)
698         hrn = args[0]
699         try:
700             list = self.registry().List(hrn, self.my_credential_string)
701         except IndexError:
702             raise Exception, "Not enough parameters for the 'list' command"
703
704         # filter on person, slice, site, node, etc.
705         # THis really should be in the self.filter_records funct def comment...
706         list = filter_records(opts.type, list)
707         for record in list:
708             print "%s (%s)" % (record['hrn'], record['type'])
709         if opts.file:
710             save_records_to_file(opts.file, list, opts.fileformat)
711         return
712     
713     def show(self, opts, args):
714         """
715         show details about named registry record (Resolve)
716         """
717         if len(args)!= 1:
718             self.print_help()
719             sys.exit(1)
720         hrn = args[0]
721         records = self.registry().Resolve(hrn, self.my_credential_string)
722         records = filter_records(opts.type, records)
723         if not records:
724             self.logger.error("No record of type %s"% opts.type)
725         for record in records:
726             if record['type'] in ['user']:
727                 record = UserRecord(dict=record)
728             elif record['type'] in ['slice']:
729                 record = SliceRecord(dict=record)
730             elif record['type'] in ['node']:
731                 record = NodeRecord(dict=record)
732             elif record['type'].startswith('authority'):
733                 record = AuthorityRecord(dict=record)
734             else:
735                 record = SfaRecord(dict=record)
736             if (opts.format == "text"): 
737                 record.dump()  
738             else:
739                 print record.save_to_string() 
740         if opts.file:
741             save_records_to_file(opts.file, records, opts.fileformat)
742         return
743     
744     def add(self, opts, args):
745         "add record into registry from xml file (Register)"
746         auth_cred = self.my_authority_credential_string()
747         if len(args)!=1:
748             self.print_help()
749             sys.exit(1)
750         record_filepath = args[0]
751         rec_file = self.get_record_file(record_filepath)
752         record = load_record_from_file(rec_file).as_dict()
753         return self.registry().Register(record, auth_cred)
754     
755     def update(self, opts, args):
756         "update record into registry from xml file (Update)"
757         if len(args)!=1:
758             self.print_help()
759             sys.exit(1)
760         rec_file = self.get_record_file(args[0])
761         record = load_record_from_file(rec_file)
762         if record['type'] == "user":
763             if record.get_name() == self.user:
764                 cred = self.my_credential_string
765             else:
766                 cred = self.my_authority_credential_string()
767         elif record['type'] in ["slice"]:
768             try:
769                 cred = self.slice_credential_string(record.get_name())
770             except ServerException, e:
771                # XXX smbaker -- once we have better error return codes, update this
772                # to do something better than a string compare
773                if "Permission error" in e.args[0]:
774                    cred = self.my_authority_credential_string()
775                else:
776                    raise
777         elif record.get_type() in ["authority"]:
778             cred = self.my_authority_credential_string()
779         elif record.get_type() == 'node':
780             cred = self.my_authority_credential_string()
781         else:
782             raise "unknown record type" + record.get_type()
783         record = record.as_dict()
784         return self.registry().Update(record, cred)
785   
786     def remove(self, opts, args):
787         "remove registry record by name (Remove)"
788         auth_cred = self.my_authority_credential_string()
789         if len(args)!=1:
790             self.print_help()
791             sys.exit(1)
792         hrn = args[0]
793         type = opts.type 
794         if type in ['all']:
795             type = '*'
796         return self.registry().Remove(hrn, auth_cred, type)
797     
798     # ==================================================================
799     # Slice-related commands
800     # ==================================================================
801
802     def slices(self, opts, args):
803         "list instantiated slices (ListSlices) - returns urn's"
804         creds = [self.my_credential_string]
805         if opts.delegate:
806             delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
807             creds.append(delegated_cred)  
808         server = self.server_proxy_from_opts(opts)
809         api_options = {}
810         api_options ['call_id'] = unique_call_id()
811         result = server.ListSlices(creds, *self.ois(server,api_options))
812         value = ReturnValue.get_value(result)
813         display_list(value)
814         return
815     
816     # show rspec for named slice
817     def resources(self, opts, args):
818         """
819         with no arg, discover available resources,
820 or currently provisioned resources  (ListResources)
821         """
822         server = self.server_proxy_from_opts(opts)
823    
824         api_options = {}
825         api_options ['call_id'] = unique_call_id()
826         #panos add info api_options
827         if opts.info:
828             api_options['info'] = opts.info
829         
830         if args:
831             cred = self.slice_credential_string(args[0])
832             hrn = args[0]
833             api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
834         else:
835             cred = self.my_credential_string
836      
837         creds = [cred]
838         if opts.delegate:
839             delegated_cred = self.delegate_cred(cred, get_authority(self.authority))
840             creds.append(delegated_cred)
841         if opts.rspec_version:
842             version_manager = VersionManager()
843             server_version = self.get_cached_server_version(server)
844             if 'sfa' in server_version:
845                 # just request the version the client wants 
846                 api_options['geni_rspec_version'] = version_manager.get_version(opts.rspec_version).to_dict()
847             else:
848                 # this must be a protogeni aggregate. We should request a v2 ad rspec
849                 # regardless of what the client user requested 
850                 api_options['geni_rspec_version'] = version_manager.get_version('ProtoGENI 2').to_dict()     
851         else:
852             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
853
854         result = server.ListResources(creds, *self.ois(server,api_options))
855         value = ReturnValue.get_value(result)
856         if opts.file is None:
857             display_rspec(value, opts.format)
858         else:
859             save_rspec_to_file(value, opts.file)
860         return
861
862     def create(self, opts, args):
863         """
864         create or update named slice with given rspec
865         """
866         server = self.server_proxy_from_opts(opts)
867         server_version = self.get_cached_server_version(server)
868         slice_hrn = args[0]
869         slice_urn = hrn_to_urn(slice_hrn, 'slice')
870         slice_cred = self.slice_credential_string(slice_hrn)
871         delegated_cred = None
872         if server_version.get('interface') == 'slicemgr':
873             # delegate our cred to the slice manager
874             # do not delegate cred to slicemgr...not working at the moment
875             pass
876             #if server_version.get('hrn'):
877             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
878             #elif server_version.get('urn'):
879             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
880                  
881         rspec_file = self.get_rspec_file(args[1])
882         rspec = open(rspec_file).read()
883
884         # need to pass along user keys to the aggregate.
885         # users = [
886         #  { urn: urn:publicid:IDN+emulab.net+user+alice
887         #    keys: [<ssh key A>, <ssh key B>]
888         #  }]
889         users = []
890         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
891         if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
892             slice_record = slice_records[0]
893             user_hrns = slice_record['researcher']
894             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
895             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
896
897             if 'sfa' not in server_version:
898                 users = pg_users_arg(user_records)
899                 rspec = RSpec(rspec)
900                 rspec.filter({'component_manager_id': server_version['urn']})
901                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
902                 creds = [slice_cred]
903             else:
904                 users = sfa_users_arg(user_records, slice_record)
905                 creds = [slice_cred]
906                 if delegated_cred:
907                     creds.append(delegated_cred)
908         # do not append users, keys, or slice tags. Anything 
909         # not contained in this request will be removed from the slice 
910         api_options = {}
911         api_options ['append'] = False
912         api_options ['call_id'] = unique_call_id()
913         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server,api_options))
914         value = ReturnValue.get_value(result)
915         if opts.file is None:
916             print value
917         else:
918             save_rspec_to_file (value, opts.file)
919         return value
920
921     def delete(self, opts, args):
922         """
923         delete named slice (DeleteSliver)
924         """
925         slice_hrn = args[0]
926         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
927         slice_cred = self.slice_credential_string(slice_hrn)
928         creds = [slice_cred]
929         if opts.delegate:
930             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
931             creds.append(delegated_cred)
932         server = self.server_proxy_from_opts(opts)
933         api_options = {}
934         api_options ['call_id'] = unique_call_id()
935         return server.DeleteSliver(slice_urn, creds, *self.ois(server,api_options))
936   
937     def status(self, opts, args):
938         """
939         retrieve slice status (SliverStatus)
940         """
941         slice_hrn = args[0]
942         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
943         slice_cred = self.slice_credential_string(slice_hrn)
944         creds = [slice_cred]
945         if opts.delegate:
946             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
947             creds.append(delegated_cred)
948         server = self.server_proxy_from_opts(opts)
949         api_options = {}
950         api_options ['call_id'] = unique_call_id()
951         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
952         value = ReturnValue.get_value(result)
953         print value
954         if opts.file:
955             save_variable_to_file(value, opts.file, opts.fileformat)
956
957     def start(self, opts, args):
958         """
959         start named slice (Start)
960         """
961         slice_hrn = args[0]
962         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
963         slice_cred = self.slice_credential_string(args[0])
964         creds = [slice_cred]
965         if opts.delegate:
966             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
967             creds.append(delegated_cred)
968         server = self.server_proxy_from_opts(opts)
969         # xxx Thierry - does this not need an api_options as well
970         return server.Start(slice_urn, creds)
971     
972     def stop(self, opts, args):
973         """
974         stop named slice (Stop)
975         """
976         slice_hrn = args[0]
977         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
978         slice_cred = self.slice_credential_string(args[0])
979         creds = [slice_cred]
980         if opts.delegate:
981             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
982             creds.append(delegated_cred)
983         server = self.server_proxy_from_opts(opts)
984         return server.Stop(slice_urn, creds)
985     
986     # reset named slice
987     def reset(self, opts, args):
988         """
989         reset named slice (reset_slice)
990         """
991         slice_hrn = args[0]
992         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
993         server = self.server_proxy_from_opts(opts)
994         slice_cred = self.slice_credential_string(args[0])
995         creds = [slice_cred]
996         if opts.delegate:
997             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
998             creds.append(delegated_cred)
999         return server.reset_slice(creds, slice_urn)
1000
1001     def renew(self, opts, args):
1002         """
1003         renew slice (RenewSliver)
1004         """
1005         slice_hrn = args[0]
1006         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1007         server = self.server_proxy_from_opts(opts)
1008         slice_cred = self.slice_credential_string(args[0])
1009         creds = [slice_cred]
1010         if opts.delegate:
1011             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1012             creds.append(delegated_cred)
1013         time = args[1]
1014         api_options = {}
1015         api_options ['call_id'] = unique_call_id()
1016         result =  server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1017         value = ReturnValue.get_value(result)
1018         return value
1019
1020
1021     def shutdown(self, opts, args):
1022         """
1023         shutdown named slice (Shutdown)
1024         """
1025         slice_hrn = args[0]
1026         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1027         slice_cred = self.slice_credential_string(slice_hrn)
1028         creds = [slice_cred]
1029         if opts.delegate:
1030             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1031             creds.append(delegated_cred)
1032         server = self.server_proxy_from_opts(opts)
1033         return server.Shutdown(slice_urn, creds)         
1034     
1035
1036     def get_ticket(self, opts, args):
1037         """
1038         get a ticket for the specified slice
1039         """
1040         slice_hrn, rspec_path = args[0], args[1]
1041         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1042         slice_cred = self.slice_credential_string(slice_hrn)
1043         creds = [slice_cred]
1044         if opts.delegate:
1045             delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1046             creds.append(delegated_cred)
1047         rspec_file = self.get_rspec_file(rspec_path) 
1048         rspec = open(rspec_file).read()
1049         server = self.server_proxy_from_opts(opts)
1050         ticket_string = server.GetTicket(slice_urn, creds, rspec, [])
1051         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1052         self.logger.info("writing ticket to %s"%file)
1053         ticket = SfaTicket(string=ticket_string)
1054         ticket.save_to_file(filename=file, save_parents=True)
1055
1056     def redeem_ticket(self, opts, args):
1057         """
1058         Connects to nodes in a slice and redeems a ticket
1059 (slice hrn is retrieved from the ticket)
1060         """
1061         ticket_file = args[0]
1062         
1063         # get slice hrn from the ticket
1064         # use this to get the right slice credential 
1065         ticket = SfaTicket(filename=ticket_file)
1066         ticket.decode()
1067         slice_hrn = ticket.gidObject.get_hrn()
1068         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1069         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1070         slice_cred = self.slice_credential_string(slice_hrn)
1071         
1072         # get a list of node hostnames from the RSpec 
1073         tree = etree.parse(StringIO(ticket.rspec))
1074         root = tree.getroot()
1075         hostnames = root.xpath("./network/site/node/hostname/text()")
1076         
1077         # create an xmlrpc connection to the component manager at each of these
1078         # components and gall redeem_ticket
1079         connections = {}
1080         for hostname in hostnames:
1081             try:
1082                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1083                 server = self.server_proxy(hostname, CM_PORT, self.private_key, \
1084                                                self.my_gid, verbose=self.options.debug)
1085                 server.RedeemTicket(ticket.save_to_string(save_parents=True), slice_cred)
1086                 self.logger.info("Success")
1087             except socket.gaierror:
1088                 self.logger.error("redeem_ticket failed: Component Manager not accepting requests")
1089             except Exception, e:
1090                 self.logger.log_exc(e.message)
1091         return
1092
1093     def create_gid(self, opts, args):
1094         """
1095         Create a GID (CreateGid)
1096         """
1097         if len(args) < 1:
1098             self.print_help()
1099             sys.exit(1)
1100         target_hrn = args[0]
1101         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1102         if opts.file:
1103             filename = opts.file
1104         else:
1105             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1106         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1107         GID(string=gid).save_to_file(filename)
1108          
1109
1110     def delegate(self, opts, args):
1111         """
1112         (locally) create delegate credential for use by given hrn
1113         """
1114         delegee_hrn = args[0]
1115         if opts.delegate_user:
1116             cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1117         elif opts.delegate_slice:
1118             slice_cred = self.slice_credential_string(opts.delegate_slice)
1119             cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1120         else:
1121             self.logger.warning("Must specify either --user or --slice <hrn>")
1122             return
1123         delegated_cred = Credential(string=cred)
1124         object_hrn = delegated_cred.get_gid_object().get_hrn()
1125         if opts.delegate_user:
1126             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1127                                   + get_leaf(object_hrn) + ".cred")
1128         elif opts.delegate_slice:
1129             dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1130                                   + get_leaf(object_hrn) + ".cred")
1131
1132         delegated_cred.save_to_file(dest_fn, save_parents=True)
1133
1134         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1135     
1136     def get_trusted_certs(self, opts, args):
1137         """
1138         return uhe trusted certs at this interface (get_trusted_certs)
1139         """ 
1140         trusted_certs = self.registry().get_trusted_certs()
1141         for trusted_cert in trusted_certs:
1142             gid = GID(string=trusted_cert)
1143             gid.dump()
1144             cert = Certificate(string=trusted_cert)
1145             self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1146         return 
1147