2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
17 from lxml import etree
18 from StringIO import StringIO
19 from optparse import OptionParser
20 from pprint import PrettyPrinter
21 from tempfile import mkstemp
23 from sfa.trust.certificate import Keypair, Certificate
24 from sfa.trust.gid import GID
25 from sfa.trust.credential import Credential
26 from sfa.trust.sfaticket import SfaTicket
28 from sfa.util.faults import SfaInvalidArgument
29 from sfa.util.sfalogging import sfi_logger
30 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
31 from sfa.util.config import Config
32 from sfa.util.version import version_core
33 from sfa.util.cache import Cache
34 from sfa.util.printable import printable
36 from sfa.storage.record import Record
38 from sfa.rspecs.rspec import RSpec
39 from sfa.rspecs.rspec_converter import RSpecConverter
40 from sfa.rspecs.version_manager import VersionManager
42 from sfa.client.sfaclientlib import SfaClientBootstrap
43 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
44 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
45 from sfa.client.return_value import ReturnValue
46 from sfa.client.candidates import Candidates
47 from sfa.client.manifolduploader import ManifoldUploader
50 DEFAULT_RSPEC_VERSION = "GENI 3"
52 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
53 terminal_render, filter_records
56 def display_rspec(rspec, format='rspec'):
58 tree = etree.parse(StringIO(rspec))
60 result = root.xpath("./network/site/node/hostname/text()")
61 elif format in ['ip']:
62 # The IP address is not yet part of the new RSpec
63 # so this doesn't do anything yet.
64 tree = etree.parse(StringIO(rspec))
66 result = root.xpath("./network/site/node/ipv4/text()")
73 def display_list(results):
74 for result in results:
77 def display_records(recordList, dump=False):
78 ''' Print all fields in the record'''
79 for record in recordList:
80 display_record(record, dump)
82 def display_record(record, dump=False):
84 record.dump(sort=True)
86 info = record.getdict()
87 print "{} ({})".format(info['hrn'], info['type'])
91 def filter_records(type, records):
93 for record in records:
94 if (record['type'] == type) or (type == "all"):
95 filtered_records.append(record)
96 return filtered_records
99 def credential_printable (cred):
100 credential = Credential(cred=cred)
102 result += credential.pretty_cred()
104 rights = credential.get_privileges()
105 result += "type={}\n".format(credential.type)
106 result += "version={}\n".format(credential.version)
107 result += "rights={}\n".format(rights)
110 def show_credentials (cred_s):
111 if not isinstance (cred_s,list): cred_s = [cred_s]
113 print "Using Credential {}".format(credential_printable(cred))
115 ########## save methods
118 def save_raw_to_file(var, filename, format='text', banner=None):
120 _save_raw_to_file(var, sys.stdout, format, banner)
122 with open(filename, w) as fileobj:
123 _save_raw_to_file(var, fileobj, format, banner)
124 print "(Over)wrote {}".format(filename)
126 def _save_raw_to_file(var, f, format, banner):
128 if banner: f.write(banner+"\n")
129 f.write("{}".format(var))
130 if banner: f.write('\n'+banner+"\n")
131 elif format == "pickled":
132 f.write(pickle.dumps(var))
133 elif format == "json":
134 f.write(json.dumps(var)) # python 2.6
136 # this should never happen
137 print "unknown output format", format
140 def save_rspec_to_file(rspec, filename):
141 if not filename.endswith(".rspec"):
142 filename = filename + ".rspec"
143 with open(filename, 'w') as f:
144 f.write("{}".format(rspec))
145 print "(Over)wrote {}".format(filename)
147 def save_record_to_file(filename, record_dict):
148 record = Record(dict=record_dict)
149 xml = record.save_as_xml()
150 with codecs.open(filename, encoding='utf-8',mode="w") as f:
152 print "(Over)wrote {}".format(filename)
154 def save_records_to_file(filename, record_dicts, format="xml"):
156 for index, record_dict in enumerate(record_dicts):
157 save_record_to_file(filename + "." + str(index), record_dict)
158 elif format == "xmllist":
159 with open(filename, "w") as f:
160 f.write("<recordlist>\n")
161 for record_dict in record_dicts:
162 record_obj = Record(dict=record_dict)
163 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
164 f.write("</recordlist>\n")
165 print "(Over)wrote {}".format(filename)
167 elif format == "hrnlist":
168 with open(filename, "w") as f:
169 for record_dict in record_dicts:
170 record_obj = Record(dict=record_dict)
171 f.write(record_obj.hrn + "\n")
172 print "(Over)wrote {}".format(filename)
175 # this should never happen
176 print "unknown output format", format
178 # minimally check a key argument
179 def check_ssh_key (key):
180 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
181 return re.match(good_ssh_key, key, re.IGNORECASE)
184 def normalize_type (type):
185 if type.startswith('au'):
187 elif type.startswith('us'):
189 elif type.startswith('sl'):
191 elif type.startswith('no'):
193 elif type.startswith('ag'):
195 elif type.startswith('al'):
198 print 'unknown type {} - should start with one of au|us|sl|no|ag|al'.format(type)
201 def load_record_from_opts(options):
203 if hasattr(options, 'xrn') and options.xrn:
204 if hasattr(options, 'type') and options.type:
205 xrn = Xrn(options.xrn, options.type)
207 xrn = Xrn(options.xrn)
208 record_dict['urn'] = xrn.get_urn()
209 record_dict['hrn'] = xrn.get_hrn()
210 record_dict['type'] = xrn.get_type()
211 if hasattr(options, 'key') and options.key:
213 pubkey = open(options.key, 'r').read()
216 if not check_ssh_key (pubkey):
217 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
218 record_dict['reg-keys'] = [pubkey]
219 if hasattr(options, 'slices') and options.slices:
220 record_dict['slices'] = options.slices
221 if hasattr(options, 'reg_researchers') and options.reg_researchers is not None:
222 record_dict['reg-researchers'] = options.reg_researchers
223 if hasattr(options, 'email') and options.email:
224 record_dict['email'] = options.email
225 # authorities can have a name for standalone deployment
226 if hasattr(options, 'name') and options.name:
227 record_dict['name'] = options.name
228 if hasattr(options, 'reg_pis') and options.reg_pis:
229 record_dict['reg-pis'] = options.reg_pis
231 # handle extra settings
232 record_dict.update(options.extras)
234 return Record(dict=record_dict)
236 def load_record_from_file(filename):
237 f=codecs.open(filename, encoding="utf-8", mode="r")
238 xml_string = f.read()
240 return Record(xml=xml_string)
244 def unique_call_id(): return uuid.uuid4().urn
246 ########## a simple model for maintaing 3 doc attributes per command (instead of just one)
247 # essentially for the methods that implement a subcommand like sfi list
248 # we need to keep track of
249 # (*) doc a few lines that tell what it does, still located in __doc__
250 # (*) args_string a simple one-liner that describes mandatory arguments
251 # (*) example well, one or several releant examples
253 # since __doc__ only accounts for one, we use this simple mechanism below
254 # however we keep doc in place for easier migration
256 from functools import wraps
258 # we use a list as well as a dict so we can keep track of the order
262 def declare_command (args_string, example,aliases=None):
264 name=getattr(m,'__name__')
265 doc=getattr(m,'__doc__',"-- missing doc --")
266 doc=doc.strip(" \t\n")
267 commands_list.append(name)
268 # last item is 'canonical' name, so we can know which commands are aliases
269 command_tuple=(doc, args_string, example,name)
270 commands_dict[name]=command_tuple
271 if aliases is not None:
272 for alias in aliases:
273 commands_list.append(alias)
274 commands_dict[alias]=command_tuple
276 def new_method (*args, **kwds): return m(*args, **kwds)
281 def remove_none_fields (record):
282 none_fields=[ k for (k,v) in record.items() if v is None ]
283 for k in none_fields: del record[k]
289 # dirty hack to make this class usable from the outside
290 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
293 def default_sfi_dir ():
294 if os.path.isfile("./sfi_config"):
297 return os.path.expanduser("~/.sfi/")
299 # dummy to meet Sfi's expectations for its 'options' field
300 # i.e. s/t we can do setattr on
304 def __init__ (self,options=None):
305 if options is None: options=Sfi.DummyOptions()
306 for opt in Sfi.required_options:
307 if not hasattr(options,opt): setattr(options,opt,None)
308 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
309 self.options = options
311 self.authority = None
312 self.logger = sfi_logger
313 self.logger.enable_console()
314 ### various auxiliary material that we keep at hand
316 # need to call this other than just 'config' as we have a command/method with that name
317 self.config_instance=None
318 self.config_file=None
319 self.client_bootstrap=None
321 ### suitable if no reasonable command has been provided
322 def print_commands_help (self, options):
323 verbose=getattr(options,'verbose')
324 format3="%10s %-35s %s"
328 print format3%("command", "cmd_args", "description")
332 self.create_parser_global().print_help()
333 # preserve order from the code
334 for command in commands_list:
336 (doc, args_string, example, canonical) = commands_dict[command]
338 print "Cannot find info on command %s - skipped"%command
342 if command==canonical:
343 doc = doc.replace("\n", "\n" + format3offset * ' ')
344 print format3 % (command,args_string,doc)
346 self.create_parser_command(command).print_help()
348 print format3 % (command,"<<alias for %s>>"%canonical,"")
350 ### now if a known command was found we can be more verbose on that one
351 def print_help (self):
352 print "==================== Generic sfi usage"
353 self.sfi_parser.print_help()
354 (doc, _, example, canonical) = commands_dict[self.command]
355 if canonical != self.command:
356 print "\n==================== NOTE: {} is an alias for genuine {}"\
357 .format(self.command, canonical)
358 self.command = canonical
359 print "\n==================== Purpose of {}".format(self.command)
361 print "\n==================== Specific usage for {}".format(self.command)
362 self.command_parser.print_help()
364 print "\n==================== {} example(s)".format(self.command)
367 def create_parser_global(self):
368 # Generate command line parser
369 parser = OptionParser(add_help_option=False,
370 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
371 description="Commands: {}".format(" ".join(commands_list)))
372 parser.add_option("-r", "--registry", dest="registry",
373 help="root registry", metavar="URL", default=None)
374 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
375 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
376 parser.add_option("-R", "--raw", dest="raw", default=None,
377 help="Save raw, unparsed server response to a file")
378 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
379 help="raw file format ([text]|pickled|json)", default="text",
380 choices=("text","pickled","json"))
381 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
382 help="text string to write before and after raw output")
383 parser.add_option("-d", "--dir", dest="sfi_dir",
384 help="config & working directory - default is %default",
385 metavar="PATH", default=Sfi.default_sfi_dir())
386 parser.add_option("-u", "--user", dest="user",
387 help="user name", metavar="HRN", default=None)
388 parser.add_option("-a", "--auth", dest="auth",
389 help="authority name", metavar="HRN", default=None)
390 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
391 help="verbose mode - cumulative")
392 parser.add_option("-D", "--debug",
393 action="store_true", dest="debug", default=False,
394 help="Debug (xml-rpc) protocol messages")
395 # would it make sense to use ~/.ssh/id_rsa as a default here ?
396 parser.add_option("-k", "--private-key",
397 action="store", dest="user_private_key", default=None,
398 help="point to the private key file to use if not yet installed in sfi_dir")
399 parser.add_option("-t", "--timeout", dest="timeout", default=None,
400 help="Amout of time to wait before timing out the request")
401 parser.add_option("-h", "--help",
402 action="store_true", dest="help", default=False,
403 help="one page summary on commands & exit")
404 parser.disable_interspersed_args()
409 def create_parser_command(self, command):
410 if command not in commands_dict:
411 msg="Invalid command\n"
413 msg += ','.join(commands_list)
414 self.logger.critical(msg)
417 # retrieve args_string
418 (_, args_string, __,canonical) = commands_dict[command]
420 parser = OptionParser(add_help_option=False,
421 usage="sfi [sfi_options] {} [cmd_options] {}"\
422 .format(command, args_string))
423 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
424 help="Summary of one command usage")
426 if canonical in ("config"):
427 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
428 help='how myslice config variables as well')
430 if canonical in ("version"):
431 parser.add_option("-l","--local",
432 action="store_true", dest="version_local", default=False,
433 help="display version of the local client")
435 if canonical in ("version", "trusted"):
436 parser.add_option("-R","--registry_interface",
437 action="store_true", dest="registry_interface", default=False,
438 help="target the registry interface instead of slice interface")
440 if canonical in ("register", "update"):
441 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
442 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type (2 first chars is enough)', default=None)
443 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
444 parser.add_option('-n', '--name', dest='name', default="", help="name (optional for authorities)")
445 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
447 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
448 default='', type="str", action='callback', callback=optparse_listvalue_callback)
449 parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='<researchers>',
450 help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback',
451 callback=optparse_listvalue_callback)
452 parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
453 default='', type="str", action='callback', callback=optparse_listvalue_callback)
454 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
455 action="callback", callback=optparse_dictvalue_callback, nargs=1,
456 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
458 # user specifies remote aggregate/sm/component
459 if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
460 "action", "shutdown", "renew", "status"):
461 parser.add_option("-d", "--delegate", dest="delegate", default=None,
463 help="Include a credential delegated to the user's root"+\
464 "authority in set of credentials for this call")
466 # show_credential option
467 if canonical in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
468 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
469 help="show credential(s) used in human-readable form")
470 if canonical in ("renew"):
471 parser.add_option("-l","--as-long-as-possible",dest='alap',action='store_true',default=False,
472 help="renew as long as possible")
473 # registy filter option
474 if canonical in ("list", "show", "remove"):
475 parser.add_option("-t", "--type", dest="type", metavar="<type>",
477 help="type filter - 2 first chars is enough ([all]|user|slice|authority|node|aggregate)")
478 if canonical in ("show"):
479 parser.add_option("-k","--key",dest="keys",action="append",default=[],
480 help="specify specific keys to be displayed from record")
481 parser.add_option("-n","--no-details",dest="no_details",action="store_true",default=False,
482 help="call Resolve without the 'details' option")
483 if canonical in ("resources", "describe"):
485 parser.add_option("-r", "--rspec-version", dest="rspec_version", default=DEFAULT_RSPEC_VERSION,
486 help="schema type and version of resulting RSpec (default:{})".format(DEFAULT_RSPEC_VERSION))
487 # disable/enable cached rspecs
488 parser.add_option("-c", "--current", dest="current", default=False,
490 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
492 parser.add_option("-f", "--format", dest="format", type="choice",
493 help="display format ([xml]|dns|ip)", default="xml",
494 choices=("xml", "dns", "ip"))
495 #panos: a new option to define the type of information about resources a user is interested in
496 parser.add_option("-i", "--info", dest="info",
497 help="optional component information", default=None)
498 # a new option to retrieve or not reservation-oriented RSpecs (leases)
499 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
500 help="Retrieve or not reservation-oriented RSpecs ([resources]|leases|all)",
501 choices=("all", "resources", "leases"), default="resources")
504 if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
505 parser.add_option("-o", "--output", dest="file",
506 help="output XML to file", metavar="FILE", default=None)
508 if canonical in ("show", "list"):
509 parser.add_option("-f", "--format", dest="format", type="choice",
510 help="display format ([text]|xml)", default="text",
511 choices=("text", "xml"))
513 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
514 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
515 choices=("xml", "xmllist", "hrnlist"))
516 if canonical == 'list':
517 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
518 help="list all child records", default=False)
519 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
520 help="gives details, like user keys", default=False)
521 if canonical in ("delegate"):
522 parser.add_option("-u", "--user",
523 action="store_true", dest="delegate_user", default=False,
524 help="delegate your own credentials; default if no other option is provided")
525 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
526 metavar="slice_hrn", help="delegate cred. for slice HRN")
527 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
528 metavar='auth_hrn', help="delegate cred for auth HRN")
529 # this primarily is a shorthand for -A my_hrn^
530 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
531 help="delegate your PI credentials, so s.t. like -A your_hrn^")
532 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
533 help="""by default the mandatory argument is expected to be a user,
534 use this if you mean an authority instead""")
536 if canonical in ("myslice"):
537 parser.add_option("-p","--password",dest='password',action='store',default=None,
538 help="specify mainfold password on the command line")
539 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
540 metavar="slice_hrn", help="delegate cred. for slice HRN")
541 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
542 metavar='auth_hrn', help="delegate PI cred for auth HRN")
543 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
544 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
550 # Main: parse arguments and dispatch to command
552 def dispatch(self, command, command_options, command_args):
553 (doc, args_string, example, canonical) = commands_dict[command]
554 method=getattr(self, canonical, None)
556 print "sfi: unknown command {}".format(command)
557 raise SystemExit("Unknown command {}".format(command))
558 for arg in command_args:
559 if 'help' in arg or arg == '-h':
562 return method(command_options, command_args)
565 self.sfi_parser = self.create_parser_global()
566 (options, args) = self.sfi_parser.parse_args()
568 self.print_commands_help(options)
570 self.options = options
572 self.logger.setLevelFromOptVerbose(self.options.verbose)
575 self.logger.critical("No command given. Use -h for help.")
576 self.print_commands_help(options)
579 # complete / find unique match with command set
580 command_candidates = Candidates (commands_list)
582 command = command_candidates.only_match(input)
584 self.print_commands_help(options)
586 # second pass options parsing
588 self.command_parser = self.create_parser_command(command)
589 (command_options, command_args) = self.command_parser.parse_args(args[1:])
590 if command_options.help:
593 self.command_options = command_options
595 # allow incoming types on 2 characters only
596 if hasattr(command_options, 'type'):
597 command_options.type = normalize_type(command_options.type)
598 if not command_options.type:
603 self.logger.debug("Command={}".format(self.command))
606 retcod = self.dispatch(command, command_options, command_args)
610 self.logger.log_exc ("sfi command {} failed".format(command))
615 def read_config(self):
616 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
617 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
619 if Config.is_ini(config_file):
620 config = Config (config_file)
622 # try upgrading from shell config format
623 fp, fn = mkstemp(suffix='sfi_config', text=True)
625 # we need to preload the sections we want parsed
626 # from the shell config
627 config.add_section('sfi')
628 # sface users should be able to use this same file to configure their stuff
629 config.add_section('sface')
630 # manifold users should be able to specify the details
631 # of their backend server here for 'sfi myslice'
632 config.add_section('myslice')
633 config.load(config_file)
635 shutil.move(config_file, shell_config_file)
637 config.save(config_file)
640 self.logger.critical("Failed to read configuration file {}".format(config_file))
641 self.logger.info("Make sure to remove the export clauses and to add quotes")
642 if self.options.verbose==0:
643 self.logger.info("Re-run with -v for more details")
645 self.logger.log_exc("Could not read config file {}".format(config_file))
648 self.config_instance=config
651 if (self.options.sm is not None):
652 self.sm_url = self.options.sm
653 elif hasattr(config, "SFI_SM"):
654 self.sm_url = config.SFI_SM
656 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in {}".format(config_file))
660 if (self.options.registry is not None):
661 self.reg_url = self.options.registry
662 elif hasattr(config, "SFI_REGISTRY"):
663 self.reg_url = config.SFI_REGISTRY
665 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in {}".format(config_file))
669 if (self.options.user is not None):
670 self.user = self.options.user
671 elif hasattr(config, "SFI_USER"):
672 self.user = config.SFI_USER
674 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in {}".format(config_file))
678 if (self.options.auth is not None):
679 self.authority = self.options.auth
680 elif hasattr(config, "SFI_AUTH"):
681 self.authority = config.SFI_AUTH
683 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in {}".format(config_file))
686 self.config_file=config_file
691 # Get various credential and spec files
693 # Establishes limiting conventions
694 # - conflates MAs and SAs
695 # - assumes last token in slice name is unique
697 # Bootstraps credentials
698 # - bootstrap user credential from self-signed certificate
699 # - bootstrap authority credential from user credential
700 # - bootstrap slice credential from user credential
703 # init self-signed cert, user credentials and gid
704 def bootstrap (self):
705 if self.options.verbose:
706 self.logger.info("Initializing SfaClientBootstrap with {}".format(self.reg_url))
707 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
709 # if -k is provided, use this to initialize private key
710 if self.options.user_private_key:
711 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
713 # trigger legacy compat code if needed
714 # the name has changed from just <leaf>.pkey to <hrn>.pkey
715 if not os.path.isfile(client_bootstrap.private_key_filename()):
716 self.logger.info ("private key not found, trying legacy name")
718 legacy_private_key = os.path.join (self.options.sfi_dir, "{}.pkey"
719 .format(Xrn.unescape(get_leaf(self.user))))
720 self.logger.debug("legacy_private_key={}"
721 .format(legacy_private_key))
722 client_bootstrap.init_private_key_if_missing (legacy_private_key)
723 self.logger.info("Copied private key from legacy location {}"
724 .format(legacy_private_key))
726 self.logger.log_exc("Can't find private key ")
730 client_bootstrap.bootstrap_my_gid()
731 # extract what's needed
732 self.private_key = client_bootstrap.private_key()
733 self.my_credential_string = client_bootstrap.my_credential_string ()
734 self.my_credential = {'geni_type': 'geni_sfa',
736 'geni_value': self.my_credential_string}
737 self.my_gid = client_bootstrap.my_gid ()
738 self.client_bootstrap = client_bootstrap
741 def my_authority_credential_string(self):
742 if not self.authority:
743 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
745 return self.client_bootstrap.authority_credential_string (self.authority)
747 def authority_credential_string(self, auth_hrn):
748 return self.client_bootstrap.authority_credential_string (auth_hrn)
750 def slice_credential_string(self, name):
751 return self.client_bootstrap.slice_credential_string (name)
753 def slice_credential(self, name):
754 return {'geni_type': 'geni_sfa',
756 'geni_value': self.slice_credential_string(name)}
758 # xxx should be supported by sfaclientbootstrap as well
759 def delegate_cred(self, object_cred, hrn, type='authority'):
760 # the gid and hrn of the object we are delegating
761 if isinstance(object_cred, str):
762 object_cred = Credential(string=object_cred)
763 object_gid = object_cred.get_gid_object()
764 object_hrn = object_gid.get_hrn()
766 if not object_cred.get_privileges().get_all_delegate():
767 self.logger.error("Object credential {} does not have delegate bit set"
771 # the delegating user's gid
772 caller_gidfile = self.my_gid()
774 # the gid of the user who will be delegated to
775 delegee_gid = self.client_bootstrap.gid(hrn,type)
776 delegee_hrn = delegee_gid.get_hrn()
777 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
778 return dcred.save_to_string(save_parents=True)
781 # Management of the servers
786 if not hasattr (self, 'registry_proxy'):
787 self.logger.info("Contacting Registry at: {}".format(self.reg_url))
788 self.registry_proxy \
789 = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
790 timeout=self.options.timeout, verbose=self.options.debug)
791 return self.registry_proxy
795 if not hasattr (self, 'sliceapi_proxy'):
796 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
797 if hasattr(self.command_options,'component') and self.command_options.component:
798 # resolve the hrn at the registry
799 node_hrn = self.command_options.component
800 records = self.registry().Resolve(node_hrn, self.my_credential_string)
801 records = filter_records('node', records)
803 self.logger.warning("No such component:{}".format(opts.component))
805 cm_url = "http://{}:{}/".format(record['hostname'], CM_PORT)
806 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
808 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
809 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
810 self.sm_url = 'http://' + self.sm_url
811 self.logger.info("Contacting Slice Manager at: {}".format(self.sm_url))
812 self.sliceapi_proxy \
813 = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
814 timeout=self.options.timeout, verbose=self.options.debug)
815 return self.sliceapi_proxy
817 def get_cached_server_version(self, server):
818 # check local cache first
821 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
822 cache_key = server.url + "-version"
824 cache = Cache(cache_file)
827 self.logger.info("Local cache not found at: {}".format(cache_file))
830 version = cache.get(cache_key)
833 result = server.GetVersion()
834 version= ReturnValue.get_value(result)
835 # cache version for 20 minutes
836 cache.add(cache_key, version, ttl= 60*20)
837 self.logger.info("Updating cache file {}".format(cache_file))
838 cache.save_to_file(cache_file)
842 ### resurrect this temporarily so we can support V1 aggregates for a while
843 def server_supports_options_arg(self, server):
845 Returns true if server support the optional call_id arg, false otherwise.
847 server_version = self.get_cached_server_version(server)
849 # xxx need to rewrite this
850 if int(server_version.get('geni_api')) >= 2:
854 def server_supports_call_id_arg(self, server):
855 server_version = self.get_cached_server_version(server)
857 if 'sfa' in server_version and 'code_tag' in server_version:
858 code_tag = server_version['code_tag']
859 code_tag_parts = code_tag.split("-")
860 version_parts = code_tag_parts[0].split(".")
861 major, minor = version_parts[0], version_parts[1]
862 rev = code_tag_parts[1]
863 if int(major) == 1 and minor == 0 and build >= 22:
867 ### ois = options if supported
868 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
869 def ois (self, server, option_dict):
870 if self.server_supports_options_arg (server):
872 elif self.server_supports_call_id_arg (server):
873 return [ unique_call_id () ]
877 ### cis = call_id if supported - like ois
878 def cis (self, server):
879 if self.server_supports_call_id_arg (server):
880 return [ unique_call_id ]
884 ######################################## miscell utilities
885 def get_rspec_file(self, rspec):
886 if (os.path.isabs(rspec)):
889 file = os.path.join(self.options.sfi_dir, rspec)
890 if (os.path.isfile(file)):
893 self.logger.critical("No such rspec file {}".format(rspec))
896 def get_record_file(self, record):
897 if (os.path.isabs(record)):
900 file = os.path.join(self.options.sfi_dir, record)
901 if (os.path.isfile(file)):
904 self.logger.critical("No such registry record file {}".format(record))
908 # helper function to analyze raw output
909 # for main : return 0 if everything is fine, something else otherwise (mostly 1 for now)
910 def success (self, raw):
911 return_value=ReturnValue (raw)
912 output=ReturnValue.get_output(return_value)
913 # means everything is fine
916 # something went wrong
917 print 'ERROR:',output
920 #==========================================================================
921 # Following functions implement the commands
923 # Registry-related commands
924 #==========================================================================
926 @declare_command("","")
927 def config (self, options, args):
928 "Display contents of current config"
929 print "# From configuration file {}".format(self.config_file)
930 flags=[ ('sfi', [ ('registry','reg_url'),
931 ('auth','authority'),
937 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
939 for (section, tuples) in flags:
940 print "[{}]".format(section)
942 for (external_name, internal_name) in tuples:
943 print "{:-20} = {}".format(external_name, getattr(self, internal_name))
946 varname = "{}_{}".format(section.upper(), name.upper())
947 value = getattr(self.config_instance,varname)
948 print "{:-20} = {}".format(name, value)
949 # xxx should analyze result
952 @declare_command("","")
953 def version(self, options, args):
955 display an SFA server version (GetVersion)
956 or version information about sfi itself
958 if options.version_local:
959 version=version_core()
961 if options.registry_interface:
962 server=self.registry()
964 server = self.sliceapi()
965 result = server.GetVersion()
966 version = ReturnValue.get_value(result)
968 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
970 pprinter = PrettyPrinter(indent=4)
971 pprinter.pprint(version)
972 # xxx should analyze result
975 @declare_command("authority","")
976 def list(self, options, args):
978 list entries in named authority registry (List)
985 if options.recursive:
986 opts['recursive'] = options.recursive
988 if options.show_credential:
989 show_credentials(self.my_credential_string)
991 list = self.registry().List(hrn, self.my_credential_string, options)
993 raise Exception, "Not enough parameters for the 'list' command"
995 # filter on person, slice, site, node, etc.
996 # This really should be in the self.filter_records funct def comment...
997 list = filter_records(options.type, list)
998 terminal_render (list, options)
1000 save_records_to_file(options.file, list, options.fileformat)
1001 # xxx should analyze result
1004 @declare_command("name","")
1005 def show(self, options, args):
1007 show details about named registry record (Resolve)
1013 # explicitly require Resolve to run in details mode
1015 if not options.no_details: resolve_options['details']=True
1016 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
1017 record_dicts = filter_records(options.type, record_dicts)
1018 if not record_dicts:
1019 self.logger.error("No record of type {}".format(options.type))
1021 # user has required to focus on some keys
1023 def project (record):
1025 for key in options.keys:
1026 try: projected[key]=record[key]
1029 record_dicts = [ project (record) for record in record_dicts ]
1030 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
1031 for record in records:
1032 if (options.format == "text"): record.dump(sort=True)
1033 else: print record.save_as_xml()
1035 save_records_to_file(options.file, record_dicts, options.fileformat)
1036 # xxx should analyze result
1039 # this historically was named 'add', it is now 'register' with an alias for legacy
1040 @declare_command("[xml-filename]","",['add'])
1041 def register(self, options, args):
1042 """create new record in registry (Register)
1043 from command line options (recommended)
1044 old-school method involving an xml file still supported"""
1046 auth_cred = self.my_authority_credential_string()
1047 if options.show_credential:
1048 show_credentials(auth_cred)
1055 record_filepath = args[0]
1056 rec_file = self.get_record_file(record_filepath)
1057 record_dict.update(load_record_from_file(rec_file).todict())
1059 print "Cannot load record file {}".format(record_filepath)
1062 record_dict.update(load_record_from_opts(options).todict())
1063 # we should have a type by now
1064 if 'type' not in record_dict :
1067 # this is still planetlab dependent.. as plc will whine without that
1068 # also, it's only for adding
1069 if record_dict['type'] == 'user':
1070 if not 'first_name' in record_dict:
1071 record_dict['first_name'] = record_dict['hrn']
1072 if 'last_name' not in record_dict:
1073 record_dict['last_name'] = record_dict['hrn']
1074 register = self.registry().Register(record_dict, auth_cred)
1075 # xxx looks like the result here is not ReturnValue-compatible
1076 #return self.success (register)
1077 # xxx should analyze result
1080 @declare_command("[xml-filename]","")
1081 def update(self, options, args):
1082 """update record into registry (Update)
1083 from command line options (recommended)
1084 old-school method involving an xml file still supported"""
1087 record_filepath = args[0]
1088 rec_file = self.get_record_file(record_filepath)
1089 record_dict.update(load_record_from_file(rec_file).todict())
1091 record_dict.update(load_record_from_opts(options).todict())
1092 # at the very least we need 'type' here
1093 if 'type' not in record_dict or record_dict['type'] is None:
1097 # don't translate into an object, as this would possibly distort
1098 # user-provided data; e.g. add an 'email' field to Users
1099 if record_dict['type'] in ['user']:
1100 if record_dict['hrn'] == self.user:
1101 cred = self.my_credential_string
1103 cred = self.my_authority_credential_string()
1104 elif record_dict['type'] in ['slice']:
1106 cred = self.slice_credential_string(record_dict['hrn'])
1107 except ServerException, e:
1108 # XXX smbaker -- once we have better error return codes, update this
1109 # to do something better than a string compare
1110 if "Permission error" in e.args[0]:
1111 cred = self.my_authority_credential_string()
1114 elif record_dict['type'] in ['authority']:
1115 cred = self.my_authority_credential_string()
1116 elif record_dict['type'] in ['node']:
1117 cred = self.my_authority_credential_string()
1119 raise Exception("unknown record type {}".format(record_dict['type']))
1120 if options.show_credential:
1121 show_credentials(cred)
1122 update = self.registry().Update(record_dict, cred)
1123 # xxx looks like the result here is not ReturnValue-compatible
1124 #return self.success(update)
1125 # xxx should analyze result
1128 @declare_command("hrn","")
1129 def remove(self, options, args):
1130 "remove registry record by name (Remove)"
1131 auth_cred = self.my_authority_credential_string()
1139 if options.show_credential:
1140 show_credentials(auth_cred)
1141 remove = self.registry().Remove(hrn, auth_cred, type)
1142 # xxx looks like the result here is not ReturnValue-compatible
1143 #return self.success (remove)
1144 # xxx should analyze result
1147 # ==================================================================
1148 # Slice-related commands
1149 # ==================================================================
1151 # show rspec for named slice
1152 @declare_command("","",['discover'])
1153 def resources(self, options, args):
1155 discover available resources (ListResources)
1157 server = self.sliceapi()
1160 creds = [self.my_credential]
1161 if options.delegate:
1162 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1163 if options.show_credential:
1164 show_credentials(creds)
1166 # no need to check if server accepts the options argument since the options has
1167 # been a required argument since v1 API
1169 # always send call_id to v2 servers
1170 api_options ['call_id'] = unique_call_id()
1171 # ask for cached value if available
1172 api_options ['cached'] = True
1174 api_options['info'] = options.info
1175 if options.list_leases:
1176 api_options['list_leases'] = options.list_leases
1178 if options.current == True:
1179 api_options['cached'] = False
1181 api_options['cached'] = True
1182 if options.rspec_version:
1183 version_manager = VersionManager()
1184 server_version = self.get_cached_server_version(server)
1185 if 'sfa' in server_version:
1186 # just request the version the client wants
1187 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1189 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1191 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1192 list_resources = server.ListResources (creds, api_options)
1193 value = ReturnValue.get_value(list_resources)
1194 if self.options.raw:
1195 save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
1196 if options.file is not None:
1197 save_rspec_to_file(value, options.file)
1198 if (self.options.raw is None) and (options.file is None):
1199 display_rspec(value, options.format)
1200 return self.success(list_resources)
1202 @declare_command("slice_hrn","")
1203 def describe(self, options, args):
1205 shows currently allocated/provisioned resources
1206 of the named slice or set of slivers (Describe)
1208 server = self.sliceapi()
1211 creds = [self.slice_credential(args[0])]
1212 if options.delegate:
1213 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1214 if options.show_credential:
1215 show_credentials(creds)
1217 api_options = {'call_id': unique_call_id(),
1219 'info': options.info,
1220 'list_leases': options.list_leases,
1221 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1224 api_options['info'] = options.info
1226 if options.rspec_version:
1227 version_manager = VersionManager()
1228 server_version = self.get_cached_server_version(server)
1229 if 'sfa' in server_version:
1230 # just request the version the client wants
1231 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1233 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1234 urn = Xrn(args[0], type='slice').get_urn()
1235 remove_none_fields(api_options)
1236 describe = server.Describe([urn], creds, api_options)
1237 value = ReturnValue.get_value(describe)
1238 if self.options.raw:
1239 save_raw_to_file(describe, self.options.raw, self.options.rawformat, self.options.rawbanner)
1240 if options.file is not None:
1241 save_rspec_to_file(value['geni_rspec'], options.file)
1242 if (self.options.raw is None) and (options.file is None):
1243 display_rspec(value['geni_rspec'], options.format)
1244 return self.success (describe)
1246 @declare_command("slice_hrn [<sliver_urn>...]","")
1247 def delete(self, options, args):
1249 de-allocate and de-provision all or named slivers of the named slice (Delete)
1251 server = self.sliceapi()
1255 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1258 # we have sliver urns
1259 sliver_urns = args[1:]
1261 # we provision all the slivers of the slice
1262 sliver_urns = [slice_urn]
1265 slice_cred = self.slice_credential(slice_hrn)
1266 creds = [slice_cred]
1268 # options and call_id when supported
1270 api_options ['call_id'] = unique_call_id()
1271 if options.show_credential:
1272 show_credentials(creds)
1273 delete = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1274 value = ReturnValue.get_value(delete)
1275 if self.options.raw:
1276 save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
1279 return self.success (delete)
1281 @declare_command("slice_hrn rspec","")
1282 def allocate(self, options, args):
1284 allocate resources to the named slice (Allocate)
1286 server = self.sliceapi()
1287 server_version = self.get_cached_server_version(server)
1292 rspec_file = self.get_rspec_file(args[1])
1294 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1297 creds = [self.slice_credential(slice_hrn)]
1299 delegated_cred = None
1300 if server_version.get('interface') == 'slicemgr':
1301 # delegate our cred to the slice manager
1302 # do not delegate cred to slicemgr...not working at the moment
1304 #if server_version.get('hrn'):
1305 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1306 #elif server_version.get('urn'):
1307 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1309 if options.show_credential:
1310 show_credentials(creds)
1314 api_options ['call_id'] = unique_call_id()
1318 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1319 remove_none_fields(slice_records[0])
1320 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1321 slice_record = slice_records[0]
1322 user_hrns = slice_record['reg-researchers']
1323 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1324 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1325 sfa_users = sfa_users_arg(user_records, slice_record)
1326 geni_users = pg_users_arg(user_records)
1328 api_options['sfa_users'] = sfa_users
1329 api_options['geni_users'] = geni_users
1331 with open(rspec_file) as rspec:
1332 rspec_xml = rspec.read()
1333 allocate = server.Allocate(slice_urn, creds, rspec_xml, api_options)
1334 value = ReturnValue.get_value(allocate)
1335 if self.options.raw:
1336 save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
1337 if options.file is not None:
1338 save_rspec_to_file (value['geni_rspec'], options.file)
1339 if (self.options.raw is None) and (options.file is None):
1341 return self.success(allocate)
1343 @declare_command("slice_hrn [<sliver_urn>...]","")
1344 def provision(self, options, args):
1346 provision all or named already allocated slivers of the named slice (Provision)
1348 server = self.sliceapi()
1349 server_version = self.get_cached_server_version(server)
1351 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1353 # we have sliver urns
1354 sliver_urns = args[1:]
1356 # we provision all the slivers of the slice
1357 sliver_urns = [slice_urn]
1360 creds = [self.slice_credential(slice_hrn)]
1361 delegated_cred = None
1362 if server_version.get('interface') == 'slicemgr':
1363 # delegate our cred to the slice manager
1364 # do not delegate cred to slicemgr...not working at the moment
1366 #if server_version.get('hrn'):
1367 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1368 #elif server_version.get('urn'):
1369 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1371 if options.show_credential:
1372 show_credentials(creds)
1375 api_options ['call_id'] = unique_call_id()
1377 # set the requtested rspec version
1378 version_manager = VersionManager()
1379 rspec_version = version_manager._get_version('geni', '3').to_dict()
1380 api_options['geni_rspec_version'] = rspec_version
1383 # need to pass along user keys to the aggregate.
1385 # { urn: urn:publicid:IDN+emulab.net+user+alice
1386 # keys: [<ssh key A>, <ssh key B>]
1389 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1390 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1391 slice_record = slice_records[0]
1392 user_hrns = slice_record['reg-researchers']
1393 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1394 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1395 users = pg_users_arg(user_records)
1397 api_options['geni_users'] = users
1398 provision = server.Provision(sliver_urns, creds, api_options)
1399 value = ReturnValue.get_value(provision)
1400 if self.options.raw:
1401 save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
1402 if options.file is not None:
1403 save_rspec_to_file (value['geni_rspec'], options.file)
1404 if (self.options.raw is None) and (options.file is None):
1406 return self.success(provision)
1408 @declare_command("slice_hrn","")
1409 def status(self, options, args):
1411 retrieve the status of the slivers belonging to the named slice (Status)
1413 server = self.sliceapi()
1417 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1420 slice_cred = self.slice_credential(slice_hrn)
1421 creds = [slice_cred]
1423 # options and call_id when supported
1425 api_options['call_id']=unique_call_id()
1426 if options.show_credential:
1427 show_credentials(creds)
1428 status = server.Status([slice_urn], creds, *self.ois(server,api_options))
1429 value = ReturnValue.get_value(status)
1430 if self.options.raw:
1431 save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
1434 return self.success (status)
1436 @declare_command("slice_hrn [<sliver_urn>...] action","")
1437 def action(self, options, args):
1439 Perform the named operational action on all or named slivers of the named slice
1441 server = self.sliceapi()
1445 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1447 # we have sliver urns
1448 sliver_urns = args[1:-1]
1450 # we provision all the slivers of the slice
1451 sliver_urns = [slice_urn]
1454 slice_cred = self.slice_credential(args[0])
1455 creds = [slice_cred]
1456 if options.delegate:
1457 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1458 creds.append(delegated_cred)
1460 perform_action = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1461 value = ReturnValue.get_value(perform_action)
1462 if self.options.raw:
1463 save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
1466 return self.success (perform_action)
1468 @declare_command("slice_hrn [<sliver_urn>...] time",
1469 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1470 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1471 "sfi renew onelab.ple.heartbeat +5d",
1472 "sfi renew onelab.ple.heartbeat +3w",
1473 "sfi renew onelab.ple.heartbeat +2m",]))
1474 def renew(self, options, args):
1478 server = self.sliceapi()
1483 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1486 # we have sliver urns
1487 sliver_urns = args[1:-1]
1489 # we provision all the slivers of the slice
1490 sliver_urns = [slice_urn]
1491 input_time = args[-1]
1493 # time: don't try to be smart on the time format, server-side will
1495 slice_cred = self.slice_credential(args[0])
1496 creds = [slice_cred]
1497 # options and call_id when supported
1499 api_options['call_id']=unique_call_id()
1501 api_options['geni_extend_alap']=True
1502 if options.show_credential:
1503 show_credentials(creds)
1504 renew = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1505 value = ReturnValue.get_value(renew)
1506 if self.options.raw:
1507 save_raw_to_file(renew, self.options.raw, self.options.rawformat, self.options.rawbanner)
1510 return self.success(renew)
1512 @declare_command("slice_hrn","")
1513 def shutdown(self, options, args):
1515 shutdown named slice (Shutdown)
1517 server = self.sliceapi()
1520 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1522 slice_cred = self.slice_credential(slice_hrn)
1523 creds = [slice_cred]
1524 shutdown = server.Shutdown(slice_urn, creds)
1525 value = ReturnValue.get_value(shutdown)
1526 if self.options.raw:
1527 save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
1530 return self.success (shutdown)
1532 @declare_command("[name]","")
1533 def gid(self, options, args):
1535 Create a GID (CreateGid)
1540 target_hrn = args[0]
1541 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1542 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1544 filename = options.file
1546 filename = os.sep.join([self.options.sfi_dir, '{}.gid'.format(target_hrn)])
1547 self.logger.info("writing {} gid to {}".format(target_hrn, filename))
1548 GID(string=gid).save_to_file(filename)
1549 # xxx should analyze result
1552 ####################
1553 @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1555 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1556 the set of credentials in the scope for this call would be
1557 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1559 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1561 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1562 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1563 because of the two -s options
1566 def delegate (self, options, args):
1568 (locally) create delegate credential for use by given hrn
1569 make sure to check for 'sfi myslice' instead if you plan
1576 # support for several delegations in the same call
1577 # so first we gather the things to do
1579 for slice_hrn in options.delegate_slices:
1580 message = "{}.slice".format(slice_hrn)
1581 original = self.slice_credential_string(slice_hrn)
1582 tuples.append ( (message, original,) )
1583 if options.delegate_pi:
1584 my_authority=self.authority
1585 message = "{}.pi".format(my_authority)
1586 original = self.my_authority_credential_string()
1587 tuples.append ( (message, original,) )
1588 for auth_hrn in options.delegate_auths:
1589 message = "{}.auth".format(auth_hrn)
1590 original = self.authority_credential_string(auth_hrn)
1591 tuples.append ( (message, original, ) )
1592 # if nothing was specified at all at this point, let's assume -u
1593 if not tuples: options.delegate_user=True
1595 if options.delegate_user:
1596 message = "{}.user".format(self.user)
1597 original = self.my_credential_string
1598 tuples.append ( (message, original, ) )
1600 # default type for beneficial is user unless -A
1601 if options.delegate_to_authority: to_type='authority'
1602 else: to_type='user'
1604 # let's now handle all this
1605 # it's all in the filenaming scheme
1606 for (message,original) in tuples:
1607 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1608 delegated_credential = Credential (string=delegated_string)
1609 filename = os.path.join(self.options.sfi_dir,
1610 "{}_for_{}.{}.cred".format(message, to_hrn, to_type))
1611 delegated_credential.save_to_file(filename, save_parents=True)
1612 self.logger.info("delegated credential for {} to {} and wrote to {}"
1613 .format(message, to_hrn, filename))
1615 ####################
1616 @declare_command("","""$ less +/myslice sfi_config
1618 backend = http://manifold.pl.sophia.inria.fr:7080
1619 # the HRN that myslice uses, so that we are delegating to
1620 delegate = ple.upmc.slicebrowser
1621 # platform - this is a myslice concept
1623 # username - as of this writing (May 2013) a simple login name
1627 will first collect the slices that you are part of, then make sure
1628 all your credentials are up-to-date (read: refresh expired ones)
1629 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1630 and upload them all on myslice backend, using 'platform' and 'user'.
1631 A password will be prompted for the upload part.
1633 $ sfi -v myslice -- or sfi -vv myslice
1634 same but with more and more verbosity
1636 $ sfi m -b http://mymanifold.foo.com:7080/
1637 is synonym to sfi myslice as no other command starts with an 'm'
1638 and uses a custom backend for this one call
1641 def myslice (self, options, args):
1643 """ This helper is for refreshing your credentials at myslice; it will
1644 * compute all the slices that you currently have credentials on
1645 * refresh all your credentials (you as a user and pi, your slices)
1646 * upload them to the manifold backend server
1647 for last phase, sfi_config is read to look for the [myslice] section,
1648 and namely the 'backend', 'delegate' and 'user' settings"""
1654 # enable info by default
1655 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1656 ### the rough sketch goes like this
1657 # (0) produce a p12 file
1658 self.client_bootstrap.my_pkcs12()
1660 # (a) rain check for sufficient config in sfi_config
1662 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1663 for key in myslice_keys:
1665 # oct 2013 - I'm finding myself juggling with config files
1666 # so a couple of command-line options can now override config
1667 if hasattr(options,key) and getattr(options,key) is not None:
1668 value=getattr(options,key)
1670 full_key="MYSLICE_" + key.upper()
1671 value=getattr(self.config_instance,full_key,None)
1673 myslice_dict[key]=value
1675 print "Unsufficient config, missing key {} in [myslice] section of sfi_config"\
1677 if len(myslice_dict) != len(myslice_keys):
1680 # (b) figure whether we are PI for the authority where we belong
1681 self.logger.info("Resolving our own id {}".format(self.user))
1682 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1683 if len(my_records) != 1:
1684 print "Cannot Resolve {} -- exiting".format(self.user)
1686 my_record = my_records[0]
1687 my_auths_all = my_record['reg-pi-authorities']
1688 self.logger.info("Found {} authorities that we are PI for".format(len(my_auths_all)))
1689 self.logger.debug("They are {}".format(my_auths_all))
1691 my_auths = my_auths_all
1692 if options.delegate_auths:
1693 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1694 self.logger.debug("Restricted to user-provided auths {}".format(my_auths))
1696 # (c) get the set of slices that we are in
1697 my_slices_all=my_record['reg-slices']
1698 self.logger.info("Found {} slices that we are member of".format(len(my_slices_all)))
1699 self.logger.debug("They are: {}".format(my_slices_all))
1701 my_slices = my_slices_all
1702 # if user provided slices, deal only with these - if they are found
1703 if options.delegate_slices:
1704 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1705 self.logger.debug("Restricted to user-provided slices: {}".format(my_slices))
1707 # (d) make sure we have *valid* credentials for all these
1709 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1710 for auth_hrn in my_auths:
1711 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1712 for slice_hrn in my_slices:
1713 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1715 # (e) check for the delegated version of these
1716 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1717 # switch to myslice using an authority instead of a user
1718 delegatee_type='user'
1719 delegatee_hrn=myslice_dict['delegate']
1720 hrn_delegated_credentials = []
1721 for (hrn, htype, credential) in hrn_credentials:
1722 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1723 # save these so user can monitor what she's uploaded
1724 filename = os.path.join ( self.options.sfi_dir,
1725 "{}.{}_for_{}.{}.cred"\
1726 .format(hrn, htype, delegatee_hrn, delegatee_type))
1727 with file(filename,'w') as f:
1728 f.write(delegated_credential)
1729 self.logger.debug("(Over)wrote {}".format(filename))
1730 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1732 # (f) and finally upload them to manifold server
1733 # xxx todo add an option so the password can be set on the command line
1734 # (but *NOT* in the config file) so other apps can leverage this
1735 self.logger.info("Uploading on backend at {}".format(myslice_dict['backend']))
1736 uploader = ManifoldUploader (logger=self.logger,
1737 url=myslice_dict['backend'],
1738 platform=myslice_dict['platform'],
1739 username=myslice_dict['username'],
1740 password=options.password)
1741 uploader.prompt_all()
1742 (count_all,count_success)=(0,0)
1743 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1745 inspect=Credential(string=delegated_credential)
1746 expire_datetime=inspect.get_expiration()
1747 message="{} ({}) [exp:{}]".format(hrn, htype, expire_datetime)
1748 if uploader.upload(delegated_credential,message=message):
1751 self.logger.info("Successfully uploaded {}/{} credentials"
1752 .format(count_success, count_all))
1754 # at first I thought we would want to save these,
1755 # like 'sfi delegate does' but on second thought
1756 # it is probably not helpful as people would not
1757 # need to run 'sfi delegate' at all anymore
1758 if count_success != count_all: sys.exit(1)
1759 # xxx should analyze result
1762 @declare_command("cred","")
1763 def trusted(self, options, args):
1765 return the trusted certs at this interface (get_trusted_certs)
1767 if options.registry_interface:
1768 server=self.registry()
1770 server = self.sliceapi()
1771 cred = self.my_authority_credential_string()
1772 trusted_certs = server.get_trusted_certs(cred)
1773 if not options.registry_interface:
1774 trusted_certs = ReturnValue.get_value(trusted_certs)
1776 for trusted_cert in trusted_certs:
1777 print "\n===========================================================\n"
1778 gid = GID(string=trusted_cert)
1780 cert = Certificate(string=trusted_cert)
1781 self.logger.debug('Sfi.trusted -> {}'.format(cert.get_subject()))
1782 print "Certificate:\n{}\n\n".format(trusted_cert)
1783 # xxx should analyze result