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'
30 def add_options(*args, **kwargs):
32 func.__dict__.setdefault('add_options', []).insert(0, (args, kwargs))
37 class Commands(object):
39 def _get_commands(self):
41 for attrib in dir(self):
42 if callable(getattr(self, attrib)) and not attrib.startswith('_'):
43 command_names.append(attrib)
47 class RegistryCommands(Commands):
49 def __init__(self, *args, **kwds):
50 self.api = Generic.the_flavour().make_api(interface='registry')
53 """Display the Registry version"""
54 version = self.api.manager.GetVersion(self.api, {})
55 pprinter.pprint(version)
58 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='authority to list (hrn/urn - mandatory)')
59 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default='all')
60 @add_options('-r', '--recursive', dest='recursive', metavar='<recursive>', help='list all child records',
61 action='store_true', default=False)
62 @add_options('-v', '--verbose', dest='verbose', action='store_true', default=False)
63 def list(self, xrn, type=None, recursive=False, verbose=False):
64 """List names registered at a given authority - possibly filtered by type"""
66 options_dict = {'recursive': recursive}
67 records = self.api.manager.List(
68 self.api, xrn.get_hrn(), options=options_dict)
69 list = filter_records(type, records)
70 # terminal_render expects an options object
75 options.verbose = verbose
76 terminal_render(list, options)
79 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
80 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
81 @add_options('-o', '--outfile', dest='outfile', metavar='<outfile>', help='save record to file')
82 @add_options('-f', '--format', dest='format', metavar='<display>', type='choice',
83 choices=('text', 'xml', 'simple'), help='display record in different formats')
84 def show(self, xrn, type=None, format=None, outfile=None):
85 """Display details for a registered object"""
86 records = self.api.manager.Resolve(self.api, xrn, type, details=True)
87 for record in records:
88 sfa_record = Record(dict=record)
89 sfa_record.dump(format)
91 save_records_to_file(outfile, records)
93 def _record_dict(self, xrn, type, email, key,
94 slices, researchers, pis,
95 url, description, extras):
102 record_dict['urn'] = xrn.get_urn()
103 record_dict['hrn'] = xrn.get_hrn()
104 record_dict['type'] = xrn.get_type()
106 record_dict['url'] = url
108 record_dict['description'] = description
111 pubkey = open(key, 'r').read()
114 record_dict['reg-keys'] = [pubkey]
116 record_dict['slices'] = slices
118 record_dict['reg-researchers'] = researchers
120 record_dict['email'] = email
122 record_dict['reg-pis'] = pis
124 record_dict.update(extras)
128 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn', default=None)
129 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type (mandatory)')
130 @add_options('-a', '--all', dest='all', metavar='<all>', action='store_true', default=False, help='check all users GID')
131 @add_options('-v', '--verbose', dest='verbose', metavar='<verbose>', action='store_true', default=False, help='verbose mode: display user\'s hrn ')
132 def check_gid(self, xrn=None, type=None, all=None, verbose=None):
133 """Check the correspondance between the GID and the PubKey"""
136 from sfa.storage.model import RegRecord
137 db_query = self.api.dbsession().query(RegRecord).filter_by(type=type)
139 hrn = Xrn(xrn).get_hrn()
140 db_query = db_query.filter_by(hrn=hrn)
142 print("Use either -a or -x <xrn>, not both !!!")
144 elif not all and not xrn:
145 print("Use either -a or -x <xrn>, one of them is mandatory !!!")
148 records = db_query.all()
150 print("No Record found")
157 for record in records:
158 # get the pubkey stored in SFA DB
160 db_pubkey_str = record.reg_keys[0].key
162 db_pubkey_obj = convert_public_key(db_pubkey_str)
164 ERROR.append(record.hrn)
167 NOKEY.append(record.hrn)
170 # get the pubkey from the gid
172 gid_obj = GID(string=gid_str)
173 gid_pubkey_obj = gid_obj.get_pubkey()
175 # Check if gid_pubkey_obj and db_pubkey_obj are the same
176 check = gid_pubkey_obj.is_same(db_pubkey_obj)
178 OK.append(record.hrn)
180 NOK.append(record.hrn)
183 print("Users NOT having a PubKey: %s\n\
184 Users having a non RSA PubKey: %s\n\
185 Users having a GID/PubKey correpondence OK: %s\n\
186 Users having a GID/PubKey correpondence Not OK: %s\n" % (len(NOKEY), len(ERROR), len(OK), len(NOK)))
188 print("Users NOT having a PubKey: %s and are: \n%s\n\n\
189 Users having a non RSA PubKey: %s and are: \n%s\n\n\
190 Users having a GID/PubKey correpondence OK: %s and are: \n%s\n\n\
191 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))
194 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
195 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
196 @add_options('-e', '--email', dest='email', default="",
197 help="email (mandatory for users)")
198 @add_options('-u', '--url', dest='url', metavar='<url>', default=None,
199 help="URL, useful for slices")
200 @add_options('-d', '--description', dest='description', metavar='<description>',
201 help='Description, useful for slices', default=None)
202 @add_options('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
204 @add_options('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
205 default='', type="str", action='callback', callback=optparse_listvalue_callback)
206 @add_options('-r', '--researchers', dest='researchers', metavar='<researchers>', help='Set/replace slice researchers',
207 default='', type="str", action='callback', callback=optparse_listvalue_callback)
208 @add_options('-p', '--pis', dest='pis', metavar='<PIs>',
209 help='Set/replace Principal Investigators/Project Managers',
210 default='', type="str", action='callback', callback=optparse_listvalue_callback)
211 @add_options('-X', '--extra', dest='extras', default={}, type='str', metavar="<EXTRA_ASSIGNS>",
212 action="callback", callback=optparse_dictvalue_callback, nargs=1,
213 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
214 def register(self, xrn, type=None, email='', key=None,
215 slices='', pis='', researchers='',
216 url=None, description=None, extras={}):
217 """Create a new Registry record"""
218 record_dict = self._record_dict(xrn=xrn, type=type, email=email, key=key,
219 slices=slices, researchers=researchers, pis=pis,
220 url=url, description=description, extras=extras)
221 self.api.manager.Register(self.api, record_dict)
224 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
225 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
226 @add_options('-u', '--url', dest='url', metavar='<url>', help='URL', default=None)
227 @add_options('-d', '--description', dest='description', metavar='<description>',
228 help='Description', default=None)
229 @add_options('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
231 @add_options('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
232 default='', type="str", action='callback', callback=optparse_listvalue_callback)
233 @add_options('-r', '--researchers', dest='researchers', metavar='<researchers>', help='Set/replace slice researchers',
234 default='', type="str", action='callback', callback=optparse_listvalue_callback)
235 @add_options('-p', '--pis', dest='pis', metavar='<PIs>',
236 help='Set/replace Principal Investigators/Project Managers',
237 default='', type="str", action='callback', callback=optparse_listvalue_callback)
238 @add_options('-X', '--extra', dest='extras', default={}, type='str', metavar="<EXTRA_ASSIGNS>",
239 action="callback", callback=optparse_dictvalue_callback, nargs=1,
240 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
241 def update(self, xrn, type=None, email='', key=None,
242 slices='', pis='', researchers='',
243 url=None, description=None, extras={}):
244 """Update an existing Registry record"""
245 record_dict = self._record_dict(xrn=xrn, type=type, email=email, key=key,
246 slices=slices, researchers=researchers, pis=pis,
247 url=url, description=description, extras=extras)
248 self.api.manager.Update(self.api, record_dict)
251 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
252 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
253 def remove(self, xrn, type=None):
254 """Remove given object from the registry"""
256 self.api.manager.Remove(self.api, xrn)
259 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
260 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
261 def credential(self, xrn, type=None):
262 """Invoke GetCredential"""
263 cred = self.api.manager.GetCredential(
264 self.api, xrn, type, self.api.hrn)
268 def import_registry(self):
269 """Run the importer"""
270 from sfa.importer import Importer
271 importer = Importer()
276 """Initialize or upgrade the db"""
277 from sfa.storage.dbschema import DBSchema
278 dbschema = DBSchema()
279 dbschema.init_or_upgrade()
282 @add_options('-a', '--all', dest='all', metavar='<all>', action='store_true', default=False,
283 help='Remove all registry records and all files in %s area' % help_basedir)
284 @add_options('-c', '--certs', dest='certs', metavar='<certs>', action='store_true', default=False,
285 help='Remove all cached certs/gids found in %s' % help_basedir)
286 @add_options('-0', '--no-reinit', dest='reinit', metavar='<reinit>', action='store_false', default=True,
287 help='Prevents new DB schema from being installed after cleanup')
288 def nuke(self, all=False, certs=False, reinit=True):
289 """Cleanup local registry DB, plus various additional filesystem cleanups optionally"""
290 from sfa.storage.dbschema import DBSchema
291 from sfa.util.sfalogging import _SfaLogger
293 logfile='/var/log/sfa_import.log', loggername='importlog')
294 logger.setLevelFromOptVerbose(self.api.config.SFA_API_LOGLEVEL)
295 logger.info("Purging SFA records from database")
296 dbschema = DBSchema()
299 # for convenience we re-create the schema here, so there's no need for an explicit
300 # service sfa restart
301 # however in some (upgrade) scenarios this might be wrong
303 logger.info("re-creating empty schema")
304 dbschema.init_or_upgrade()
306 # remove the server certificate and all gids found in
307 # /var/lib/sfa/authorities
309 logger.info("Purging cached certificates")
310 for (dir, _, files) in os.walk('/var/lib/sfa/authorities'):
312 if file.endswith('.gid') or file == 'server.cert':
313 path = dir + os.sep + file
316 # just remove all files that do not match 'server.key' or 'server.cert'
318 logger.info("Purging registry filesystem cache")
319 preserved_files = ['server.key', 'server.cert']
320 for dir, _, files in os.walk(Hierarchy().basedir):
322 if file in preserved_files:
324 path = dir + os.sep + file
328 class CertCommands(Commands):
330 def __init__(self, *args, **kwds):
331 self.api = Generic.the_flavour().make_api(interface='registry')
333 def import_gid(self, xrn):
336 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
337 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
338 @add_options('-o', '--outfile', dest='outfile', metavar='<outfile>', help='output file', default=None)
339 def export(self, xrn, type=None, outfile=None):
340 """Fetch an object's GID from the Registry"""
341 from sfa.storage.model import RegRecord
342 hrn = Xrn(xrn).get_hrn()
343 request = self.api.dbsession().query(RegRecord).filter_by(hrn=hrn)
345 request = request.filter_by(type=type)
346 record = request.first()
348 gid = GID(string=record.gid)
350 # check the authorities hierarchy
351 hierarchy = Hierarchy()
353 auth_info = hierarchy.get_auth_info(hrn)
354 gid = auth_info.gid_object
356 print("Record: %s not found" % hrn)
360 outfile = os.path.abspath('./%s.gid' % gid.get_hrn())
361 gid.save_to_file(outfile, save_parents=True)
363 @add_options('-g', '--gidfile', dest='gid', metavar='<gid>', help='path of gid file to display (mandatory)')
364 def display(self, gidfile):
365 """Print contents of a GID file"""
366 gid_path = os.path.abspath(gidfile)
367 if not gid_path or not os.path.isfile(gid_path):
368 print("No such gid file: %s" % gidfile)
370 gid = GID(filename=gid_path)
371 gid.dump(dump_parents=True)
374 class AggregateCommands(Commands):
376 def __init__(self, *args, **kwds):
377 self.api = Generic.the_flavour().make_api(interface='aggregate')
380 """Display the Aggregate version"""
381 version = self.api.manager.GetVersion(self.api, {})
382 pprinter.pprint(version)
384 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
385 def status(self, xrn):
386 """Retrieve the status of the slivers belonging to the named slice (Status)"""
387 urns = [Xrn(xrn, 'slice').get_urn()]
388 status = self.api.manager.Status(self.api, urns, [], {})
389 pprinter.pprint(status)
391 @add_options('-r', '--rspec-version', dest='rspec_version', metavar='<rspec_version>',
392 default='GENI', help='version/format of the resulting rspec response')
393 def resources(self, rspec_version='GENI'):
394 """Display the available resources at an aggregate"""
395 options = {'geni_rspec_version': rspec_version}
397 resources = self.api.manager.ListResources(self.api, [], options)
400 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='slice hrn/urn (mandatory)')
401 @add_options('-r', '--rspec', dest='rspec', metavar='<rspec>', help='rspec file (mandatory)')
402 def allocate(self, xrn, rspec):
403 """Allocate slivers"""
404 xrn = Xrn(xrn, 'slice')
405 slice_urn = xrn.get_urn()
406 rspec_string = open(rspec).read()
408 manifest = self.api.manager.Allocate(
409 self.api, slice_urn, [], rspec_string, options)
412 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='slice hrn/urn (mandatory)')
413 def provision(self, xrn):
414 """Provision slivers"""
415 xrn = Xrn(xrn, 'slice')
416 slice_urn = xrn.get_urn()
418 manifest = self.api.manager.provision(
419 self.api, [slice_urn], [], options)
422 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='slice hrn/urn (mandatory)')
423 def delete(self, xrn):
425 self.api.manager.Delete(self.api, [xrn], [], {})
428 class SliceManagerCommands(AggregateCommands):
430 def __init__(self, *args, **kwds):
431 self.api = Generic.the_flavour().make_api(interface='slicemgr')
436 CATEGORIES = {'certificate': CertCommands,
437 'registry': RegistryCommands,
438 'aggregate': AggregateCommands,
439 'slicemgr': SliceManagerCommands}
441 # returns (name,class) or (None,None)
442 def find_category(self, input):
443 full_name = Candidates(SfaAdmin.CATEGORIES.keys()).only_match(input)
446 return (full_name, SfaAdmin.CATEGORIES[full_name])
448 def summary_usage(self, category=None):
449 print("Usage:", self.script_name + " category command [<options>]")
450 if category and category in SfaAdmin.CATEGORIES:
451 categories = [category]
453 categories = SfaAdmin.CATEGORIES
455 cls = SfaAdmin.CATEGORIES[c]
456 print("==================== category=%s" % c)
457 names = cls.__dict__.keys()
460 method = cls.__dict__[name]
461 if name.startswith('_'):
464 format = "%%-%ds" % margin
465 print("%-15s" % name, end=' ')
466 doc = getattr(method, '__doc__', None)
468 print("<missing __doc__>")
470 lines = [line.strip() for line in doc.split("\n")]
473 for extra_line in lines:
474 print(margin * " ", extra_line)
478 argv = copy.deepcopy(sys.argv)
479 self.script_name = argv.pop(0)
480 # ensure category is specified
484 # ensure category is valid
485 category_input = argv.pop(0)
486 (category_name, category_class) = self.find_category(category_input)
487 if not category_name or not category_class:
488 self.summary_usage(category_name)
490 usage = "%%prog %s command [options]" % (category_name)
491 parser = OptionParser(usage=usage)
493 # ensure command is valid
494 category_instance = category_class()
495 commands = category_instance._get_commands()
497 # xxx what is this about ?
498 command_name = '__call__'
500 command_input = argv.pop(0)
501 command_name = Candidates(commands).only_match(command_input)
503 if command_name and hasattr(category_instance, command_name):
504 command = getattr(category_instance, command_name)
506 self.summary_usage(category_name)
508 # ensure options are valid
509 usage = "%%prog %s %s [options]" % (category_name, command_name)
510 parser = OptionParser(usage=usage)
511 for args, kwdargs in getattr(command, 'add_options', []):
512 parser.add_option(*args, **kwdargs)
513 (opts, cmd_args) = parser.parse_args(argv)
514 cmd_kwds = vars(opts)
516 # dont overrride meth
517 for k, v in cmd_kwds.items():
523 # print "invoking %s *=%s **=%s"%(command.__name__, cmd_args,
525 command(*cmd_args, **cmd_kwds)
528 print("Possible wrong number of arguments supplied")
530 # traceback.print_exc()
531 print(command.__doc__)
536 print("Command failed, please check log for more info")