3 # pylint: disable=c0111, c0103, w0402, w0622
5 from __future__ import print_function
10 from pprint import PrettyPrinter
11 from optparse import OptionParser
13 from sfa.generic import Generic
14 from sfa.util.xrn import Xrn
15 from sfa.storage.record import Record
17 from sfa.trust.hierarchy import Hierarchy
18 from sfa.trust.gid import GID
19 from sfa.trust.certificate import convert_public_key
21 from sfa.client.common import (optparse_listvalue_callback,
22 optparse_dictvalue_callback,
23 terminal_render, filter_records)
24 from sfa.client.candidates import Candidates
25 from sfa.client.sfi import save_records_to_file
27 pprinter = PrettyPrinter(indent=4)
30 help_basedir = Hierarchy().basedir
32 help_basedir = '*unable to locate Hierarchy().basedir'
35 def add_options(*args, **kwargs):
37 func.__dict__.setdefault('add_options', []).insert(0, (args, kwargs))
42 class Commands(object):
44 def _get_commands(self):
46 for attrib in dir(self):
47 if callable(getattr(self, attrib)) and not attrib.startswith('_'):
48 command_names.append(attrib)
52 class RegistryCommands(Commands):
54 def __init__(self, *args, **kwds):
55 self.api = Generic.the_flavour().make_api(interface='registry')
58 """Display the Registry version"""
59 version = self.api.manager.GetVersion(self.api, {})
60 pprinter.pprint(version)
62 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
63 help='authority to list (hrn/urn - mandatory)')
64 @add_options('-t', '--type', dest='type', metavar='<type>',
65 help='object type', default='all')
66 @add_options('-r', '--recursive', dest='recursive', metavar='<recursive>',
67 help='list all child records',
68 action='store_true', default=False)
69 @add_options('-v', '--verbose', dest='verbose',
70 action='store_true', default=False)
71 def list(self, xrn, type=None, recursive=False, verbose=False):
73 List names registered at a given authority, possibly filtered by type
76 options_dict = {'recursive': recursive}
77 records = self.api.manager.List(
78 self.api, xrn.get_hrn(), options=options_dict)
79 list = filter_records(type, records)
80 # terminal_render expects an options object
82 class Options: # pylint: disable=r0903
83 def __init__(self, verbose):
84 self.verbose = verbose
86 options = Options(verbose)
87 terminal_render(list, options)
89 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
90 help='object hrn/urn (mandatory)')
91 @add_options('-t', '--type', dest='type', metavar='<type>',
92 help='object type', default=None)
93 @add_options('-o', '--outfile', dest='outfile', metavar='<outfile>',
94 help='save record to file')
95 @add_options('-f', '--format', dest='format', metavar='<display>',
96 type='choice', choices=('text', 'xml', 'simple'),
97 help='display record in different formats')
98 def show(self, xrn, type=None, format=None, outfile=None):
99 """Display details for a registered object"""
100 records = self.api.manager.Resolve(self.api, xrn, type, details=True)
101 for record in records:
102 sfa_record = Record(dict=record)
103 sfa_record.dump(format)
105 save_records_to_file(outfile, records)
108 def _record_dict(xrn, type, email, key,
109 slices, researchers, pis,
110 url, description, extras):
117 record_dict['urn'] = xrn.get_urn()
118 record_dict['hrn'] = xrn.get_hrn()
119 record_dict['type'] = xrn.get_type()
121 record_dict['url'] = url
123 record_dict['description'] = description
126 pubkey = open(key, 'r').read()
129 record_dict['reg-keys'] = [pubkey]
131 record_dict['slices'] = slices
133 record_dict['reg-researchers'] = researchers
135 record_dict['email'] = email
137 record_dict['reg-pis'] = pis
139 record_dict.update(extras)
142 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
143 help='object hrn/urn', default=None)
144 @add_options('-t', '--type', dest='type', metavar='<type>',
145 help='object type (mandatory)')
146 @add_options('-a', '--all', dest='all', metavar='<all>',
147 action='store_true', default=False,
148 help='check all users GID')
149 @add_options('-v', '--verbose', dest='verbose', metavar='<verbose>',
150 action='store_true', default=False,
151 help='verbose mode: display user\'s hrn ')
152 def check_gid(self, xrn=None, type=None, all=None, verbose=None):
153 """Check the correspondance between the GID and the PubKey"""
156 from sfa.storage.model import RegRecord
157 db_query = self.api.dbsession().query(RegRecord).filter_by(type=type)
159 hrn = Xrn(xrn).get_hrn()
160 db_query = db_query.filter_by(hrn=hrn)
162 print("Use either -a or -x <xrn>, not both !!!")
164 elif not all and not xrn:
165 print("Use either -a or -x <xrn>, one of them is mandatory !!!")
168 records = db_query.all()
170 print("No Record found")
177 for record in records:
178 # get the pubkey stored in SFA DB
180 db_pubkey_str = record.reg_keys[0].key
182 db_pubkey_obj = convert_public_key(db_pubkey_str)
184 ERROR.append(record.hrn)
187 NOKEY.append(record.hrn)
190 # get the pubkey from the gid
192 gid_obj = GID(string=gid_str)
193 gid_pubkey_obj = gid_obj.get_pubkey()
195 # Check if gid_pubkey_obj and db_pubkey_obj are the same
196 check = gid_pubkey_obj.is_same(db_pubkey_obj)
198 OK.append(record.hrn)
200 NOK.append(record.hrn)
203 print("Users NOT having a PubKey: %s\n\
204 Users having a non RSA PubKey: %s\n\
205 Users having a GID/PubKey correpondence OK: %s\n\
206 Users having a GID/PubKey correpondence Not OK: %s\n"
207 % (len(NOKEY), len(ERROR), len(OK), len(NOK)))
209 print("Users NOT having a PubKey: %s and are: \n%s\n\n\
210 Users having a non RSA PubKey: %s and are: \n%s\n\n\
211 Users having a GID/PubKey correpondence OK: %s and are: \n%s\n\n\
212 Users having a GID/PubKey correpondence NOT OK: %s and are: \n%s\n\n"
213 % (len(NOKEY), NOKEY, len(ERROR), ERROR,
214 len(OK), OK, len(NOK), NOK))
217 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
218 help='object hrn/urn (mandatory)')
219 @add_options('-t', '--type', dest='type', metavar='<type>',
220 help='object type', default=None)
221 @add_options('-e', '--email', dest='email', default="",
222 help="email (mandatory for users)")
223 @add_options('-u', '--url', dest='url', metavar='<url>', default=None,
224 help="URL, useful for slices")
225 @add_options('-d', '--description', dest='description',
226 metavar='<description>',
227 help='Description, useful for slices', default=None)
228 @add_options('-k', '--key', dest='key', metavar='<key>',
229 help='public key string or file',
231 @add_options('-s', '--slices', dest='slices', metavar='<slices>',
232 help='Set/replace slice xrns',
233 default='', type="str", action='callback',
234 callback=optparse_listvalue_callback)
235 @add_options('-r', '--researchers', dest='researchers',
236 metavar='<researchers>', help='Set/replace slice researchers',
237 default='', type="str", action='callback',
238 callback=optparse_listvalue_callback)
239 @add_options('-p', '--pis', dest='pis', metavar='<PIs>',
240 help='Set/replace Principal Investigators/Project Managers',
241 default='', type="str", action='callback',
242 callback=optparse_listvalue_callback)
243 @add_options('-X', '--extra', dest='extras',
244 default={}, type='str', metavar="<EXTRA_ASSIGNS>",
245 action="callback", callback=optparse_dictvalue_callback,
247 help="set extra/testbed-dependent flags,"
248 " e.g. --extra enabled=true")
249 def register(self, xrn, type=None, email='', key=None,
250 slices='', pis='', researchers='',
251 url=None, description=None, extras={}):
252 """Create a new Registry record"""
253 record_dict = self._record_dict(
254 xrn=xrn, type=type, email=email, key=key,
255 slices=slices, researchers=researchers, pis=pis,
256 url=url, description=description, extras=extras)
257 self.api.manager.Register(self.api, record_dict)
259 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
260 help='object hrn/urn (mandatory)')
261 @add_options('-t', '--type', dest='type', metavar='<type>',
262 help='object type', default=None)
263 @add_options('-u', '--url', dest='url', metavar='<url>',
264 help='URL', default=None)
265 @add_options('-d', '--description', dest='description',
266 metavar='<description>',
267 help='Description', default=None)
268 @add_options('-k', '--key', dest='key', metavar='<key>',
269 help='public key string or file',
271 @add_options('-s', '--slices', dest='slices', metavar='<slices>',
272 help='Set/replace slice xrns',
273 default='', type="str", action='callback',
274 callback=optparse_listvalue_callback)
275 @add_options('-r', '--researchers', dest='researchers',
276 metavar='<researchers>', help='Set/replace slice researchers',
277 default='', type="str", action='callback',
278 callback=optparse_listvalue_callback)
279 @add_options('-p', '--pis', dest='pis', metavar='<PIs>',
280 help='Set/replace Principal Investigators/Project Managers',
281 default='', type="str", action='callback',
282 callback=optparse_listvalue_callback)
283 @add_options('-X', '--extra', dest='extras', default={}, type='str',
284 metavar="<EXTRA_ASSIGNS>", nargs=1,
285 action="callback", callback=optparse_dictvalue_callback,
286 help="set extra/testbed-dependent flags,"
287 " e.g. --extra enabled=true")
288 def update(self, xrn, type=None, email='', key=None,
289 slices='', pis='', researchers='',
290 url=None, description=None, extras={}):
291 """Update an existing Registry record"""
292 record_dict = self._record_dict(
293 xrn=xrn, type=type, email=email, key=key,
294 slices=slices, researchers=researchers, pis=pis,
295 url=url, description=description, extras=extras)
296 self.api.manager.Update(self.api, record_dict)
298 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
299 help='object hrn/urn (mandatory)')
300 @add_options('-t', '--type', dest='type', metavar='<type>',
301 help='object type', default=None)
302 def remove(self, xrn, type=None):
303 """Remove given object from the registry"""
305 self.api.manager.Remove(self.api, xrn)
307 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
308 help='object hrn/urn (mandatory)')
309 @add_options('-t', '--type', dest='type', metavar='<type>',
310 help='object type', default=None)
311 def credential(self, xrn, type=None):
312 """Invoke GetCredential"""
313 cred = self.api.manager.GetCredential(
314 self.api, xrn, type, self.api.hrn)
318 def import_registry(self):
319 """Run the importer"""
320 from sfa.importer import Importer
321 importer = Importer()
326 """Initialize or upgrade the db"""
327 from sfa.storage.dbschema import DBSchema
328 dbschema = DBSchema()
329 dbschema.init_or_upgrade()
332 @add_options('-a', '--all', dest='all', metavar='<all>',
333 action='store_true', default=False,
334 help='Remove all registry records and all files in %s area'
336 @add_options('-c', '--certs', dest='certs',
337 metavar='<certs>', action='store_true', default=False,
338 help='Remove all cached certs/gids found in %s'
340 @add_options('-0', '--no-reinit', dest='reinit', metavar='<reinit>',
341 action='store_false', default=True,
342 help="Prevents new DB schema"
343 " from being installed after cleanup")
344 def nuke(self, all=False, certs=False, reinit=True):
346 Cleanup local registry DB, plus various additional
347 filesystem cleanups optionally
349 from sfa.storage.dbschema import DBSchema
350 from sfa.util.sfalogging import init_logger, logger
351 init_logger('import')
352 logger.setLevelFromOptVerbose(self.api.config.SFA_API_LOGLEVEL)
353 logger.info("Purging SFA records from database")
354 dbschema = DBSchema()
357 # for convenience we re-create the schema here,
358 # so there's no need for an explicit
359 # service sfa restart
360 # however in some (upgrade) scenarios this might be wrong
362 logger.info("re-creating empty schema")
363 dbschema.init_or_upgrade()
365 # remove the server certificate and all gids found in
366 # /var/lib/sfa/authorities
368 logger.info("Purging cached certificates")
369 for (dir, _, files) in os.walk('/var/lib/sfa/authorities'):
371 if file.endswith('.gid') or file == 'server.cert':
372 path = dir + os.sep + file
375 # just remove all files that do not match 'server.key' or 'server.cert'
377 logger.info("Purging registry filesystem cache")
378 preserved_files = ['server.key', 'server.cert']
379 for dir, _, files in os.walk(Hierarchy().basedir):
381 if file in preserved_files:
383 path = dir + os.sep + file
387 class CertCommands(Commands):
389 def __init__(self, *args, **kwds):
390 self.api = Generic.the_flavour().make_api(interface='registry')
392 def import_gid(self, xrn):
395 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
396 help='object hrn/urn (mandatory)')
397 @add_options('-t', '--type', dest='type', metavar='<type>',
398 help='object type', default=None)
399 @add_options('-o', '--outfile', dest='outfile', metavar='<outfile>',
400 help='output file', default=None)
401 def export(self, xrn, type=None, outfile=None):
402 """Fetch an object's GID from the Registry"""
403 from sfa.storage.model import RegRecord
404 hrn = Xrn(xrn).get_hrn()
405 request = self.api.dbsession().query(RegRecord).filter_by(hrn=hrn)
407 request = request.filter_by(type=type)
408 record = request.first()
410 gid = GID(string=record.gid)
412 # check the authorities hierarchy
413 hierarchy = Hierarchy()
415 auth_info = hierarchy.get_auth_info(hrn)
416 gid = auth_info.gid_object
418 print("Record: %s not found" % hrn)
422 outfile = os.path.abspath('./%s.gid' % gid.get_hrn())
423 gid.save_to_file(outfile, save_parents=True)
425 @add_options('-g', '--gidfile', dest='gid', metavar='<gid>',
426 help='path of gid file to display (mandatory)')
427 def display(self, gidfile):
428 """Print contents of a GID file"""
429 gid_path = os.path.abspath(gidfile)
430 if not gid_path or not os.path.isfile(gid_path):
431 print("No such gid file: %s" % gidfile)
433 gid = GID(filename=gid_path)
434 gid.dump(dump_parents=True)
437 class AggregateCommands(Commands):
439 def __init__(self, *args, **kwds):
440 self.api = Generic.the_flavour().make_api(interface='aggregate')
443 """Display the Aggregate version"""
444 version = self.api.manager.GetVersion(self.api, {})
445 pprinter.pprint(version)
447 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
448 help='object hrn/urn (mandatory)')
449 def status(self, xrn):
451 Retrieve the status of the slivers
452 belonging to the named slice (Status)
454 urns = [Xrn(xrn, 'slice').get_urn()]
455 status = self.api.manager.Status(self.api, urns, [], {})
456 pprinter.pprint(status)
458 @add_options('-r', '--rspec-version', dest='rspec_version',
459 metavar='<rspec_version>', default='GENI',
460 help='version/format of the resulting rspec response')
461 def resources(self, rspec_version='GENI'):
462 """Display the available resources at an aggregate"""
463 options = {'geni_rspec_version': rspec_version}
465 resources = self.api.manager.ListResources(self.api, [], options)
468 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
469 help='slice hrn/urn (mandatory)')
470 @add_options('-r', '--rspec', dest='rspec', metavar='<rspec>',
471 help='rspec file (mandatory)')
472 def allocate(self, xrn, rspec):
473 """Allocate slivers"""
474 xrn = Xrn(xrn, 'slice')
475 slice_urn = xrn.get_urn()
476 rspec_string = open(rspec).read()
478 manifest = self.api.manager.Allocate(
479 self.api, slice_urn, [], rspec_string, options)
482 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
483 help='slice hrn/urn (mandatory)')
484 def provision(self, xrn):
485 """Provision slivers"""
486 xrn = Xrn(xrn, 'slice')
487 slice_urn = xrn.get_urn()
489 manifest = self.api.manager.provision(
490 self.api, [slice_urn], [], options)
493 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
494 help='slice hrn/urn (mandatory)')
495 def delete(self, xrn):
497 self.api.manager.Delete(self.api, [xrn], [], {})
500 class SliceManagerCommands(AggregateCommands):
502 def __init__(self, *args, **kwds):
503 self.api = Generic.the_flavour().make_api(interface='slicemgr')
508 CATEGORIES = {'certificate': CertCommands,
509 'registry': RegistryCommands,
510 'aggregate': AggregateCommands,
511 'slicemgr': SliceManagerCommands}
513 # returns (name,class) or (None,None)
514 def find_category(self, input):
515 full_name = Candidates(SfaAdmin.CATEGORIES.keys()).only_match(input)
518 return (full_name, SfaAdmin.CATEGORIES[full_name])
520 def summary_usage(self, category=None):
521 print("Usage:", self.script_name + " category command [<options>]")
522 if category and category in SfaAdmin.CATEGORIES:
523 categories = [category]
525 categories = SfaAdmin.CATEGORIES
527 cls = SfaAdmin.CATEGORIES[c]
528 print("==================== category=%s" % c)
529 names = cls.__dict__.keys()
532 method = cls.__dict__[name]
533 if name.startswith('_'):
536 print("%-15s" % name, end=' ')
537 doc = getattr(method, '__doc__', None)
539 print("<missing __doc__>")
541 lines = [line.strip() for line in doc.split("\n")]
544 for extra_line in lines:
545 print(margin * " ", extra_line)
549 argv = copy.deepcopy(sys.argv)
550 self.script_name = argv.pop(0)
551 # ensure category is specified
555 # ensure category is valid
556 category_input = argv.pop(0)
557 (category_name, category_class) = self.find_category(category_input)
558 if not category_name or not category_class:
559 self.summary_usage(category_name)
561 usage = "%%prog %s command [options]" % (category_name)
562 parser = OptionParser(usage=usage)
564 # ensure command is valid
565 category_instance = category_class()
566 commands = category_instance._get_commands()
568 # xxx what is this about ?
569 command_name = '__call__'
571 command_input = argv.pop(0)
572 command_name = Candidates(commands).only_match(command_input)
574 if command_name and hasattr(category_instance, command_name):
575 command = getattr(category_instance, command_name)
577 self.summary_usage(category_name)
579 # ensure options are valid
580 usage = "%%prog %s %s [options]" % (category_name, command_name)
581 parser = OptionParser(usage=usage)
582 for args, kwdargs in getattr(command, 'add_options', []):
583 parser.add_option(*args, **kwdargs)
584 (opts, cmd_args) = parser.parse_args(argv)
585 cmd_kwds = vars(opts)
587 # dont overrride meth
588 for k, v in cmd_kwds.items():
594 # print "invoking %s *=%s **=%s"%(command.__name__, cmd_args,
596 command(*cmd_args, **cmd_kwds)
599 print("Possible wrong number of arguments supplied")
600 print(command.__doc__)
605 print("Command failed, please check log for more info")