2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
6 from __future__ import print_function
19 from lxml import etree
20 from StringIO import StringIO
21 from optparse import OptionParser
22 from pprint import PrettyPrinter
23 from tempfile import mkstemp
25 from sfa.trust.certificate import Keypair, Certificate
26 from sfa.trust.gid import GID
27 from sfa.trust.credential import Credential
28 from sfa.trust.sfaticket import SfaTicket
30 from sfa.util.faults import SfaInvalidArgument
31 from sfa.util.sfalogging import sfi_logger
32 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
33 from sfa.util.config import Config
34 from sfa.util.version import version_core
35 from sfa.util.cache import Cache
36 from sfa.util.printable import printable
38 from sfa.storage.record import Record
40 from sfa.rspecs.rspec import RSpec
41 from sfa.rspecs.rspec_converter import RSpecConverter
42 from sfa.rspecs.version_manager import VersionManager
44 from sfa.client.sfaclientlib import SfaClientBootstrap
45 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
46 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
47 from sfa.client.return_value import ReturnValue
48 from sfa.client.candidates import Candidates
49 from sfa.client.manifolduploader import ManifoldUploader
52 DEFAULT_RSPEC_VERSION = "GENI 3"
54 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
55 terminal_render, filter_records
58 def display_rspec(rspec, format='rspec'):
60 tree = etree.parse(StringIO(rspec))
62 result = root.xpath("./network/site/node/hostname/text()")
63 elif format in ['ip']:
64 # The IP address is not yet part of the new RSpec
65 # so this doesn't do anything yet.
66 tree = etree.parse(StringIO(rspec))
68 result = root.xpath("./network/site/node/ipv4/text()")
75 def display_list(results):
76 for result in results:
79 def display_records(recordList, dump=False):
80 ''' Print all fields in the record'''
81 for record in recordList:
82 display_record(record, dump)
84 def display_record(record, dump=False):
86 record.dump(sort=True)
88 info = record.getdict()
89 print("{} ({})".format(info['hrn'], info['type']))
93 def filter_records(type, records):
95 for record in records:
96 if (record['type'] == type) or (type == "all"):
97 filtered_records.append(record)
98 return filtered_records
101 def credential_printable (cred):
102 credential = Credential(cred=cred)
104 result += credential.pretty_cred()
106 rights = credential.get_privileges()
107 result += "type={}\n".format(credential.type)
108 result += "version={}\n".format(credential.version)
109 result += "rights={}\n".format(rights)
112 def show_credentials (cred_s):
113 if not isinstance (cred_s,list): cred_s = [cred_s]
115 print("Using Credential {}".format(credential_printable(cred)))
117 ########## save methods
120 def save_raw_to_file(var, filename, format='text', banner=None):
122 _save_raw_to_file(var, sys.stdout, format, banner)
124 with open(filename, w) as fileobj:
125 _save_raw_to_file(var, fileobj, format, banner)
126 print("(Over)wrote {}".format(filename))
128 def _save_raw_to_file(var, f, format, banner):
130 if banner: f.write(banner+"\n")
131 f.write("{}".format(var))
132 if banner: f.write('\n'+banner+"\n")
133 elif format == "pickled":
134 f.write(pickle.dumps(var))
135 elif format == "json":
136 f.write(json.dumps(var)) # python 2.6
138 # this should never happen
139 print("unknown output format", format)
142 def save_rspec_to_file(rspec, filename):
143 if not filename.endswith(".rspec"):
144 filename = filename + ".rspec"
145 with open(filename, 'w') as f:
146 f.write("{}".format(rspec))
147 print("(Over)wrote {}".format(filename))
149 def save_record_to_file(filename, record_dict):
150 record = Record(dict=record_dict)
151 xml = record.save_as_xml()
152 with codecs.open(filename, encoding='utf-8',mode="w") as f:
154 print("(Over)wrote {}".format(filename))
156 def save_records_to_file(filename, record_dicts, format="xml"):
158 for index, record_dict in enumerate(record_dicts):
159 save_record_to_file(filename + "." + str(index), record_dict)
160 elif format == "xmllist":
161 with open(filename, "w") as f:
162 f.write("<recordlist>\n")
163 for record_dict in record_dicts:
164 record_obj = Record(dict=record_dict)
165 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
166 f.write("</recordlist>\n")
167 print("(Over)wrote {}".format(filename))
169 elif format == "hrnlist":
170 with open(filename, "w") as f:
171 for record_dict in record_dicts:
172 record_obj = Record(dict=record_dict)
173 f.write(record_obj.hrn + "\n")
174 print("(Over)wrote {}".format(filename))
177 # this should never happen
178 print("unknown output format", format)
180 # minimally check a key argument
181 def check_ssh_key (key):
182 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
183 return re.match(good_ssh_key, key, re.IGNORECASE)
186 def normalize_type (type):
187 if type.startswith('au'):
189 elif type.startswith('us'):
191 elif type.startswith('sl'):
193 elif type.startswith('no'):
195 elif type.startswith('ag'):
197 elif type.startswith('al'):
200 print('unknown type {} - should start with one of au|us|sl|no|ag|al'.format(type))
203 def load_record_from_opts(options):
205 if hasattr(options, 'xrn') and options.xrn:
206 if hasattr(options, 'type') and options.type:
207 xrn = Xrn(options.xrn, options.type)
209 xrn = Xrn(options.xrn)
210 record_dict['urn'] = xrn.get_urn()
211 record_dict['hrn'] = xrn.get_hrn()
212 record_dict['type'] = xrn.get_type()
213 if hasattr(options, 'key') and options.key:
215 pubkey = open(options.key, 'r').read()
218 if not check_ssh_key (pubkey):
219 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
220 record_dict['reg-keys'] = [pubkey]
221 if hasattr(options, 'slices') and options.slices:
222 record_dict['slices'] = options.slices
223 if hasattr(options, 'reg_researchers') and options.reg_researchers is not None:
224 record_dict['reg-researchers'] = options.reg_researchers
225 if hasattr(options, 'email') and options.email:
226 record_dict['email'] = options.email
227 # authorities can have a name for standalone deployment
228 if hasattr(options, 'name') and options.name:
229 record_dict['name'] = options.name
230 if hasattr(options, 'reg_pis') and options.reg_pis:
231 record_dict['reg-pis'] = options.reg_pis
233 # handle extra settings
234 record_dict.update(options.extras)
236 return Record(dict=record_dict)
238 def load_record_from_file(filename):
239 with codecs.open(filename, encoding="utf-8", mode="r") as f:
241 return Record(xml=xml_str)
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)))
945 for external_name, internal_name in tuples:
946 varname = "{}_{}".format(section.upper(), external_name.upper())
947 value = getattr(self.config_instance,varname)
948 print("{:<20} = {}".format(external_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).record_to_dict())
1059 print("Cannot load record file {}".format(record_filepath))
1062 record_dict.update(load_record_from_opts(options).record_to_dict())
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).record_to_dict())
1091 record_dict.update(load_record_from_opts(options).record_to_dict())
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': options.rspec_version}
1191 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1193 list_resources = server.ListResources (creds, api_options)
1194 value = ReturnValue.get_value(list_resources)
1195 if self.options.raw:
1196 save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
1197 if options.file is not None:
1198 save_rspec_to_file(value, options.file)
1199 if (self.options.raw is None) and (options.file is None):
1200 display_rspec(value, options.format)
1201 return self.success(list_resources)
1203 @declare_command("slice_hrn","")
1204 def describe(self, options, args):
1206 shows currently allocated/provisioned resources
1207 of the named slice or set of slivers (Describe)
1209 server = self.sliceapi()
1212 creds = [self.slice_credential(args[0])]
1213 if options.delegate:
1214 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1215 if options.show_credential:
1216 show_credentials(creds)
1218 api_options = {'call_id': unique_call_id(),
1220 'info': options.info,
1221 'list_leases': options.list_leases,
1222 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1225 api_options['info'] = options.info
1227 if options.rspec_version:
1228 version_manager = VersionManager()
1229 server_version = self.get_cached_server_version(server)
1230 if 'sfa' in server_version:
1231 # just request the version the client wants
1232 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1234 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1235 urn = Xrn(args[0], type='slice').get_urn()
1236 remove_none_fields(api_options)
1237 describe = server.Describe([urn], creds, api_options)
1238 value = ReturnValue.get_value(describe)
1239 if self.options.raw:
1240 save_raw_to_file(describe, self.options.raw, self.options.rawformat, self.options.rawbanner)
1241 if options.file is not None:
1242 save_rspec_to_file(value['geni_rspec'], options.file)
1243 if (self.options.raw is None) and (options.file is None):
1244 display_rspec(value['geni_rspec'], options.format)
1245 return self.success (describe)
1247 @declare_command("slice_hrn [<sliver_urn>...]","")
1248 def delete(self, options, args):
1250 de-allocate and de-provision all or named slivers of the named slice (Delete)
1252 server = self.sliceapi()
1256 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1259 # we have sliver urns
1260 sliver_urns = args[1:]
1262 # we provision all the slivers of the slice
1263 sliver_urns = [slice_urn]
1266 slice_cred = self.slice_credential(slice_hrn)
1267 creds = [slice_cred]
1269 # options and call_id when supported
1271 api_options ['call_id'] = unique_call_id()
1272 if options.show_credential:
1273 show_credentials(creds)
1274 delete = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1275 value = ReturnValue.get_value(delete)
1276 if self.options.raw:
1277 save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
1280 return self.success (delete)
1282 @declare_command("slice_hrn rspec","")
1283 def allocate(self, options, args):
1285 allocate resources to the named slice (Allocate)
1287 server = self.sliceapi()
1288 server_version = self.get_cached_server_version(server)
1293 rspec_file = self.get_rspec_file(args[1])
1295 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1298 creds = [self.slice_credential(slice_hrn)]
1300 delegated_cred = None
1301 if server_version.get('interface') == 'slicemgr':
1302 # delegate our cred to the slice manager
1303 # do not delegate cred to slicemgr...not working at the moment
1305 #if server_version.get('hrn'):
1306 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1307 #elif server_version.get('urn'):
1308 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1310 if options.show_credential:
1311 show_credentials(creds)
1315 api_options ['call_id'] = unique_call_id()
1319 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1320 remove_none_fields(slice_records[0])
1321 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1322 slice_record = slice_records[0]
1323 user_hrns = slice_record['reg-researchers']
1324 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1325 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1326 sfa_users = sfa_users_arg(user_records, slice_record)
1327 geni_users = pg_users_arg(user_records)
1329 api_options['sfa_users'] = sfa_users
1330 api_options['geni_users'] = geni_users
1332 with open(rspec_file) as rspec:
1333 rspec_xml = rspec.read()
1334 allocate = server.Allocate(slice_urn, creds, rspec_xml, api_options)
1335 value = ReturnValue.get_value(allocate)
1336 if self.options.raw:
1337 save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
1338 if options.file is not None:
1339 save_rspec_to_file (value['geni_rspec'], options.file)
1340 if (self.options.raw is None) and (options.file is None):
1342 return self.success(allocate)
1344 @declare_command("slice_hrn [<sliver_urn>...]","")
1345 def provision(self, options, args):
1347 provision all or named already allocated slivers of the named slice (Provision)
1349 server = self.sliceapi()
1350 server_version = self.get_cached_server_version(server)
1352 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1354 # we have sliver urns
1355 sliver_urns = args[1:]
1357 # we provision all the slivers of the slice
1358 sliver_urns = [slice_urn]
1361 creds = [self.slice_credential(slice_hrn)]
1362 delegated_cred = None
1363 if server_version.get('interface') == 'slicemgr':
1364 # delegate our cred to the slice manager
1365 # do not delegate cred to slicemgr...not working at the moment
1367 #if server_version.get('hrn'):
1368 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1369 #elif server_version.get('urn'):
1370 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1372 if options.show_credential:
1373 show_credentials(creds)
1376 api_options ['call_id'] = unique_call_id()
1378 # set the requtested rspec version
1379 version_manager = VersionManager()
1380 rspec_version = version_manager._get_version('geni', '3').to_dict()
1381 api_options['geni_rspec_version'] = rspec_version
1384 # need to pass along user keys to the aggregate.
1386 # { urn: urn:publicid:IDN+emulab.net+user+alice
1387 # keys: [<ssh key A>, <ssh key B>]
1390 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1391 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']!=[]:
1392 slice_record = slice_records[0]
1393 user_hrns = slice_record['reg-researchers']
1394 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1395 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1396 users = pg_users_arg(user_records)
1398 api_options['geni_users'] = users
1399 provision = server.Provision(sliver_urns, creds, api_options)
1400 value = ReturnValue.get_value(provision)
1401 if self.options.raw:
1402 save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
1403 if options.file is not None:
1404 save_rspec_to_file (value['geni_rspec'], options.file)
1405 if (self.options.raw is None) and (options.file is None):
1407 return self.success(provision)
1409 @declare_command("slice_hrn","")
1410 def status(self, options, args):
1412 retrieve the status of the slivers belonging to the named slice (Status)
1414 server = self.sliceapi()
1418 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1421 slice_cred = self.slice_credential(slice_hrn)
1422 creds = [slice_cred]
1424 # options and call_id when supported
1426 api_options['call_id']=unique_call_id()
1427 if options.show_credential:
1428 show_credentials(creds)
1429 status = server.Status([slice_urn], creds, *self.ois(server,api_options))
1430 value = ReturnValue.get_value(status)
1431 if self.options.raw:
1432 save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
1435 return self.success (status)
1437 @declare_command("slice_hrn [<sliver_urn>...] action","")
1438 def action(self, options, args):
1440 Perform the named operational action on all or named slivers of the named slice
1442 server = self.sliceapi()
1446 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1448 # we have sliver urns
1449 sliver_urns = args[1:-1]
1451 # we provision all the slivers of the slice
1452 sliver_urns = [slice_urn]
1455 slice_cred = self.slice_credential(args[0])
1456 creds = [slice_cred]
1457 if options.delegate:
1458 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1459 creds.append(delegated_cred)
1461 perform_action = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1462 value = ReturnValue.get_value(perform_action)
1463 if self.options.raw:
1464 save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
1467 return self.success (perform_action)
1469 @declare_command("slice_hrn [<sliver_urn>...] time",
1470 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1471 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1472 "sfi renew onelab.ple.heartbeat +5d",
1473 "sfi renew onelab.ple.heartbeat +3w",
1474 "sfi renew onelab.ple.heartbeat +2m",]))
1475 def renew(self, options, args):
1479 server = self.sliceapi()
1484 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1487 # we have sliver urns
1488 sliver_urns = args[1:-1]
1490 # we provision all the slivers of the slice
1491 sliver_urns = [slice_urn]
1492 input_time = args[-1]
1494 # time: don't try to be smart on the time format, server-side will
1496 slice_cred = self.slice_credential(args[0])
1497 creds = [slice_cred]
1498 # options and call_id when supported
1500 api_options['call_id']=unique_call_id()
1502 api_options['geni_extend_alap']=True
1503 if options.show_credential:
1504 show_credentials(creds)
1505 renew = server.Renew(sliver_urns, creds, input_time, *self.ois(server,api_options))
1506 value = ReturnValue.get_value(renew)
1507 if self.options.raw:
1508 save_raw_to_file(renew, self.options.raw, self.options.rawformat, self.options.rawbanner)
1511 return self.success(renew)
1513 @declare_command("slice_hrn","")
1514 def shutdown(self, options, args):
1516 shutdown named slice (Shutdown)
1518 server = self.sliceapi()
1521 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1523 slice_cred = self.slice_credential(slice_hrn)
1524 creds = [slice_cred]
1525 shutdown = server.Shutdown(slice_urn, creds)
1526 value = ReturnValue.get_value(shutdown)
1527 if self.options.raw:
1528 save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
1531 return self.success (shutdown)
1533 @declare_command("[name]","")
1534 def gid(self, options, args):
1536 Create a GID (CreateGid)
1541 target_hrn = args[0]
1542 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1543 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1545 filename = options.file
1547 filename = os.sep.join([self.options.sfi_dir, '{}.gid'.format(target_hrn)])
1548 self.logger.info("writing {} gid to {}".format(target_hrn, filename))
1549 GID(string=gid).save_to_file(filename)
1550 # xxx should analyze result
1553 ####################
1554 @declare_command("to_hrn","""$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1556 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1557 the set of credentials in the scope for this call would be
1558 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1560 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1562 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1563 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1564 because of the two -s options
1567 def delegate (self, options, args):
1569 (locally) create delegate credential for use by given hrn
1570 make sure to check for 'sfi myslice' instead if you plan
1577 # support for several delegations in the same call
1578 # so first we gather the things to do
1580 for slice_hrn in options.delegate_slices:
1581 message = "{}.slice".format(slice_hrn)
1582 original = self.slice_credential_string(slice_hrn)
1583 tuples.append ( (message, original,) )
1584 if options.delegate_pi:
1585 my_authority=self.authority
1586 message = "{}.pi".format(my_authority)
1587 original = self.my_authority_credential_string()
1588 tuples.append ( (message, original,) )
1589 for auth_hrn in options.delegate_auths:
1590 message = "{}.auth".format(auth_hrn)
1591 original = self.authority_credential_string(auth_hrn)
1592 tuples.append ( (message, original, ) )
1593 # if nothing was specified at all at this point, let's assume -u
1594 if not tuples: options.delegate_user=True
1596 if options.delegate_user:
1597 message = "{}.user".format(self.user)
1598 original = self.my_credential_string
1599 tuples.append ( (message, original, ) )
1601 # default type for beneficial is user unless -A
1602 if options.delegate_to_authority: to_type='authority'
1603 else: to_type='user'
1605 # let's now handle all this
1606 # it's all in the filenaming scheme
1607 for (message,original) in tuples:
1608 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1609 delegated_credential = Credential (string=delegated_string)
1610 filename = os.path.join(self.options.sfi_dir,
1611 "{}_for_{}.{}.cred".format(message, to_hrn, to_type))
1612 delegated_credential.save_to_file(filename, save_parents=True)
1613 self.logger.info("delegated credential for {} to {} and wrote to {}"
1614 .format(message, to_hrn, filename))
1616 ####################
1617 @declare_command("","""$ less +/myslice sfi_config
1619 backend = http://manifold.pl.sophia.inria.fr:7080
1620 # the HRN that myslice uses, so that we are delegating to
1621 delegate = ple.upmc.slicebrowser
1622 # platform - this is a myslice concept
1624 # username - as of this writing (May 2013) a simple login name
1628 will first collect the slices that you are part of, then make sure
1629 all your credentials are up-to-date (read: refresh expired ones)
1630 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1631 and upload them all on myslice backend, using 'platform' and 'user'.
1632 A password will be prompted for the upload part.
1634 $ sfi -v myslice -- or sfi -vv myslice
1635 same but with more and more verbosity
1637 $ sfi m -b http://mymanifold.foo.com:7080/
1638 is synonym to sfi myslice as no other command starts with an 'm'
1639 and uses a custom backend for this one call
1642 def myslice (self, options, args):
1644 """ This helper is for refreshing your credentials at myslice; it will
1645 * compute all the slices that you currently have credentials on
1646 * refresh all your credentials (you as a user and pi, your slices)
1647 * upload them to the manifold backend server
1648 for last phase, sfi_config is read to look for the [myslice] section,
1649 and namely the 'backend', 'delegate' and 'user' settings"""
1655 # enable info by default
1656 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1657 ### the rough sketch goes like this
1658 # (0) produce a p12 file
1659 self.client_bootstrap.my_pkcs12()
1661 # (a) rain check for sufficient config in sfi_config
1663 myslice_keys=[ 'backend', 'delegate', 'platform', 'username']
1664 for key in myslice_keys:
1666 # oct 2013 - I'm finding myself juggling with config files
1667 # so a couple of command-line options can now override config
1668 if hasattr(options,key) and getattr(options,key) is not None:
1669 value=getattr(options,key)
1671 full_key="MYSLICE_" + key.upper()
1672 value=getattr(self.config_instance,full_key,None)
1674 myslice_dict[key]=value
1676 print("Unsufficient config, missing key {} in [myslice] section of sfi_config"
1678 if len(myslice_dict) != len(myslice_keys):
1681 # (b) figure whether we are PI for the authority where we belong
1682 self.logger.info("Resolving our own id {}".format(self.user))
1683 my_records=self.registry().Resolve(self.user,self.my_credential_string)
1684 if len(my_records) != 1:
1685 print("Cannot Resolve {} -- exiting".format(self.user))
1687 my_record = my_records[0]
1688 my_auths_all = my_record['reg-pi-authorities']
1689 self.logger.info("Found {} authorities that we are PI for".format(len(my_auths_all)))
1690 self.logger.debug("They are {}".format(my_auths_all))
1692 my_auths = my_auths_all
1693 if options.delegate_auths:
1694 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1695 self.logger.debug("Restricted to user-provided auths {}".format(my_auths))
1697 # (c) get the set of slices that we are in
1698 my_slices_all=my_record['reg-slices']
1699 self.logger.info("Found {} slices that we are member of".format(len(my_slices_all)))
1700 self.logger.debug("They are: {}".format(my_slices_all))
1702 my_slices = my_slices_all
1703 # if user provided slices, deal only with these - if they are found
1704 if options.delegate_slices:
1705 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1706 self.logger.debug("Restricted to user-provided slices: {}".format(my_slices))
1708 # (d) make sure we have *valid* credentials for all these
1710 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1711 for auth_hrn in my_auths:
1712 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1713 for slice_hrn in my_slices:
1714 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1716 # (e) check for the delegated version of these
1717 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1718 # switch to myslice using an authority instead of a user
1719 delegatee_type='user'
1720 delegatee_hrn=myslice_dict['delegate']
1721 hrn_delegated_credentials = []
1722 for (hrn, htype, credential) in hrn_credentials:
1723 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1724 # save these so user can monitor what she's uploaded
1725 filename = os.path.join ( self.options.sfi_dir,
1726 "{}.{}_for_{}.{}.cred"\
1727 .format(hrn, htype, delegatee_hrn, delegatee_type))
1728 with file(filename,'w') as f:
1729 f.write(delegated_credential)
1730 self.logger.debug("(Over)wrote {}".format(filename))
1731 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1733 # (f) and finally upload them to manifold server
1734 # xxx todo add an option so the password can be set on the command line
1735 # (but *NOT* in the config file) so other apps can leverage this
1736 self.logger.info("Uploading on backend at {}".format(myslice_dict['backend']))
1737 uploader = ManifoldUploader (logger=self.logger,
1738 url=myslice_dict['backend'],
1739 platform=myslice_dict['platform'],
1740 username=myslice_dict['username'],
1741 password=options.password)
1742 uploader.prompt_all()
1743 (count_all,count_success)=(0,0)
1744 for (hrn,htype,delegated_credential,filename) in hrn_delegated_credentials:
1746 inspect=Credential(string=delegated_credential)
1747 expire_datetime=inspect.get_expiration()
1748 message="{} ({}) [exp:{}]".format(hrn, htype, expire_datetime)
1749 if uploader.upload(delegated_credential,message=message):
1752 self.logger.info("Successfully uploaded {}/{} credentials"
1753 .format(count_success, count_all))
1755 # at first I thought we would want to save these,
1756 # like 'sfi delegate does' but on second thought
1757 # it is probably not helpful as people would not
1758 # need to run 'sfi delegate' at all anymore
1759 if count_success != count_all: sys.exit(1)
1760 # xxx should analyze result
1763 @declare_command("cred","")
1764 def trusted(self, options, args):
1766 return the trusted certs at this interface (get_trusted_certs)
1768 if options.registry_interface:
1769 server=self.registry()
1771 server = self.sliceapi()
1772 cred = self.my_authority_credential_string()
1773 trusted_certs = server.get_trusted_certs(cred)
1774 if not options.registry_interface:
1775 trusted_certs = ReturnValue.get_value(trusted_certs)
1777 for trusted_cert in trusted_certs:
1778 print("\n===========================================================\n")
1779 gid = GID(string=trusted_cert)
1781 cert = Certificate(string=trusted_cert)
1782 self.logger.debug('Sfi.trusted -> {}'.format(cert.get_subject()))
1783 print("Certificate:\n{}\n\n".format(trusted_cert))
1784 # xxx should analyze result