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)
57 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='authority to list (hrn/urn - mandatory)')
58 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default='all')
59 @add_options('-r', '--recursive', dest='recursive', metavar='<recursive>', help='list all child records',
60 action='store_true', default=False)
61 @add_options('-v', '--verbose', dest='verbose', action='store_true', default=False)
62 def list(self, xrn, type=None, recursive=False, verbose=False):
63 """List names registered at a given authority - possibly filtered by type"""
65 options_dict = {'recursive': recursive}
66 records = self.api.manager.List(
67 self.api, xrn.get_hrn(), options=options_dict)
68 list = filter_records(type, records)
69 # terminal_render expects an options object
74 options.verbose = verbose
75 terminal_render(list, options)
77 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
78 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
79 @add_options('-o', '--outfile', dest='outfile', metavar='<outfile>', help='save record to file')
80 @add_options('-f', '--format', dest='format', metavar='<display>', type='choice',
81 choices=('text', 'xml', 'simple'), help='display record in different formats')
82 def show(self, xrn, type=None, format=None, outfile=None):
83 """Display details for a registered object"""
84 records = self.api.manager.Resolve(self.api, xrn, type, details=True)
85 for record in records:
86 sfa_record = Record(dict=record)
87 sfa_record.dump(format)
89 save_records_to_file(outfile, records)
91 def _record_dict(self, xrn, type, email, key,
92 slices, researchers, pis,
93 url, description, extras):
100 record_dict['urn'] = xrn.get_urn()
101 record_dict['hrn'] = xrn.get_hrn()
102 record_dict['type'] = xrn.get_type()
104 record_dict['url'] = url
106 record_dict['description'] = description
109 pubkey = open(key, 'r').read()
112 record_dict['reg-keys'] = [pubkey]
114 record_dict['slices'] = slices
116 record_dict['reg-researchers'] = researchers
118 record_dict['email'] = email
120 record_dict['reg-pis'] = pis
122 record_dict.update(extras)
125 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn', default=None)
126 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type (mandatory)')
127 @add_options('-a', '--all', dest='all', metavar='<all>', action='store_true', default=False, help='check all users GID')
128 @add_options('-v', '--verbose', dest='verbose', metavar='<verbose>', action='store_true', default=False, help='verbose mode: display user\'s hrn ')
129 def check_gid(self, xrn=None, type=None, all=None, verbose=None):
130 """Check the correspondance between the GID and the PubKey"""
133 from sfa.storage.model import RegRecord
134 db_query = self.api.dbsession().query(RegRecord).filter_by(type=type)
136 hrn = Xrn(xrn).get_hrn()
137 db_query = db_query.filter_by(hrn=hrn)
139 print("Use either -a or -x <xrn>, not both !!!")
141 elif not all and not xrn:
142 print("Use either -a or -x <xrn>, one of them is mandatory !!!")
145 records = db_query.all()
147 print("No Record found")
154 for record in records:
155 # get the pubkey stored in SFA DB
157 db_pubkey_str = record.reg_keys[0].key
159 db_pubkey_obj = convert_public_key(db_pubkey_str)
161 ERROR.append(record.hrn)
164 NOKEY.append(record.hrn)
167 # get the pubkey from the gid
169 gid_obj = GID(string=gid_str)
170 gid_pubkey_obj = gid_obj.get_pubkey()
172 # Check if gid_pubkey_obj and db_pubkey_obj are the same
173 check = gid_pubkey_obj.is_same(db_pubkey_obj)
175 OK.append(record.hrn)
177 NOK.append(record.hrn)
180 print("Users NOT having a PubKey: %s\n\
181 Users having a non RSA PubKey: %s\n\
182 Users having a GID/PubKey correpondence OK: %s\n\
183 Users having a GID/PubKey correpondence Not OK: %s\n" % (len(NOKEY), len(ERROR), len(OK), len(NOK)))
185 print("Users NOT having a PubKey: %s and are: \n%s\n\n\
186 Users having a non RSA PubKey: %s and are: \n%s\n\n\
187 Users having a GID/PubKey correpondence OK: %s and are: \n%s\n\n\
188 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))
190 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
191 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
192 @add_options('-e', '--email', dest='email', default="",
193 help="email (mandatory for users)")
194 @add_options('-u', '--url', dest='url', metavar='<url>', default=None,
195 help="URL, useful for slices")
196 @add_options('-d', '--description', dest='description', metavar='<description>',
197 help='Description, useful for slices', default=None)
198 @add_options('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
200 @add_options('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
201 default='', type="str", action='callback', callback=optparse_listvalue_callback)
202 @add_options('-r', '--researchers', dest='researchers', metavar='<researchers>', help='Set/replace slice researchers',
203 default='', type="str", action='callback', callback=optparse_listvalue_callback)
204 @add_options('-p', '--pis', dest='pis', metavar='<PIs>',
205 help='Set/replace Principal Investigators/Project Managers',
206 default='', type="str", action='callback', callback=optparse_listvalue_callback)
207 @add_options('-X', '--extra', dest='extras', default={}, type='str', metavar="<EXTRA_ASSIGNS>",
208 action="callback", callback=optparse_dictvalue_callback, nargs=1,
209 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
210 def register(self, xrn, type=None, email='', key=None,
211 slices='', pis='', researchers='',
212 url=None, description=None, extras={}):
213 """Create a new Registry record"""
214 record_dict = self._record_dict(xrn=xrn, type=type, email=email, key=key,
215 slices=slices, researchers=researchers, pis=pis,
216 url=url, description=description, extras=extras)
217 self.api.manager.Register(self.api, record_dict)
219 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
220 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
221 @add_options('-u', '--url', dest='url', metavar='<url>', help='URL', default=None)
222 @add_options('-d', '--description', dest='description', metavar='<description>',
223 help='Description', default=None)
224 @add_options('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
226 @add_options('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
227 default='', type="str", action='callback', callback=optparse_listvalue_callback)
228 @add_options('-r', '--researchers', dest='researchers', metavar='<researchers>', help='Set/replace slice researchers',
229 default='', type="str", action='callback', callback=optparse_listvalue_callback)
230 @add_options('-p', '--pis', dest='pis', metavar='<PIs>',
231 help='Set/replace Principal Investigators/Project Managers',
232 default='', type="str", action='callback', callback=optparse_listvalue_callback)
233 @add_options('-X', '--extra', dest='extras', default={}, type='str', metavar="<EXTRA_ASSIGNS>",
234 action="callback", callback=optparse_dictvalue_callback, nargs=1,
235 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
236 def update(self, xrn, type=None, email='', key=None,
237 slices='', pis='', researchers='',
238 url=None, description=None, extras={}):
239 """Update an existing Registry record"""
240 record_dict = self._record_dict(xrn=xrn, type=type, email=email, key=key,
241 slices=slices, researchers=researchers, pis=pis,
242 url=url, description=description, extras=extras)
243 self.api.manager.Update(self.api, record_dict)
245 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
246 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
247 def remove(self, xrn, type=None):
248 """Remove given object from the registry"""
250 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(
257 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
269 dbschema = 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
283 logfile='/var/log/sfa_import.log', loggername='importlog')
284 logger.setLevelFromOptVerbose(self.api.config.SFA_API_LOGLEVEL)
285 logger.info("Purging SFA records from database")
286 dbschema = DBSchema()
289 # for convenience we re-create the schema here, so there's no need for an explicit
290 # service sfa restart
291 # however in some (upgrade) scenarios this might be wrong
293 logger.info("re-creating empty schema")
294 dbschema.init_or_upgrade()
296 # remove the server certificate and all gids found in
297 # /var/lib/sfa/authorities
299 logger.info("Purging cached certificates")
300 for (dir, _, files) in os.walk('/var/lib/sfa/authorities'):
302 if file.endswith('.gid') or file == 'server.cert':
303 path = dir + os.sep + file
306 # just remove all files that do not match 'server.key' or 'server.cert'
308 logger.info("Purging registry filesystem cache")
309 preserved_files = ['server.key', 'server.cert']
310 for dir, _, files in os.walk(Hierarchy().basedir):
312 if file in preserved_files:
314 path = dir + os.sep + file
318 class CertCommands(Commands):
320 def __init__(self, *args, **kwds):
321 self.api = Generic.the_flavour().make_api(interface='registry')
323 def import_gid(self, xrn):
326 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
327 @add_options('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
328 @add_options('-o', '--outfile', dest='outfile', metavar='<outfile>', help='output file', default=None)
329 def export(self, xrn, type=None, outfile=None):
330 """Fetch an object's GID from the Registry"""
331 from sfa.storage.model import RegRecord
332 hrn = Xrn(xrn).get_hrn()
333 request = self.api.dbsession().query(RegRecord).filter_by(hrn=hrn)
335 request = request.filter_by(type=type)
336 record = request.first()
338 gid = GID(string=record.gid)
340 # check the authorities hierarchy
341 hierarchy = Hierarchy()
343 auth_info = hierarchy.get_auth_info(hrn)
344 gid = auth_info.gid_object
346 print("Record: %s not found" % hrn)
350 outfile = os.path.abspath('./%s.gid' % gid.get_hrn())
351 gid.save_to_file(outfile, save_parents=True)
353 @add_options('-g', '--gidfile', dest='gid', metavar='<gid>', help='path of gid file to display (mandatory)')
354 def display(self, gidfile):
355 """Print contents of a GID file"""
356 gid_path = os.path.abspath(gidfile)
357 if not gid_path or not os.path.isfile(gid_path):
358 print("No such gid file: %s" % gidfile)
360 gid = GID(filename=gid_path)
361 gid.dump(dump_parents=True)
364 class AggregateCommands(Commands):
366 def __init__(self, *args, **kwds):
367 self.api = Generic.the_flavour().make_api(interface='aggregate')
370 """Display the Aggregate version"""
371 version = self.api.manager.GetVersion(self.api, {})
372 pprinter.pprint(version)
374 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
375 def status(self, xrn):
376 """Retrieve the status of the slivers belonging to the named slice (Status)"""
377 urns = [Xrn(xrn, 'slice').get_urn()]
378 status = self.api.manager.Status(self.api, urns, [], {})
379 pprinter.pprint(status)
381 @add_options('-r', '--rspec-version', dest='rspec_version', metavar='<rspec_version>',
382 default='GENI', help='version/format of the resulting rspec response')
383 def resources(self, rspec_version='GENI'):
384 """Display the available resources at an aggregate"""
385 options = {'geni_rspec_version': rspec_version}
387 resources = self.api.manager.ListResources(self.api, [], options)
390 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='slice hrn/urn (mandatory)')
391 @add_options('-r', '--rspec', dest='rspec', metavar='<rspec>', help='rspec file (mandatory)')
392 def allocate(self, xrn, rspec):
393 """Allocate slivers"""
394 xrn = Xrn(xrn, 'slice')
395 slice_urn = xrn.get_urn()
396 rspec_string = open(rspec).read()
398 manifest = self.api.manager.Allocate(
399 self.api, slice_urn, [], rspec_string, options)
402 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='slice hrn/urn (mandatory)')
403 def provision(self, xrn):
404 """Provision slivers"""
405 xrn = Xrn(xrn, 'slice')
406 slice_urn = xrn.get_urn()
408 manifest = self.api.manager.provision(
409 self.api, [slice_urn], [], options)
412 @add_options('-x', '--xrn', dest='xrn', metavar='<xrn>', help='slice hrn/urn (mandatory)')
413 def delete(self, xrn):
415 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)
436 return (full_name, SfaAdmin.CATEGORIES[full_name])
438 def summary_usage(self, category=None):
439 print("Usage:", self.script_name + " category command [<options>]")
440 if category and category in SfaAdmin.CATEGORIES:
441 categories = [category]
443 categories = SfaAdmin.CATEGORIES
445 cls = SfaAdmin.CATEGORIES[c]
446 print("==================== category=%s" % c)
447 names = cls.__dict__.keys()
450 method = cls.__dict__[name]
451 if name.startswith('_'):
454 format = "%%-%ds" % margin
455 print("%-15s" % name, end=' ')
456 doc = getattr(method, '__doc__', None)
458 print("<missing __doc__>")
460 lines = [line.strip() for line in doc.split("\n")]
463 for extra_line in lines:
464 print(margin * " ", extra_line)
468 argv = copy.deepcopy(sys.argv)
469 self.script_name = argv.pop(0)
470 # ensure category is specified
474 # ensure category is valid
475 category_input = argv.pop(0)
476 (category_name, category_class) = self.find_category(category_input)
477 if not category_name or not category_class:
478 self.summary_usage(category_name)
480 usage = "%%prog %s command [options]" % (category_name)
481 parser = OptionParser(usage=usage)
483 # ensure command is valid
484 category_instance = category_class()
485 commands = category_instance._get_commands()
487 # xxx what is this about ?
488 command_name = '__call__'
490 command_input = argv.pop(0)
491 command_name = Candidates(commands).only_match(command_input)
493 if command_name and hasattr(category_instance, command_name):
494 command = getattr(category_instance, command_name)
496 self.summary_usage(category_name)
498 # ensure options are valid
499 usage = "%%prog %s %s [options]" % (category_name, command_name)
500 parser = OptionParser(usage=usage)
501 for args, kwdargs in getattr(command, 'add_options', []):
502 parser.add_option(*args, **kwdargs)
503 (opts, cmd_args) = parser.parse_args(argv)
504 cmd_kwds = vars(opts)
506 # dont overrride meth
507 for k, v in cmd_kwds.items():
513 # print "invoking %s *=%s **=%s"%(command.__name__, cmd_args,
515 command(*cmd_args, **cmd_kwds)
518 print("Possible wrong number of arguments supplied")
520 # traceback.print_exc()
521 print(command.__doc__)
526 print("Command failed, please check log for more info")