tweak - can call sfaadmin --help outside an sfa box
[sfa.git] / sfa / client / sfaadmin.py
1 #!/usr/bin/python
2 import os
3 import sys
4 import copy
5 from pprint import pformat, PrettyPrinter
6 from optparse import OptionParser
7
8 from sfa.generic import Generic
9 from sfa.util.xrn import Xrn
10 from sfa.storage.record import Record 
11 from sfa.client.sfi import save_records_to_file
12 from sfa.trust.hierarchy import Hierarchy
13 from sfa.trust.gid import GID
14
15 pprinter = PrettyPrinter(indent=4)
16
17 try:
18     help_basedir=Hierarchy().basedir
19 except:
20     help_basedir='*unable to locate Hierarchy().basedir'
21
22 def optparse_listvalue_callback(option, opt, value, parser):
23     setattr(parser.values, option.dest, value.split(','))
24
25 def args(*args, **kwargs):
26     def _decorator(func):
27         func.__dict__.setdefault('options', []).insert(0, (args, kwargs))
28         return func
29     return _decorator
30
31 class Commands(object):
32     def _get_commands(self):
33         available_methods = []
34         for attrib in dir(self):
35             if callable(getattr(self, attrib)) and not attrib.startswith('_'):
36                 available_methods.append(attrib)
37         return available_methods         
38
39
40 class RegistryCommands(Commands):
41     def __init__(self, *args, **kwds):
42         self.api= Generic.the_flavour().make_api(interface='registry')
43  
44     def version(self):
45         """Display the Registry version""" 
46         version = self.api.manager.GetVersion(self.api, {})
47         pprinter.pprint(version)
48
49     @args('-x', '--xrn', dest='xrn', metavar='<xrn>', help='authority to list (hrn/urn - mandatory)') 
50     @args('-t', '--type', dest='type', metavar='<type>', help='object type', default=None) 
51     @args('-r', '--recursive', dest='recursive', metavar='<recursive>', help='list all child records', 
52           action='store_true', default=False) 
53     def list(self, xrn, type=None, recursive=False):
54         """List names registered at a given authority - possibly filtered by type"""
55         xrn = Xrn(xrn, type) 
56         options = {'recursive': recursive}    
57         records = self.api.manager.List(self.api, xrn.get_hrn(), options=options)
58         for record in records:
59             if not type or record['type'] == type:
60                 print "%s (%s)" % (record['hrn'], record['type'])
61
62
63     @args('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)') 
64     @args('-t', '--type', dest='type', metavar='<type>', help='object type', default=None) 
65     @args('-o', '--outfile', dest='outfile', metavar='<outfile>', help='save record to file') 
66     @args('-f', '--format', dest='format', metavar='<display>', type='choice', 
67           choices=('text', 'xml', 'simple'), help='display record in different formats') 
68     def show(self, xrn, type=None, format=None, outfile=None):
69         """Display details for a registered object"""
70         records = self.api.manager.Resolve(self.api, xrn, type, True)
71         for record in records:
72             sfa_record = Record(dict=record)
73             sfa_record.dump(format) 
74         if outfile:
75             save_records_to_file(outfile, records)  
76
77
78     def _record_dict(self, xrn=None, type=None, 
79                      url=None, description=None, email='', 
80                      key=None, 
81                      slices=[], researchers=[], pis=[]):
82         record_dict = {}
83         if xrn:
84             if type:
85                 xrn = Xrn(xrn, type)
86             else:
87                 xrn = Xrn(xrn)
88             record_dict['urn'] = xrn.get_urn()
89             record_dict['hrn'] = xrn.get_hrn()
90             record_dict['type'] = xrn.get_type()
91         if url:
92             record_dict['url'] = url
93         if description:
94             record_dict['description'] = description
95         if key:
96             try:
97                 pubkey = open(key, 'r').read()
98             except IOError:
99                 pubkey = key
100             record_dict['keys'] = [pubkey]
101         if slices:
102             record_dict['slices'] = slices
103         if researchers:
104             record_dict['researcher'] = researchers
105         if email:
106             record_dict['email'] = email
107         if pis:
108             record_dict['pi'] = pis
109         return record_dict
110
111     @args('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)') 
112     @args('-t', '--type', dest='type', metavar='<type>', help='object type', default=None) 
113     @args('-e', '--email', dest='email', default="",
114           help="email (mandatory for users)")
115     @args('-u', '--url', dest='url', metavar='<url>', default=None,
116           help="URL, useful for slices")
117     @args('-d', '--description', dest='description', metavar='<description>', 
118           help='Description, useful for slices', default=None)
119     @args('-k', '--key', dest='key', metavar='<key>', help='public key string or file', 
120           default=None)
121     @args('-s', '--slices', dest='slices', metavar='<slices>', help='slice xrns', 
122           default='', type="str", action='callback', callback=optparse_listvalue_callback)
123     @args('-r', '--researchers', dest='researchers', metavar='<researchers>', help='slice researchers', 
124           default='', type="str", action='callback', callback=optparse_listvalue_callback)
125     @args('-p', '--pis', dest='pis', metavar='<PIs>', 
126           help='Principal Investigators/Project Managers ', 
127           default='', type="str", action='callback', callback=optparse_listvalue_callback)
128     def register(self, xrn, type=None, url=None, description=None, key=None, slices='', 
129                  pis='', researchers='',email=''):
130         """Create a new Registry record"""
131         record_dict = self._record_dict(xrn=xrn, type=type, url=url, key=key, 
132                                         slices=slices, researchers=researchers, email=email, pis=pis)
133         self.api.manager.Register(self.api, record_dict)         
134
135
136     @args('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
137     @args('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
138     @args('-u', '--url', dest='url', metavar='<url>', help='URL', default=None)
139     @args('-d', '--description', dest='description', metavar='<description>',
140           help='Description', default=None)
141     @args('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
142           default=None)
143     @args('-s', '--slices', dest='slices', metavar='<slices>', help='slice xrns',
144           default='', type="str", action='callback', callback=optparse_listvalue_callback)
145     @args('-r', '--researchers', dest='researchers', metavar='<researchers>', help='slice researchers',
146           default='', type="str", action='callback', callback=optparse_listvalue_callback)
147     @args('-p', '--pis', dest='pis', metavar='<PIs>',
148           help='Principal Investigators/Project Managers ',
149           default='', type="str", action='callback', callback=optparse_listvalue_callback)
150     def update(self, xrn, type=None, url=None, description=None, key=None, slices='', 
151                pis='', researchers=''):
152         """Update an existing Registry record""" 
153         record_dict = self._record_dict(xrn=xrn, type=type, url=url, description=description, 
154                                         key=key, slices=slices, researchers=researchers, pis=pis)
155         self.api.manager.Update(self.api, record_dict)
156         
157     @args('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)') 
158     @args('-t', '--type', dest='type', metavar='<type>', help='object type', default=None) 
159     def remove(self, xrn, type=None):
160         """Remove given object from the registry"""
161         xrn = Xrn(xrn, type)
162         self.api.manager.Remove(self.api, xrn)            
163
164
165     @args('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)') 
166     @args('-t', '--type', dest='type', metavar='<type>', help='object type', default=None) 
167     def credential(self, xrn, type=None):
168         """Invoke GetCredential"""
169         cred = self.api.manager.GetCredential(self.api, xrn, type, self.api.hrn)
170         print cred
171    
172
173     def import_registry(self):
174         """Run the importer"""
175         from sfa.importer import Importer
176         importer = Importer()
177         importer.run()
178     
179     @args('-a', '--all', dest='all', metavar='<all>', action='store_true', default=False,
180           help='Remove all registry records and all files in %s area' % help_basedir)
181     @args('-c', '--certs', dest='certs', metavar='<certs>', action='store_true', default=False,
182           help='Remove all cached certs/gids found in %s' % help_basedir )
183     @args('-0', '--no-reinit', dest='reinit', metavar='<reinit>', action='store_false', default=True,
184           help='Prevents new DB schema from being installed after cleanup')
185     def nuke(self, all=False, certs=False, reinit=True):
186         """Cleanup local registry DB, plus various additional filesystem cleanups optionally"""
187         from sfa.storage.dbschema import DBSchema
188         from sfa.util.sfalogging import _SfaLogger
189         logger = _SfaLogger(logfile='/var/log/sfa_import.log', loggername='importlog')
190         logger.setLevelFromOptVerbose(self.api.config.SFA_API_LOGLEVEL)
191         logger.info("Purging SFA records from database")
192         dbschema=DBSchema()
193         dbschema.nuke()
194
195         # for convenience we re-create the schema here, so there's no need for an explicit
196         # service sfa restart
197         # however in some (upgrade) scenarios this might be wrong
198         if reinit:
199             logger.info("re-creating empty schema")
200             dbschema.init_or_upgrade()
201
202         # remove the server certificate and all gids found in /var/lib/sfa/authorities
203         if certs:
204             logger.info("Purging cached certificates")
205             for (dir, _, files) in os.walk('/var/lib/sfa/authorities'):
206                 for file in files:
207                     if file.endswith('.gid') or file == 'server.cert':
208                         path=dir+os.sep+file
209                         os.unlink(path)
210
211         # just remove all files that do not match 'server.key' or 'server.cert'
212         if all:
213             logger.info("Purging registry filesystem cache")
214             preserved_files = [ 'server.key', 'server.cert']
215             for (dir,_,files) in os.walk(Hierarchy().basedir):
216                 for file in files:
217                     if file in preserved_files: continue
218                     path=dir+os.sep+file
219                     os.unlink(path)
220         
221     
222 class CertCommands(Commands):
223     
224     def import_gid(self, xrn):
225         pass
226
227     @args('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
228     @args('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
229     @args('-o', '--outfile', dest='outfile', metavar='<outfile>', help='output file', default=None)
230     def export(self, xrn, type=None, outfile=None):
231         """Fetch an object's GID from the Registry"""  
232         from sfa.storage.alchemy import dbsession
233         from sfa.storage.model import RegRecord
234         hrn = Xrn(xrn).get_hrn()
235         request=dbsession.query(RegRecord).filter_by(hrn=hrn)
236         if type: request = request.filter_by(type=type)
237         record=request.first()
238         if record:
239             gid = GID(string=record.gid)
240         else:
241             # check the authorities hierarchy
242             hierarchy = Hierarchy()
243             try:
244                 auth_info = hierarchy.get_auth_info(hrn)
245                 gid = auth_info.gid_object
246             except:
247                 print "Record: %s not found" % hrn
248                 sys.exit(1)
249         # save to file
250         if not outfile:
251             outfile = os.path.abspath('./%s.gid' % gid.get_hrn())
252         gid.save_to_file(outfile, save_parents=True)
253         
254     @args('-g', '--gidfile', dest='gid', metavar='<gid>', help='path of gid file to display (mandatory)') 
255     def display(self, gidfile):
256         """Print contents of a GID file"""
257         gid_path = os.path.abspath(gidfile)
258         if not gid_path or not os.path.isfile(gid_path):
259             print "No such gid file: %s" % gidfile
260             sys.exit(1)
261         gid = GID(filename=gid_path)
262         gid.dump(dump_parents=True)
263     
264
265 class AggregateCommands(Commands):
266
267     def __init__(self, *args, **kwds):
268         self.api= Generic.the_flavour().make_api(interface='aggregate')
269    
270     def version(self):
271         """Display the Aggregate version"""
272         version = self.api.manager.GetVersion(self.api, {})
273         pprinter.pprint(version)
274
275     def slices(self):
276         """List the running slices at this Aggregate"""
277         print self.api.manager.ListSlices(self.api, [], {})
278
279     @args('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)') 
280     def status(self, xrn):
281         """Display the status of a slice or slivers"""
282         urn = Xrn(xrn, 'slice').get_urn()
283         status = self.api.manager.SliverStatus(self.api, urn, [], {})
284         pprinter.pprint(status)
285  
286     @args('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn', default=None)
287     @args('-r', '--rspec-version', dest='rspec_version', metavar='<rspec_version>', 
288           default='GENI', help='version/format of the resulting rspec response')  
289     def resources(self, xrn=None, rspec_version='GENI'):
290         """Display the available resources at an aggregate 
291 or the resources allocated by a slice"""  
292         options = {'geni_rspec_version': rspec_version}
293         if xrn:
294             options['geni_slice_urn'] = Xrn(xrn, 'slice').get_urn()
295         print options
296         resources = self.api.manager.ListResources(self.api, [], options)
297         print resources
298         
299     @args('-x', '--xrn', dest='xrn', metavar='<xrn>', help='slice hrn/urn (mandatory)')
300     @args('-r', '--rspec', dest='rspec', metavar='<rspec>', help='rspec file (mandatory)')  
301     @args('-u', '--user', dest='user', metavar='<user>', help='hrn/urn of slice user (mandatory)')  
302     @args('-k', '--key', dest='key', metavar='<key>', help="path to user's public key file (mandatory)")  
303     def create(self, xrn, rspec, user, key):
304         """Allocate slivers"""
305         xrn = Xrn(xrn, 'slice')
306         slice_urn=xrn.get_urn()
307         rspec_string = open(rspec).read()
308         user_xrn = Xrn(user, 'user')
309         user_urn = user_xrn.get_urn()
310         user_key_string = open(key).read()
311         users = [{'urn': user_urn, 'keys': [user_key_string]}]
312         options={}
313         self.api.manager.CreateSliver(self, slice_urn, [], rspec_string, users, options) 
314
315     @args('-x', '--xrn', dest='xrn', metavar='<xrn>', help='slice hrn/urn (mandatory)')
316     def delete(self, xrn):
317         """Delete slivers""" 
318         self.api.manager.DeleteSliver(self.api, xrn, [], {})
319  
320     @args('-x', '--xrn', dest='xrn', metavar='<xrn>', help='slice hrn/urn (mandatory)')
321     def start(self, xrn):
322         """Start slivers"""
323         self.api.manager.start_slice(self.api, xrn, [])
324
325     @args('-x', '--xrn', dest='xrn', metavar='<xrn>', help='slice hrn/urn (mandatory)')
326     def stop(self, xrn):
327         """Stop slivers"""
328         self.api.manager.stop_slice(self.api, xrn, [])      
329
330     @args('-x', '--xrn', dest='xrn', metavar='<xrn>', help='slice hrn/urn (mandatory)')
331     def reset(self, xrn):
332         """Reset sliver"""
333         self.api.manager.reset_slice(self.api, xrn)
334
335
336 #    @args('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn', default=None)
337 #    @args('-r', '--rspec', dest='rspec', metavar='<rspec>', help='request rspec', default=None)
338 #    def ticket(self, xrn, rspec):
339 #        pass
340
341
342
343 class SliceManagerCommands(AggregateCommands):
344     
345     def __init__(self, *args, **kwds):
346         self.api= Generic.the_flavour().make_api(interface='slicemgr')
347
348
349 class SfaAdmin:
350
351     CATEGORIES = {'certificate': CertCommands,
352                   'registry': RegistryCommands,
353                   'aggregate': AggregateCommands,
354                   'slicemgr': SliceManagerCommands}
355
356     def find_category (self, string):
357         for (k,v) in SfaAdmin.CATEGORIES.items():
358             if k.startswith(string): return k
359         for (k,v) in SfaAdmin.CATEGORIES.items():
360             if k.find(string) >=1: return k
361         return None
362
363     def summary_usage (self, category=None):
364         print "Usage:", self.script_name + " category action [<options>]"
365         if category and category in SfaAdmin.CATEGORIES: 
366             categories=[category]
367         else:
368             categories=SfaAdmin.CATEGORIES
369         for c in categories:
370             cls=SfaAdmin.CATEGORIES[c]
371             print "==================== category=%s"%c
372             names=cls.__dict__.keys()
373             names.sort()
374             for name in names:
375                 method=cls.__dict__[name]
376                 if name.startswith('_'): continue
377                 margin=15
378                 format="%%-%ds"%margin
379                 print "%-15s"%name,
380                 doc=getattr(method,'__doc__',None)
381                 if not doc: 
382                     print "<missing __doc__>"
383                     continue
384                 lines=[line.strip() for line in doc.split("\n")]
385                 line1=lines.pop(0)
386                 print line1
387                 for extra_line in lines: print margin*" ",extra_line
388         sys.exit(2)
389
390     def main(self):
391         argv = copy.deepcopy(sys.argv)
392         self.script_name = argv.pop(0)
393         # ensure category is specified    
394         if len(argv) < 1:
395             self.summary_usage()
396
397         # ensure category is valid
398         category_str = argv.pop(0)
399         category=self.find_category (category_str)
400         if not category:
401             self.summary_usage()
402
403         usage = "%%prog %s action [options]" % (category)
404         parser = OptionParser(usage=usage)
405         command_class =  SfaAdmin.CATEGORIES.get(category, None)
406         if not command_class:
407             self.summary_usage(category)
408     
409         # ensure command is valid      
410         command_instance = command_class()
411         actions = command_instance._get_commands()
412         if len(argv) < 1:
413             action = '__call__'
414         else:
415             action = argv.pop(0)
416     
417         if hasattr(command_instance, action):
418             command = getattr(command_instance, action)
419         else:
420             self.summary_usage(category)
421
422         # ensure options are valid
423         options = getattr(command, 'options', [])
424         usage = "%%prog %s %s [options]" % (category, action)
425         parser = OptionParser(usage=usage)
426         for arg, kwd in options:
427             parser.add_option(*arg, **kwd)
428         (opts, cmd_args) = parser.parse_args(argv)
429         cmd_kwds = vars(opts)
430
431         # dont overrride meth
432         for k, v in cmd_kwds.items():
433             if v is None:
434                 del cmd_kwds[k]
435
436         # execute command
437         try:
438             #print "invoking %s *=%s **=%s"%(command.__name__,cmd_args, cmd_kwds)
439             command(*cmd_args, **cmd_kwds)
440             sys.exit(0)
441         except TypeError:
442             print "Possible wrong number of arguments supplied"
443             #import traceback
444             #traceback.print_exc()
445             print command.__doc__
446             parser.print_help()
447             #raise
448         except Exception:
449             print "Command failed, please check log for more info"
450             raise