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 _SfaLogger
352 logfile='/var/log/sfa_import.log', loggername='importlog')
353 logger.setLevelFromOptVerbose(self.api.config.SFA_API_LOGLEVEL)
354 logger.info("Purging SFA records from database")
355 dbschema = DBSchema()
358 # for convenience we re-create the schema here,
359 # so there's no need for an explicit
360 # service sfa restart
361 # however in some (upgrade) scenarios this might be wrong
363 logger.info("re-creating empty schema")
364 dbschema.init_or_upgrade()
366 # remove the server certificate and all gids found in
367 # /var/lib/sfa/authorities
369 logger.info("Purging cached certificates")
370 for (dir, _, files) in os.walk('/var/lib/sfa/authorities'):
372 if file.endswith('.gid') or file == 'server.cert':
373 path = dir + os.sep + file
376 # just remove all files that do not match 'server.key' or 'server.cert'
378 logger.info("Purging registry filesystem cache")
379 preserved_files = ['server.key', 'server.cert']
380 for dir, _, files in os.walk(Hierarchy().basedir):
382 if file in preserved_files:
384 path = dir + os.sep + file
388 class CertCommands(Commands):
390 def __init__(self, *args, **kwds):
391 self.api = Generic.the_flavour().make_api(interface='registry')
393 def import_gid(self, xrn):
396 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
397 help='object hrn/urn (mandatory)')
398 @add_options('-t', '--type', dest='type', metavar='<type>',
399 help='object type', default=None)
400 @add_options('-o', '--outfile', dest='outfile', metavar='<outfile>',
401 help='output file', default=None)
402 def export(self, xrn, type=None, outfile=None):
403 """Fetch an object's GID from the Registry"""
404 from sfa.storage.model import RegRecord
405 hrn = Xrn(xrn).get_hrn()
406 request = self.api.dbsession().query(RegRecord).filter_by(hrn=hrn)
408 request = request.filter_by(type=type)
409 record = request.first()
411 gid = GID(string=record.gid)
413 # check the authorities hierarchy
414 hierarchy = Hierarchy()
416 auth_info = hierarchy.get_auth_info(hrn)
417 gid = auth_info.gid_object
419 print("Record: %s not found" % hrn)
423 outfile = os.path.abspath('./%s.gid' % gid.get_hrn())
424 gid.save_to_file(outfile, save_parents=True)
426 @add_options('-g', '--gidfile', dest='gid', metavar='<gid>',
427 help='path of gid file to display (mandatory)')
428 def display(self, gidfile):
429 """Print contents of a GID file"""
430 gid_path = os.path.abspath(gidfile)
431 if not gid_path or not os.path.isfile(gid_path):
432 print("No such gid file: %s" % gidfile)
434 gid = GID(filename=gid_path)
435 gid.dump(dump_parents=True)
438 class AggregateCommands(Commands):
440 def __init__(self, *args, **kwds):
441 self.api = Generic.the_flavour().make_api(interface='aggregate')
444 """Display the Aggregate version"""
445 version = self.api.manager.GetVersion(self.api, {})
446 pprinter.pprint(version)
448 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
449 help='object hrn/urn (mandatory)')
450 def status(self, xrn):
452 Retrieve the status of the slivers
453 belonging to the named slice (Status)
455 urns = [Xrn(xrn, 'slice').get_urn()]
456 status = self.api.manager.Status(self.api, urns, [], {})
457 pprinter.pprint(status)
459 @add_options('-r', '--rspec-version', dest='rspec_version',
460 metavar='<rspec_version>', default='GENI',
461 help='version/format of the resulting rspec response')
462 def resources(self, rspec_version='GENI'):
463 """Display the available resources at an aggregate"""
464 options = {'geni_rspec_version': rspec_version}
466 resources = self.api.manager.ListResources(self.api, [], options)
469 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
470 help='slice hrn/urn (mandatory)')
471 @add_options('-r', '--rspec', dest='rspec', metavar='<rspec>',
472 help='rspec file (mandatory)')
473 def allocate(self, xrn, rspec):
474 """Allocate slivers"""
475 xrn = Xrn(xrn, 'slice')
476 slice_urn = xrn.get_urn()
477 rspec_string = open(rspec).read()
479 manifest = self.api.manager.Allocate(
480 self.api, slice_urn, [], rspec_string, options)
483 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
484 help='slice hrn/urn (mandatory)')
485 def provision(self, xrn):
486 """Provision slivers"""
487 xrn = Xrn(xrn, 'slice')
488 slice_urn = xrn.get_urn()
490 manifest = self.api.manager.provision(
491 self.api, [slice_urn], [], options)
494 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>',
495 help='slice hrn/urn (mandatory)')
496 def delete(self, xrn):
498 self.api.manager.Delete(self.api, [xrn], [], {})
501 class SliceManagerCommands(AggregateCommands):
503 def __init__(self, *args, **kwds):
504 self.api = Generic.the_flavour().make_api(interface='slicemgr')
509 CATEGORIES = {'certificate': CertCommands,
510 'registry': RegistryCommands,
511 'aggregate': AggregateCommands,
512 'slicemgr': SliceManagerCommands}
514 # returns (name,class) or (None,None)
515 def find_category(self, input):
516 full_name = Candidates(SfaAdmin.CATEGORIES.keys()).only_match(input)
519 return (full_name, SfaAdmin.CATEGORIES[full_name])
521 def summary_usage(self, category=None):
522 print("Usage:", self.script_name + " category command [<options>]")
523 if category and category in SfaAdmin.CATEGORIES:
524 categories = [category]
526 categories = SfaAdmin.CATEGORIES
528 cls = SfaAdmin.CATEGORIES[c]
529 print("==================== category=%s" % c)
530 names = cls.__dict__.keys()
533 method = cls.__dict__[name]
534 if name.startswith('_'):
537 print("%-15s" % name, end=' ')
538 doc = getattr(method, '__doc__', None)
540 print("<missing __doc__>")
542 lines = [line.strip() for line in doc.split("\n")]
545 for extra_line in lines:
546 print(margin * " ", extra_line)
550 argv = copy.deepcopy(sys.argv)
551 self.script_name = argv.pop(0)
552 # ensure category is specified
556 # ensure category is valid
557 category_input = argv.pop(0)
558 (category_name, category_class) = self.find_category(category_input)
559 if not category_name or not category_class:
560 self.summary_usage(category_name)
562 usage = "%%prog %s command [options]" % (category_name)
563 parser = OptionParser(usage=usage)
565 # ensure command is valid
566 category_instance = category_class()
567 commands = category_instance._get_commands()
569 # xxx what is this about ?
570 command_name = '__call__'
572 command_input = argv.pop(0)
573 command_name = Candidates(commands).only_match(command_input)
575 if command_name and hasattr(category_instance, command_name):
576 command = getattr(category_instance, command_name)
578 self.summary_usage(category_name)
580 # ensure options are valid
581 usage = "%%prog %s %s [options]" % (category_name, command_name)
582 parser = OptionParser(usage=usage)
583 for args, kwdargs in getattr(command, 'add_options', []):
584 parser.add_option(*args, **kwdargs)
585 (opts, cmd_args) = parser.parse_args(argv)
586 cmd_kwds = vars(opts)
588 # dont overrride meth
589 for k, v in cmd_kwds.items():
595 # print "invoking %s *=%s **=%s"%(command.__name__, cmd_args,
597 command(*cmd_args, **cmd_kwds)
600 print("Possible wrong number of arguments supplied")
601 print(command.__doc__)
606 print("Command failed, please check log for more info")