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