2 from __future__ import print_function
7 from pprint import pformat, PrettyPrinter
8 from optparse import OptionParser
10 from sfa.generic import Generic
11 from sfa.util.xrn import Xrn
12 from sfa.storage.record import Record
14 from sfa.trust.hierarchy import Hierarchy
15 from sfa.trust.gid import GID
16 from sfa.trust.certificate import convert_public_key
18 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, terminal_render, filter_records
19 from sfa.client.candidates import Candidates
20 from sfa.client.sfi import save_records_to_file
22 pprinter = PrettyPrinter(indent=4)
25 help_basedir=Hierarchy().basedir
27 help_basedir='*unable to locate Hierarchy().basedir'
29 def add_options(*args, **kwargs):
31 func.__dict__.setdefault('add_options', []).insert(0, (args, kwargs))
35 class Commands(object):
36 def _get_commands(self):
38 for attrib in dir(self):
39 if callable(getattr(self, attrib)) and not attrib.startswith('_'):
40 command_names.append(attrib)
44 class RegistryCommands(Commands):
45 def __init__(self, *args, **kwds):
46 self.api= Generic.the_flavour().make_api(interface='registry')
49 """Display the Registry version"""
50 version = self.api.manager.GetVersion(self.api, {})
51 pprinter.pprint(version)
53 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='authority to list (hrn/urn - mandatory)')
54 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default='all')
55 @add_options('-r', '--recursive', dest='recursive', metavar='<recursive>', help='list all child records',
56 action='store_true', default=False)
57 @add_options('-v', '--verbose', dest='verbose', action='store_true', default=False)
58 def list(self, xrn, type=None, recursive=False, verbose=False):
59 """List names registered at a given authority - possibly filtered by type"""
61 options_dict = {'recursive': recursive}
62 records = self.api.manager.List(self.api, xrn.get_hrn(), options=options_dict)
63 list = filter_records(type, records)
64 # terminal_render expects an options object
67 options.verbose=verbose
68 terminal_render (list, options)
71 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
72 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
73 @add_options('-o', '--outfile', dest='outfile', metavar='<outfile>', help='save record to file')
74 @add_options('-f', '--format', dest='format', metavar='<display>', type='choice',
75 choices=('text', 'xml', 'simple'), help='display record in different formats')
76 def show(self, xrn, type=None, format=None, outfile=None):
77 """Display details for a registered object"""
78 records = self.api.manager.Resolve(self.api, xrn, type, details=True)
79 for record in records:
80 sfa_record = Record(dict=record)
81 sfa_record.dump(format)
83 save_records_to_file(outfile, records)
86 def _record_dict(self, xrn, type, email, key,
87 slices, researchers, pis,
88 url, description, extras):
95 record_dict['urn'] = xrn.get_urn()
96 record_dict['hrn'] = xrn.get_hrn()
97 record_dict['type'] = xrn.get_type()
99 record_dict['url'] = url
101 record_dict['description'] = description
104 pubkey = open(key, 'r').read()
107 record_dict['reg-keys'] = [pubkey]
109 record_dict['slices'] = slices
111 record_dict['reg-researchers'] = researchers
113 record_dict['email'] = email
115 record_dict['reg-pis'] = pis
117 record_dict.update(extras)
121 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn', default=None)
122 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type (mandatory)',)
123 @add_options('-a', '--all', dest='all', metavar='<all>', action='store_true', default=False, help='check all users GID')
124 @add_options('-v', '--verbose', dest='verbose', metavar='<verbose>', action='store_true', default=False, help='verbose mode: display user\'s hrn ')
125 def check_gid(self, xrn=None, type=None, all=None, verbose=None):
126 """Check the correspondance between the GID and the PubKey"""
129 from sfa.storage.model import RegRecord
130 db_query = self.api.dbsession().query(RegRecord).filter_by(type=type)
132 hrn = Xrn(xrn).get_hrn()
133 db_query = db_query.filter_by(hrn=hrn)
135 print("Use either -a or -x <xrn>, not both !!!")
137 elif not all and not xrn:
138 print("Use either -a or -x <xrn>, one of them is mandatory !!!")
141 records = db_query.all()
143 print("No Record found")
150 for record in records:
151 # get the pubkey stored in SFA DB
153 db_pubkey_str = record.reg_keys[0].key
155 db_pubkey_obj = convert_public_key(db_pubkey_str)
157 ERROR.append(record.hrn)
160 NOKEY.append(record.hrn)
163 # get the pubkey from the gid
165 gid_obj = GID(string = gid_str)
166 gid_pubkey_obj = gid_obj.get_pubkey()
168 # Check if gid_pubkey_obj and db_pubkey_obj are the same
169 check = gid_pubkey_obj.is_same(db_pubkey_obj)
171 OK.append(record.hrn)
173 NOK.append(record.hrn)
176 print("Users NOT having a PubKey: %s\n\
177 Users having a non RSA PubKey: %s\n\
178 Users having a GID/PubKey correpondence OK: %s\n\
179 Users having a GID/PubKey correpondence Not OK: %s\n"%(len(NOKEY), len(ERROR), len(OK), len(NOK)))
181 print("Users NOT having a PubKey: %s and are: \n%s\n\n\
182 Users having a non RSA PubKey: %s and are: \n%s\n\n\
183 Users having a GID/PubKey correpondence OK: %s and are: \n%s\n\n\
184 Users having a GID/PubKey correpondence NOT OK: %s and are: \n%s\n\n"%(len(NOKEY),NOKEY, len(ERROR), ERROR, len(OK), OK, len(NOK), NOK))
188 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
189 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
190 @add_options('-e', '--email', dest='email', default="",
191 help="email (mandatory for users)")
192 @add_options('-u', '--url', dest='url', metavar='<url>', default=None,
193 help="URL, useful for slices")
194 @add_options('-d', '--description', dest='description', metavar='<description>',
195 help='Description, useful for slices', default=None)
196 @add_options('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
198 @add_options('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
199 default='', type="str", action='callback', callback=optparse_listvalue_callback)
200 @add_options('-r', '--researchers', dest='researchers', metavar='<researchers>', help='Set/replace slice researchers',
201 default='', type="str", action='callback', callback=optparse_listvalue_callback)
202 @add_options('-p', '--pis', dest='pis', metavar='<PIs>',
203 help='Set/replace Principal Investigators/Project Managers',
204 default='', type="str", action='callback', callback=optparse_listvalue_callback)
205 @add_options('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
206 action="callback", callback=optparse_dictvalue_callback, nargs=1,
207 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
208 def register(self, xrn, type=None, email='', key=None,
209 slices='', pis='', researchers='',
210 url=None, description=None, extras={}):
211 """Create a new Registry record"""
212 record_dict = self._record_dict(xrn=xrn, type=type, email=email, key=key,
213 slices=slices, researchers=researchers, pis=pis,
214 url=url, description=description, extras=extras)
215 self.api.manager.Register(self.api, record_dict)
218 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
219 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
220 @add_options('-u', '--url', dest='url', metavar='<url>', help='URL', default=None)
221 @add_options('-d', '--description', dest='description', metavar='<description>',
222 help='Description', default=None)
223 @add_options('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
225 @add_options('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
226 default='', type="str", action='callback', callback=optparse_listvalue_callback)
227 @add_options('-r', '--researchers', dest='researchers', metavar='<researchers>', help='Set/replace slice researchers',
228 default='', type="str", action='callback', callback=optparse_listvalue_callback)
229 @add_options('-p', '--pis', dest='pis', metavar='<PIs>',
230 help='Set/replace Principal Investigators/Project Managers',
231 default='', type="str", action='callback', callback=optparse_listvalue_callback)
232 @add_options('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
233 action="callback", callback=optparse_dictvalue_callback, nargs=1,
234 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
235 def update(self, xrn, type=None, email='', key=None,
236 slices='', pis='', researchers='',
237 url=None, description=None, extras={}):
238 """Update an existing Registry record"""
239 record_dict = self._record_dict(xrn=xrn, type=type, email=email, key=key,
240 slices=slices, researchers=researchers, pis=pis,
241 url=url, description=description, extras=extras)
242 self.api.manager.Update(self.api, record_dict)
244 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
245 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
246 def remove(self, xrn, type=None):
247 """Remove given object from the registry"""
249 self.api.manager.Remove(self.api, xrn)
252 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
253 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
254 def credential(self, xrn, type=None):
255 """Invoke GetCredential"""
256 cred = self.api.manager.GetCredential(self.api, xrn, type, self.api.hrn)
260 def import_registry(self):
261 """Run the importer"""
262 from sfa.importer import Importer
263 importer = Importer()
267 """Initialize or upgrade the db"""
268 from sfa.storage.dbschema import DBSchema
270 dbschema.init_or_upgrade()
272 @add_options('-a', '--all', dest='all', metavar='<all>', action='store_true', default=False,
273 help='Remove all registry records and all files in %s area' % help_basedir)
274 @add_options('-c', '--certs', dest='certs', metavar='<certs>', action='store_true', default=False,
275 help='Remove all cached certs/gids found in %s' % help_basedir )
276 @add_options('-0', '--no-reinit', dest='reinit', metavar='<reinit>', action='store_false', default=True,
277 help='Prevents new DB schema from being installed after cleanup')
278 def nuke(self, all=False, certs=False, reinit=True):
279 """Cleanup local registry DB, plus various additional filesystem cleanups optionally"""
280 from sfa.storage.dbschema import DBSchema
281 from sfa.util.sfalogging import _SfaLogger
282 logger = _SfaLogger(logfile='/var/log/sfa_import.log', loggername='importlog')
283 logger.setLevelFromOptVerbose(self.api.config.SFA_API_LOGLEVEL)
284 logger.info("Purging SFA records from database")
288 # for convenience we re-create the schema here, so there's no need for an explicit
289 # service sfa restart
290 # however in some (upgrade) scenarios this might be wrong
292 logger.info("re-creating empty schema")
293 dbschema.init_or_upgrade()
295 # remove the server certificate and all gids found in /var/lib/sfa/authorities
297 logger.info("Purging cached certificates")
298 for (dir, _, files) in os.walk('/var/lib/sfa/authorities'):
300 if file.endswith('.gid') or file == 'server.cert':
304 # just remove all files that do not match 'server.key' or 'server.cert'
306 logger.info("Purging registry filesystem cache")
307 preserved_files = [ 'server.key', 'server.cert']
308 for (dir,_,files) in os.walk(Hierarchy().basedir):
310 if file in preserved_files: continue
315 class CertCommands(Commands):
317 def __init__(self, *args, **kwds):
318 self.api= Generic.the_flavour().make_api(interface='registry')
320 def import_gid(self, xrn):
323 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
324 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
325 @add_options('-o', '--outfile', dest='outfile', metavar='<outfile>', help='output file', default=None)
326 def export(self, xrn, type=None, outfile=None):
327 """Fetch an object's GID from the Registry"""
328 from sfa.storage.model import RegRecord
329 hrn = Xrn(xrn).get_hrn()
330 request=self.api.dbsession().query(RegRecord).filter_by(hrn=hrn)
331 if type: request = request.filter_by(type=type)
332 record=request.first()
334 gid = GID(string=record.gid)
336 # check the authorities hierarchy
337 hierarchy = Hierarchy()
339 auth_info = hierarchy.get_auth_info(hrn)
340 gid = auth_info.gid_object
342 print("Record: %s not found" % hrn)
346 outfile = os.path.abspath('./%s.gid' % gid.get_hrn())
347 gid.save_to_file(outfile, save_parents=True)
349 @add_options('-g', '--gidfile', dest='gid', metavar='<gid>', help='path of gid file to display (mandatory)')
350 def display(self, gidfile):
351 """Print contents of a GID file"""
352 gid_path = os.path.abspath(gidfile)
353 if not gid_path or not os.path.isfile(gid_path):
354 print("No such gid file: %s" % gidfile)
356 gid = GID(filename=gid_path)
357 gid.dump(dump_parents=True)
360 class AggregateCommands(Commands):
362 def __init__(self, *args, **kwds):
363 self.api= Generic.the_flavour().make_api(interface='aggregate')
366 """Display the Aggregate version"""
367 version = self.api.manager.GetVersion(self.api, {})
368 pprinter.pprint(version)
370 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
371 def status(self, xrn):
372 """Retrieve the status of the slivers belonging to the named slice (Status)"""
373 urns = [Xrn(xrn, 'slice').get_urn()]
374 status = self.api.manager.Status(self.api, urns, [], {})
375 pprinter.pprint(status)
377 @add_options('-r', '--rspec-version', dest='rspec_version', metavar='<rspec_version>',
378 default='GENI', help='version/format of the resulting rspec response')
379 def resources(self, rspec_version='GENI'):
380 """Display the available resources at an aggregate"""
381 options = {'geni_rspec_version': rspec_version}
383 resources = self.api.manager.ListResources(self.api, [], options)
387 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='slice hrn/urn (mandatory)')
388 @add_options('-r', '--rspec', dest='rspec', metavar='<rspec>', help='rspec file (mandatory)')
389 def allocate(self, xrn, rspec):
390 """Allocate slivers"""
391 xrn = Xrn(xrn, 'slice')
392 slice_urn=xrn.get_urn()
393 rspec_string = open(rspec).read()
395 manifest = self.api.manager.Allocate(self.api, slice_urn, [], rspec_string, options)
399 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='slice hrn/urn (mandatory)')
400 def provision(self, xrn):
401 """Provision slivers"""
402 xrn = Xrn(xrn, 'slice')
403 slice_urn=xrn.get_urn()
405 manifest = self.api.manager.provision(self.api, [slice_urn], [], options)
410 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='slice hrn/urn (mandatory)')
411 def delete(self, xrn):
413 self.api.manager.Delete(self.api, [xrn], [], {})
418 class SliceManagerCommands(AggregateCommands):
420 def __init__(self, *args, **kwds):
421 self.api= Generic.the_flavour().make_api(interface='slicemgr')
426 CATEGORIES = {'certificate': CertCommands,
427 'registry': RegistryCommands,
428 'aggregate': AggregateCommands,
429 'slicemgr': SliceManagerCommands}
431 # returns (name,class) or (None,None)
432 def find_category (self, input):
433 full_name=Candidates (SfaAdmin.CATEGORIES.keys()).only_match(input)
434 if not full_name: return (None,None)
435 return (full_name,SfaAdmin.CATEGORIES[full_name])
437 def summary_usage (self, category=None):
438 print("Usage:", self.script_name + " category command [<options>]")
439 if category and category in SfaAdmin.CATEGORIES:
440 categories=[category]
442 categories=SfaAdmin.CATEGORIES
444 cls=SfaAdmin.CATEGORIES[c]
445 print("==================== category=%s"%c)
446 names=cls.__dict__.keys()
449 method=cls.__dict__[name]
450 if name.startswith('_'): continue
452 format="%%-%ds"%margin
453 print("%-15s"%name, end=' ')
454 doc=getattr(method,'__doc__',None)
456 print("<missing __doc__>")
458 lines=[line.strip() for line in doc.split("\n")]
461 for extra_line in lines: print(margin*" ",extra_line)
465 argv = copy.deepcopy(sys.argv)
466 self.script_name = argv.pop(0)
467 # ensure category is specified
471 # ensure category is valid
472 category_input = argv.pop(0)
473 (category_name, category_class) = self.find_category (category_input)
474 if not category_name or not category_class:
475 self.summary_usage(category_name)
477 usage = "%%prog %s command [options]" % (category_name)
478 parser = OptionParser(usage=usage)
480 # ensure command is valid
481 category_instance = category_class()
482 commands = category_instance._get_commands()
484 # xxx what is this about ?
485 command_name = '__call__'
487 command_input = argv.pop(0)
488 command_name = Candidates (commands).only_match (command_input)
490 if command_name and hasattr(category_instance, command_name):
491 command = getattr(category_instance, command_name)
493 self.summary_usage(category_name)
495 # ensure options are valid
496 usage = "%%prog %s %s [options]" % (category_name, command_name)
497 parser = OptionParser(usage=usage)
498 for args, kwdargs in getattr(command, 'add_options', []):
499 parser.add_option(*args, **kwdargs)
500 (opts, cmd_args) = parser.parse_args(argv)
501 cmd_kwds = vars(opts)
503 # dont overrride meth
504 for k, v in cmd_kwds.items():
510 #print "invoking %s *=%s **=%s"%(command.__name__,cmd_args, cmd_kwds)
511 command(*cmd_args, **cmd_kwds)
514 print("Possible wrong number of arguments supplied")
516 #traceback.print_exc()
517 print(command.__doc__)
522 print("Command failed, please check log for more info")