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):
308 setattr(options, opt, None)
309 if not hasattr(options, 'sfi_dir'):
310 options.sfi_dir = Sfi.default_sfi_dir()
311 self.options = options
313 self.authority = None
314 self.logger = sfi_logger
315 self.logger.enable_console()
316 ### various auxiliary material that we keep at hand
318 # need to call this other than just 'config' as we have a command/method with that name
319 self.config_instance = None
320 self.config_file = None
321 self.client_bootstrap = None
323 ### suitable if no reasonable command has been provided
324 def print_commands_help (self, options):
325 verbose = getattr(options, 'verbose')
326 format3 = "%10s %-35s %s"
330 print(format3%("command", "cmd_args", "description"))
334 self.create_parser_global().print_help()
335 # preserve order from the code
336 for command in commands_list:
338 (doc, args_string, example, canonical) = commands_dict[command]
340 print("Cannot find info on command %s - skipped"%command)
344 if command==canonical:
345 doc = doc.replace("\n", "\n" + format3offset * ' ')
346 print(format3 % (command, args_string, doc))
348 self.create_parser_command(command).print_help()
350 print(format3 % (command, "<<alias for %s>>"%canonical, ""))
352 ### now if a known command was found we can be more verbose on that one
353 def print_help (self):
354 print("==================== Generic sfi usage")
355 self.sfi_parser.print_help()
356 (doc, _, example, canonical) = commands_dict[self.command]
357 if canonical != self.command:
358 print("\n==================== NOTE: {} is an alias for genuine {}"
359 .format(self.command, canonical))
360 self.command = canonical
361 print("\n==================== Purpose of {}".format(self.command))
363 print("\n==================== Specific usage for {}".format(self.command))
364 self.command_parser.print_help()
366 print("\n==================== {} example(s)".format(self.command))
369 def create_parser_global(self):
370 # Generate command line parser
371 parser = OptionParser(add_help_option=False,
372 usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
373 description="Commands: {}".format(" ".join(commands_list)))
374 parser.add_option("-r", "--registry", dest="registry",
375 help="root registry", metavar="URL", default=None)
376 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
377 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
378 parser.add_option("-R", "--raw", dest="raw", default=None,
379 help="Save raw, unparsed server response to a file")
380 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
381 help="raw file format ([text]|pickled|json)", default="text",
382 choices=("text","pickled","json"))
383 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
384 help="text string to write before and after raw output")
385 parser.add_option("-d", "--dir", dest="sfi_dir",
386 help="config & working directory - default is %default",
387 metavar="PATH", default=Sfi.default_sfi_dir())
388 parser.add_option("-u", "--user", dest="user",
389 help="user name", metavar="HRN", default=None)
390 parser.add_option("-a", "--auth", dest="auth",
391 help="authority name", metavar="HRN", default=None)
392 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
393 help="verbose mode - cumulative")
394 parser.add_option("-D", "--debug",
395 action="store_true", dest="debug", default=False,
396 help="Debug (xml-rpc) protocol messages")
397 # would it make sense to use ~/.ssh/id_rsa as a default here ?
398 parser.add_option("-k", "--private-key",
399 action="store", dest="user_private_key", default=None,
400 help="point to the private key file to use if not yet installed in sfi_dir")
401 parser.add_option("-t", "--timeout", dest="timeout", default=None,
402 help="Amout of time to wait before timing out the request")
403 parser.add_option("-h", "--help",
404 action="store_true", dest="help", default=False,
405 help="one page summary on commands & exit")
406 parser.disable_interspersed_args()
411 def create_parser_command(self, command):
412 if command not in commands_dict:
413 msg="Invalid command\n"
415 msg += ','.join(commands_list)
416 self.logger.critical(msg)
419 # retrieve args_string
420 (_, args_string, __, canonical) = commands_dict[command]
422 parser = OptionParser(add_help_option=False,
423 usage="sfi [sfi_options] {} [cmd_options] {}"\
424 .format(command, args_string))
425 parser.add_option ("-h","--help",dest='help',action='store_true',default=False,
426 help="Summary of one command usage")
428 if canonical in ("config"):
429 parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False,
430 help='how myslice config variables as well')
432 if canonical in ("version"):
433 parser.add_option("-l","--local",
434 action="store_true", dest="version_local", default=False,
435 help="display version of the local client")
437 if canonical in ("version", "trusted"):
438 parser.add_option("-R","--registry_interface",
439 action="store_true", dest="registry_interface", default=False,
440 help="target the registry interface instead of slice interface")
442 if canonical in ("register", "update"):
443 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
444 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type (2 first chars is enough)', default=None)
445 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
446 parser.add_option('-n', '--name', dest='name', default="", help="name (optional for authorities)")
447 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
449 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
450 default='', type="str", action='callback', callback=optparse_listvalue_callback)
451 parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='<researchers>',
452 help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback',
453 callback=optparse_listvalue_callback)
454 parser.add_option('-p', '--pis', dest='reg_pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
455 default='', type="str", action='callback', callback=optparse_listvalue_callback)
456 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
457 action="callback", callback=optparse_dictvalue_callback, nargs=1,
458 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
460 # user specifies remote aggregate/sm/component
461 if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
462 "action", "shutdown", "renew", "status"):
463 parser.add_option("-d", "--delegate", dest="delegate", default=None,
465 help="Include a credential delegated to the user's root"+\
466 "authority in set of credentials for this call")
468 # show_credential option
469 if canonical in ("list","resources", "describe", "provision", "allocate", "register","update","remove","delete","status","renew"):
470 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
471 help="show credential(s) used in human-readable form")
472 if canonical in ("renew"):
473 parser.add_option("-l","--as-long-as-possible",dest='alap',action='store_true',default=False,
474 help="renew as long as possible")
475 # registy filter option
476 if canonical in ("list", "show", "remove"):
477 parser.add_option("-t", "--type", dest="type", metavar="<type>",
479 help="type filter - 2 first chars is enough ([all]|user|slice|authority|node|aggregate)")
480 if canonical in ("show"):
481 parser.add_option("-k","--key",dest="keys",action="append",default=[],
482 help="specify specific keys to be displayed from record")
483 parser.add_option("-n","--no-details",dest="no_details",action="store_true",default=False,
484 help="call Resolve without the 'details' option")
485 if canonical in ("resources", "describe"):
487 parser.add_option("-r", "--rspec-version", dest="rspec_version", default=DEFAULT_RSPEC_VERSION,
488 help="schema type and version of resulting RSpec (default:{})".format(DEFAULT_RSPEC_VERSION))
489 # disable/enable cached rspecs
490 parser.add_option("-c", "--current", dest="current", default=False,
492 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
494 parser.add_option("-f", "--format", dest="format", type="choice",
495 help="display format ([xml]|dns|ip)", default="xml",
496 choices=("xml", "dns", "ip"))
497 #panos: a new option to define the type of information about resources a user is interested in
498 parser.add_option("-i", "--info", dest="info",
499 help="optional component information", default=None)
500 # a new option to retrieve or not reservation-oriented RSpecs (leases)
501 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
502 help="Retrieve or not reservation-oriented RSpecs ([resources]|leases|all)",
503 choices=("all", "resources", "leases"), default="resources")
506 if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
507 parser.add_option("-o", "--output", dest="file",
508 help="output XML to file", metavar="FILE", default=None)
510 if canonical in ("show", "list"):
511 parser.add_option("-f", "--format", dest="format", type="choice",
512 help="display format ([text]|xml)", default="text",
513 choices=("text", "xml"))
515 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
516 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
517 choices=("xml", "xmllist", "hrnlist"))
518 if canonical == 'list':
519 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
520 help="list all child records", default=False)
521 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
522 help="gives details, like user keys", default=False)
523 if canonical in ("delegate"):
524 parser.add_option("-u", "--user",
525 action="store_true", dest="delegate_user", default=False,
526 help="delegate your own credentials; default if no other option is provided")
527 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
528 metavar="slice_hrn", help="delegate cred. for slice HRN")
529 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
530 metavar='auth_hrn', help="delegate cred for auth HRN")
531 # this primarily is a shorthand for -A my_hrn^
532 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
533 help="delegate your PI credentials, so s.t. like -A your_hrn^")
534 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
535 help="""by default the mandatory argument is expected to be a user,
536 use this if you mean an authority instead""")
538 if canonical in ("myslice"):
539 parser.add_option("-p","--password",dest='password',action='store',default=None,
540 help="specify mainfold password on the command line")
541 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
542 metavar="slice_hrn", help="delegate cred. for slice HRN")
543 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
544 metavar='auth_hrn', help="delegate PI cred for auth HRN")
545 parser.add_option('-d', '--delegate', dest='delegate', help="Override 'delegate' from the config file")
546 parser.add_option('-b', '--backend', dest='backend', help="Override 'backend' from the config file")
552 # Main: parse arguments and dispatch to command
554 def dispatch(self, command, command_options, command_args):
555 (doc, args_string, example, canonical) = commands_dict[command]
556 method=getattr(self, canonical, None)
558 print("sfi: unknown command {}".format(command))
559 raise SystemExit("Unknown command {}".format(command))
560 for arg in command_args:
561 if 'help' in arg or arg == '-h':
564 return method(command_options, command_args)
567 self.sfi_parser = self.create_parser_global()
568 (options, args) = self.sfi_parser.parse_args()
570 self.print_commands_help(options)
572 self.options = options
574 self.logger.setLevelFromOptVerbose(self.options.verbose)
577 self.logger.critical("No command given. Use -h for help.")
578 self.print_commands_help(options)
581 # complete / find unique match with command set
582 command_candidates = Candidates (commands_list)
584 command = command_candidates.only_match(input)
586 self.print_commands_help(options)
588 # second pass options parsing
589 self.command = command
590 self.command_parser = self.create_parser_command(command)
591 (command_options, command_args) = self.command_parser.parse_args(args[1:])
592 if command_options.help:
595 self.command_options = command_options
597 # allow incoming types on 2 characters only
598 if hasattr(command_options, 'type'):
599 command_options.type = normalize_type(command_options.type)
600 if not command_options.type:
605 self.logger.debug("Command={}".format(self.command))
608 retcod = self.dispatch(command, command_options, command_args)
612 self.logger.log_exc ("sfi command {} failed".format(command))
617 def read_config(self):
618 config_file = os.path.join(self.options.sfi_dir, "sfi_config")
619 shell_config_file = os.path.join(self.options.sfi_dir, "sfi_config.sh")
621 if Config.is_ini(config_file):
622 config = Config (config_file)
624 # try upgrading from shell config format
625 fp, fn = mkstemp(suffix='sfi_config', text=True)
627 # we need to preload the sections we want parsed
628 # from the shell config
629 config.add_section('sfi')
630 # sface users should be able to use this same file to configure their stuff
631 config.add_section('sface')
632 # manifold users should be able to specify the details
633 # of their backend server here for 'sfi myslice'
634 config.add_section('myslice')
635 config.load(config_file)
637 shutil.move(config_file, shell_config_file)
639 config.save(config_file)
642 self.logger.critical("Failed to read configuration file {}".format(config_file))
643 self.logger.info("Make sure to remove the export clauses and to add quotes")
644 if self.options.verbose == 0:
645 self.logger.info("Re-run with -v for more details")
647 self.logger.log_exc("Could not read config file {}".format(config_file))
650 self.config_instance = config
653 if (self.options.sm is not None):
654 self.sm_url = self.options.sm
655 elif hasattr(config, "SFI_SM"):
656 self.sm_url = config.SFI_SM
658 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in {}".format(config_file))
662 if (self.options.registry is not None):
663 self.reg_url = self.options.registry
664 elif hasattr(config, "SFI_REGISTRY"):
665 self.reg_url = config.SFI_REGISTRY
667 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in {}".format(config_file))
671 if (self.options.user is not None):
672 self.user = self.options.user
673 elif hasattr(config, "SFI_USER"):
674 self.user = config.SFI_USER
676 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in {}".format(config_file))
680 if (self.options.auth is not None):
681 self.authority = self.options.auth
682 elif hasattr(config, "SFI_AUTH"):
683 self.authority = config.SFI_AUTH
685 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in {}".format(config_file))
688 self.config_file = config_file
693 # Get various credential and spec files
695 # Establishes limiting conventions
696 # - conflates MAs and SAs
697 # - assumes last token in slice name is unique
699 # Bootstraps credentials
700 # - bootstrap user credential from self-signed certificate
701 # - bootstrap authority credential from user credential
702 # - bootstrap slice credential from user credential
705 # init self-signed cert, user credentials and gid
706 def bootstrap (self):
707 if self.options.verbose:
708 self.logger.info("Initializing SfaClientBootstrap with {}".format(self.reg_url))
709 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
711 # if -k is provided, use this to initialize private key
712 if self.options.user_private_key:
713 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
715 # trigger legacy compat code if needed
716 # the name has changed from just <leaf>.pkey to <hrn>.pkey
717 if not os.path.isfile(client_bootstrap.private_key_filename()):
718 self.logger.info ("private key not found, trying legacy name")
720 legacy_private_key = os.path.join (self.options.sfi_dir, "{}.pkey"
721 .format(Xrn.unescape(get_leaf(self.user))))
722 self.logger.debug("legacy_private_key={}"
723 .format(legacy_private_key))
724 client_bootstrap.init_private_key_if_missing (legacy_private_key)
725 self.logger.info("Copied private key from legacy location {}"
726 .format(legacy_private_key))
728 self.logger.log_exc("Can't find private key ")
732 client_bootstrap.bootstrap_my_gid()
733 # extract what's needed
734 self.private_key = client_bootstrap.private_key()
735 self.my_credential_string = client_bootstrap.my_credential_string ()
736 self.my_credential = {'geni_type': 'geni_sfa',
738 'geni_value': self.my_credential_string}
739 self.my_gid = client_bootstrap.my_gid ()
740 self.client_bootstrap = client_bootstrap
743 def my_authority_credential_string(self):
744 if not self.authority:
745 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
747 return self.client_bootstrap.authority_credential_string (self.authority)
749 def authority_credential_string(self, auth_hrn):
750 return self.client_bootstrap.authority_credential_string (auth_hrn)
752 def slice_credential_string(self, name):
753 return self.client_bootstrap.slice_credential_string (name)
755 def slice_credential(self, name):
756 return {'geni_type': 'geni_sfa',
758 'geni_value': self.slice_credential_string(name)}
760 # xxx should be supported by sfaclientbootstrap as well
761 def delegate_cred(self, object_cred, hrn, type='authority'):
762 # the gid and hrn of the object we are delegating
763 if isinstance(object_cred, str):
764 object_cred = Credential(string=object_cred)
765 object_gid = object_cred.get_gid_object()
766 object_hrn = object_gid.get_hrn()
768 if not object_cred.get_privileges().get_all_delegate():
769 self.logger.error("Object credential {} does not have delegate bit set"
773 # the delegating user's gid
774 caller_gidfile = self.my_gid()
776 # the gid of the user who will be delegated to
777 delegee_gid = self.client_bootstrap.gid(hrn, type)
778 delegee_hrn = delegee_gid.get_hrn()
779 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
780 return dcred.save_to_string(save_parents=True)
783 # Management of the servers
788 if not hasattr (self, 'registry_proxy'):
789 self.logger.info("Contacting Registry at: {}".format(self.reg_url))
790 self.registry_proxy \
791 = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
792 timeout=self.options.timeout, verbose=self.options.debug)
793 return self.registry_proxy
797 if not hasattr (self, 'sliceapi_proxy'):
798 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
799 if hasattr(self.command_options, 'component') and self.command_options.component:
800 # resolve the hrn at the registry
801 node_hrn = self.command_options.component
802 records = self.registry().Resolve(node_hrn, self.my_credential_string)
803 records = filter_records('node', records)
805 self.logger.warning("No such component:{}".format(opts.component))
807 cm_url = "http://{}:{}/".format(record['hostname'], CM_PORT)
808 self.sliceapi_proxy = SfaServerProxy(cm_url, self.private_key, self.my_gid)
810 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
811 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
812 self.sm_url = 'http://' + self.sm_url
813 self.logger.info("Contacting Slice Manager at: {}".format(self.sm_url))
814 self.sliceapi_proxy \
815 = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
816 timeout=self.options.timeout, verbose=self.options.debug)
817 return self.sliceapi_proxy
819 def get_cached_server_version(self, server):
820 # check local cache first
823 cache_file = os.path.join(self.options.sfi_dir, 'sfi_cache.dat')
824 cache_key = server.url + "-version"
826 cache = Cache(cache_file)
829 self.logger.info("Local cache not found at: {}".format(cache_file))
832 version = cache.get(cache_key)
835 result = server.GetVersion()
836 version = ReturnValue.get_value(result)
837 # cache version for 20 minutes
838 cache.add(cache_key, version, ttl=60*20)
839 self.logger.info("Updating cache file {}".format(cache_file))
840 cache.save_to_file(cache_file)
844 ### resurrect this temporarily so we can support V1 aggregates for a while
845 def server_supports_options_arg(self, server):
847 Returns true if server support the optional call_id arg, false otherwise.
849 server_version = self.get_cached_server_version(server)
851 # xxx need to rewrite this
852 if int(server_version.get('geni_api')) >= 2:
856 def server_supports_call_id_arg(self, server):
857 server_version = self.get_cached_server_version(server)
859 if 'sfa' in server_version and 'code_tag' in server_version:
860 code_tag = server_version['code_tag']
861 code_tag_parts = code_tag.split("-")
862 version_parts = code_tag_parts[0].split(".")
863 major, minor = version_parts[0], version_parts[1]
864 rev = code_tag_parts[1]
865 if int(major) == 1 and minor == 0 and build >= 22:
869 ### ois = options if supported
870 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
871 def ois (self, server, option_dict):
872 if self.server_supports_options_arg (server):
874 elif self.server_supports_call_id_arg (server):
875 return [ unique_call_id () ]
879 ### cis = call_id if supported - like ois
880 def cis (self, server):
881 if self.server_supports_call_id_arg (server):
882 return [ unique_call_id ]
886 ######################################## miscell utilities
887 def get_rspec_file(self, rspec):
888 if (os.path.isabs(rspec)):
891 file = os.path.join(self.options.sfi_dir, rspec)
892 if (os.path.isfile(file)):
895 self.logger.critical("No such rspec file {}".format(rspec))
898 def get_record_file(self, record):
899 if (os.path.isabs(record)):
902 file = os.path.join(self.options.sfi_dir, record)
903 if (os.path.isfile(file)):
906 self.logger.critical("No such registry record file {}".format(record))
910 # helper function to analyze raw output
911 # for main : return 0 if everything is fine, something else otherwise (mostly 1 for now)
912 def success (self, raw):
913 return_value = ReturnValue(raw)
914 output = ReturnValue.get_output(return_value)
915 # means everything is fine
918 # something went wrong
919 print('ERROR:', output)
922 #==========================================================================
923 # Following functions implement the commands
925 # Registry-related commands
926 #==========================================================================
928 @declare_command("", "")
929 def config (self, options, args):
930 "Display contents of current config"
931 print("# From configuration file {}".format(self.config_file))
932 flags = [ ('sfi', [ ('registry', 'reg_url'),
933 ('auth', 'authority'),
939 flags.append ( ('myslice', ['backend', 'delegate', 'platform', 'username'] ) )
941 for (section, tuples) in flags:
942 print("[{}]".format(section))
944 for external_name, internal_name in tuples:
945 print("{:<20} = {}".format(external_name, getattr(self, internal_name)))
947 for external_name, internal_name in tuples:
948 varname = "{}_{}".format(section.upper(), external_name.upper())
949 value = getattr(self.config_instance, varname)
950 print("{:<20} = {}".format(external_name, value))
951 # xxx should analyze result
954 @declare_command("", "")
955 def version(self, options, args):
957 display an SFA server version (GetVersion)
958 or version information about sfi itself
960 if options.version_local:
961 version = version_core()
963 if options.registry_interface:
964 server = self.registry()
966 server = self.sliceapi()
967 result = server.GetVersion()
968 version = ReturnValue.get_value(result)
970 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
972 pprinter = PrettyPrinter(indent=4)
973 pprinter.pprint(version)
974 # xxx should analyze result
977 @declare_command("authority", "")
978 def list(self, options, args):
980 list entries in named authority registry (List)
987 if options.recursive:
988 opts['recursive'] = options.recursive
990 if options.show_credential:
991 show_credentials(self.my_credential_string)
993 list = self.registry().List(hrn, self.my_credential_string, options)
995 raise Exception("Not enough parameters for the 'list' command")
997 # filter on person, slice, site, node, etc.
998 # This really should be in the self.filter_records funct def comment...
999 list = filter_records(options.type, list)
1000 terminal_render (list, options)
1002 save_records_to_file(options.file, list, options.fileformat)
1003 # xxx should analyze result
1006 @declare_command("name", "")
1007 def show(self, options, args):
1009 show details about named registry record (Resolve)
1015 # explicitly require Resolve to run in details mode
1016 resolve_options = {}
1017 if not options.no_details:
1018 resolve_options['details'] = True
1019 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, resolve_options)
1020 record_dicts = filter_records(options.type, record_dicts)
1021 if not record_dicts:
1022 self.logger.error("No record of type {}".format(options.type))
1024 # user has required to focus on some keys
1026 def project (record):
1028 for key in options.keys:
1029 try: projected[key] = record[key]
1032 record_dicts = [ project (record) for record in record_dicts ]
1033 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
1034 for record in records:
1035 if (options.format == "text"): record.dump(sort=True)
1036 else: print(record.save_as_xml())
1038 save_records_to_file(options.file, record_dicts, options.fileformat)
1039 # xxx should analyze result
1042 # this historically was named 'add', it is now 'register' with an alias for legacy
1043 @declare_command("[xml-filename]", "", ['add'])
1044 def register(self, options, args):
1045 """create new record in registry (Register)
1046 from command line options (recommended)
1047 old-school method involving an xml file still supported"""
1049 auth_cred = self.my_authority_credential_string()
1050 if options.show_credential:
1051 show_credentials(auth_cred)
1058 record_filepath = args[0]
1059 rec_file = self.get_record_file(record_filepath)
1060 record_dict.update(load_record_from_file(rec_file).record_to_dict())
1062 print("Cannot load record file {}".format(record_filepath))
1065 record_dict.update(load_record_from_opts(options).record_to_dict())
1066 # we should have a type by now
1067 if 'type' not in record_dict :
1070 # this is still planetlab dependent.. as plc will whine without that
1071 # also, it's only for adding
1072 if record_dict['type'] == 'user':
1073 if not 'first_name' in record_dict:
1074 record_dict['first_name'] = record_dict['hrn']
1075 if 'last_name' not in record_dict:
1076 record_dict['last_name'] = record_dict['hrn']
1077 register = self.registry().Register(record_dict, auth_cred)
1078 # xxx looks like the result here is not ReturnValue-compatible
1079 #return self.success (register)
1080 # xxx should analyze result
1083 @declare_command("[xml-filename]", "")
1084 def update(self, options, args):
1085 """update record into registry (Update)
1086 from command line options (recommended)
1087 old-school method involving an xml file still supported"""
1090 record_filepath = args[0]
1091 rec_file = self.get_record_file(record_filepath)
1092 record_dict.update(load_record_from_file(rec_file).record_to_dict())
1094 record_dict.update(load_record_from_opts(options).record_to_dict())
1095 # at the very least we need 'type' here
1096 if 'type' not in record_dict or record_dict['type'] is None:
1100 # don't translate into an object, as this would possibly distort
1101 # user-provided data; e.g. add an 'email' field to Users
1102 if record_dict['type'] in ['user']:
1103 if record_dict['hrn'] == self.user:
1104 cred = self.my_credential_string
1106 cred = self.my_authority_credential_string()
1107 elif record_dict['type'] in ['slice']:
1109 cred = self.slice_credential_string(record_dict['hrn'])
1110 except ServerException as e:
1111 # XXX smbaker -- once we have better error return codes, update this
1112 # to do something better than a string compare
1113 if "Permission error" in e.args[0]:
1114 cred = self.my_authority_credential_string()
1117 elif record_dict['type'] in ['authority']:
1118 cred = self.my_authority_credential_string()
1119 elif record_dict['type'] in ['node']:
1120 cred = self.my_authority_credential_string()
1122 raise Exception("unknown record type {}".format(record_dict['type']))
1123 if options.show_credential:
1124 show_credentials(cred)
1125 update = self.registry().Update(record_dict, cred)
1126 # xxx looks like the result here is not ReturnValue-compatible
1127 #return self.success(update)
1128 # xxx should analyze result
1131 @declare_command("hrn", "")
1132 def remove(self, options, args):
1133 "remove registry record by name (Remove)"
1134 auth_cred = self.my_authority_credential_string()
1142 if options.show_credential:
1143 show_credentials(auth_cred)
1144 remove = self.registry().Remove(hrn, auth_cred, type)
1145 # xxx looks like the result here is not ReturnValue-compatible
1146 #return self.success (remove)
1147 # xxx should analyze result
1150 # ==================================================================
1151 # Slice-related commands
1152 # ==================================================================
1154 # show rspec for named slice
1155 @declare_command("", "", ['discover'])
1156 def resources(self, options, args):
1158 discover available resources (ListResources)
1160 server = self.sliceapi()
1163 creds = [self.my_credential]
1164 if options.delegate:
1165 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1166 if options.show_credential:
1167 show_credentials(creds)
1169 # no need to check if server accepts the options argument since the options has
1170 # been a required argument since v1 API
1172 # always send call_id to v2 servers
1173 api_options ['call_id'] = unique_call_id()
1174 # ask for cached value if available
1175 api_options ['cached'] = True
1177 api_options['info'] = options.info
1178 if options.list_leases:
1179 api_options['list_leases'] = options.list_leases
1181 if options.current == True:
1182 api_options['cached'] = False
1184 api_options['cached'] = True
1185 if options.rspec_version:
1186 version_manager = VersionManager()
1187 server_version = self.get_cached_server_version(server)
1188 if 'sfa' in server_version:
1189 # just request the version the client wants
1190 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1192 api_options['geni_rspec_version'] = {'type': options.rspec_version}
1194 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1196 list_resources = server.ListResources (creds, api_options)
1197 value = ReturnValue.get_value(list_resources)
1198 if self.options.raw:
1199 save_raw_to_file(list_resources, self.options.raw, self.options.rawformat, self.options.rawbanner)
1200 if options.file is not None:
1201 save_rspec_to_file(value, options.file)
1202 if (self.options.raw is None) and (options.file is None):
1203 display_rspec(value, options.format)
1204 return self.success(list_resources)
1206 @declare_command("slice_hrn", "")
1207 def describe(self, options, args):
1209 shows currently allocated/provisioned resources
1210 of the named slice or set of slivers (Describe)
1212 server = self.sliceapi()
1215 creds = [self.slice_credential(args[0])]
1216 if options.delegate:
1217 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1218 if options.show_credential:
1219 show_credentials(creds)
1221 api_options = {'call_id': unique_call_id(),
1223 'info': options.info,
1224 'list_leases': options.list_leases,
1225 'geni_rspec_version': {'type': 'geni', 'version': '3'},
1228 api_options['info'] = options.info
1230 if options.rspec_version:
1231 version_manager = VersionManager()
1232 server_version = self.get_cached_server_version(server)
1233 if 'sfa' in server_version:
1234 # just request the version the client wants
1235 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1237 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1238 urn = Xrn(args[0], type='slice').get_urn()
1239 remove_none_fields(api_options)
1240 describe = server.Describe([urn], creds, api_options)
1241 value = ReturnValue.get_value(describe)
1242 if self.options.raw:
1243 save_raw_to_file(describe, self.options.raw, self.options.rawformat, self.options.rawbanner)
1244 if options.file is not None:
1245 save_rspec_to_file(value['geni_rspec'], options.file)
1246 if (self.options.raw is None) and (options.file is None):
1247 display_rspec(value['geni_rspec'], options.format)
1248 return self.success (describe)
1250 @declare_command("slice_hrn [<sliver_urn>...]", "")
1251 def delete(self, options, args):
1253 de-allocate and de-provision all or named slivers of the named slice (Delete)
1255 server = self.sliceapi()
1259 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1262 # we have sliver urns
1263 sliver_urns = args[1:]
1265 # we provision all the slivers of the slice
1266 sliver_urns = [slice_urn]
1269 slice_cred = self.slice_credential(slice_hrn)
1270 creds = [slice_cred]
1272 # options and call_id when supported
1274 api_options ['call_id'] = unique_call_id()
1275 if options.show_credential:
1276 show_credentials(creds)
1277 delete = server.Delete(sliver_urns, creds, *self.ois(server, api_options ) )
1278 value = ReturnValue.get_value(delete)
1279 if self.options.raw:
1280 save_raw_to_file(delete, self.options.raw, self.options.rawformat, self.options.rawbanner)
1283 return self.success (delete)
1285 @declare_command("slice_hrn rspec", "")
1286 def allocate(self, options, args):
1288 allocate resources to the named slice (Allocate)
1290 server = self.sliceapi()
1291 server_version = self.get_cached_server_version(server)
1296 rspec_file = self.get_rspec_file(args[1])
1298 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1301 creds = [self.slice_credential(slice_hrn)]
1303 delegated_cred = None
1304 if server_version.get('interface') == 'slicemgr':
1305 # delegate our cred to the slice manager
1306 # do not delegate cred to slicemgr...not working at the moment
1308 #if server_version.get('hrn'):
1309 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1310 #elif server_version.get('urn'):
1311 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1313 if options.show_credential:
1314 show_credentials(creds)
1318 api_options ['call_id'] = unique_call_id()
1322 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1323 remove_none_fields(slice_records[0])
1324 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers'] != []:
1325 slice_record = slice_records[0]
1326 user_hrns = slice_record['reg-researchers']
1327 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1328 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1329 sfa_users = sfa_users_arg(user_records, slice_record)
1330 geni_users = pg_users_arg(user_records)
1332 api_options['sfa_users'] = sfa_users
1333 api_options['geni_users'] = geni_users
1335 with open(rspec_file) as rspec:
1336 rspec_xml = rspec.read()
1337 allocate = server.Allocate(slice_urn, creds, rspec_xml, api_options)
1338 value = ReturnValue.get_value(allocate)
1339 if self.options.raw:
1340 save_raw_to_file(allocate, self.options.raw, self.options.rawformat, self.options.rawbanner)
1341 if options.file is not None:
1342 save_rspec_to_file (value['geni_rspec'], options.file)
1343 if (self.options.raw is None) and (options.file is None):
1345 return self.success(allocate)
1347 @declare_command("slice_hrn [<sliver_urn>...]", "")
1348 def provision(self, options, args):
1350 provision all or named already allocated slivers of the named slice (Provision)
1352 server = self.sliceapi()
1353 server_version = self.get_cached_server_version(server)
1355 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1357 # we have sliver urns
1358 sliver_urns = args[1:]
1360 # we provision all the slivers of the slice
1361 sliver_urns = [slice_urn]
1364 creds = [self.slice_credential(slice_hrn)]
1365 delegated_cred = None
1366 if server_version.get('interface') == 'slicemgr':
1367 # delegate our cred to the slice manager
1368 # do not delegate cred to slicemgr...not working at the moment
1370 #if server_version.get('hrn'):
1371 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1372 #elif server_version.get('urn'):
1373 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1375 if options.show_credential:
1376 show_credentials(creds)
1379 api_options ['call_id'] = unique_call_id()
1381 # set the requtested rspec version
1382 version_manager = VersionManager()
1383 rspec_version = version_manager._get_version('geni', '3').to_dict()
1384 api_options['geni_rspec_version'] = rspec_version
1387 # need to pass along user keys to the aggregate.
1389 # { urn: urn:publicid:IDN+emulab.net+user+alice
1390 # keys: [<ssh key A>, <ssh key B>]
1393 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1394 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers'] != []:
1395 slice_record = slice_records[0]
1396 user_hrns = slice_record['reg-researchers']
1397 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1398 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1399 users = pg_users_arg(user_records)
1401 api_options['geni_users'] = users
1402 provision = server.Provision(sliver_urns, creds, api_options)
1403 value = ReturnValue.get_value(provision)
1404 if self.options.raw:
1405 save_raw_to_file(provision, self.options.raw, self.options.rawformat, self.options.rawbanner)
1406 if options.file is not None:
1407 save_rspec_to_file (value['geni_rspec'], options.file)
1408 if (self.options.raw is None) and (options.file is None):
1410 return self.success(provision)
1412 @declare_command("slice_hrn", "")
1413 def status(self, options, args):
1415 retrieve the status of the slivers belonging to the named slice (Status)
1417 server = self.sliceapi()
1421 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1424 slice_cred = self.slice_credential(slice_hrn)
1425 creds = [slice_cred]
1427 # options and call_id when supported
1429 api_options['call_id'] = unique_call_id()
1430 if options.show_credential:
1431 show_credentials(creds)
1432 status = server.Status([slice_urn], creds, *self.ois(server, api_options))
1433 value = ReturnValue.get_value(status)
1434 if self.options.raw:
1435 save_raw_to_file(status, self.options.raw, self.options.rawformat, self.options.rawbanner)
1438 return self.success (status)
1440 @declare_command("slice_hrn [<sliver_urn>...] action", "")
1441 def action(self, options, args):
1443 Perform the named operational action on all or named slivers of the named slice
1445 server = self.sliceapi()
1449 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1451 # we have sliver urns
1452 sliver_urns = args[1:-1]
1454 # we provision all the slivers of the slice
1455 sliver_urns = [slice_urn]
1458 slice_cred = self.slice_credential(args[0])
1459 creds = [slice_cred]
1460 if options.delegate:
1461 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1462 creds.append(delegated_cred)
1464 perform_action = server.PerformOperationalAction(sliver_urns, creds, action , api_options)
1465 value = ReturnValue.get_value(perform_action)
1466 if self.options.raw:
1467 save_raw_to_file(perform_action, self.options.raw, self.options.rawformat, self.options.rawbanner)
1470 return self.success (perform_action)
1472 @declare_command("slice_hrn [<sliver_urn>...] time",
1473 "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31",
1474 "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z",
1475 "sfi renew onelab.ple.heartbeat +5d",
1476 "sfi renew onelab.ple.heartbeat +3w",
1477 "sfi renew onelab.ple.heartbeat +2m",]))
1478 def renew(self, options, args):
1482 server = self.sliceapi()
1487 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1490 # we have sliver urns
1491 sliver_urns = args[1:-1]
1493 # we provision all the slivers of the slice
1494 sliver_urns = [slice_urn]
1495 input_time = args[-1]
1497 # time: don't try to be smart on the time format, server-side will
1499 slice_cred = self.slice_credential(args[0])
1500 creds = [slice_cred]
1501 # options and call_id when supported
1503 api_options['call_id'] = unique_call_id()
1505 api_options['geni_extend_alap'] = True
1506 if options.show_credential:
1507 show_credentials(creds)
1508 renew = server.Renew(sliver_urns, creds, input_time, *self.ois(server, api_options))
1509 value = ReturnValue.get_value(renew)
1510 if self.options.raw:
1511 save_raw_to_file(renew, self.options.raw, self.options.rawformat, self.options.rawbanner)
1514 return self.success(renew)
1516 @declare_command("slice_hrn", "")
1517 def shutdown(self, options, args):
1519 shutdown named slice (Shutdown)
1521 server = self.sliceapi()
1524 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1526 slice_cred = self.slice_credential(slice_hrn)
1527 creds = [slice_cred]
1528 shutdown = server.Shutdown(slice_urn, creds)
1529 value = ReturnValue.get_value(shutdown)
1530 if self.options.raw:
1531 save_raw_to_file(shutdown, self.options.raw, self.options.rawformat, self.options.rawbanner)
1534 return self.success (shutdown)
1536 @declare_command("[name]", "")
1537 def gid(self, options, args):
1539 Create a GID (CreateGid)
1544 target_hrn = args[0]
1545 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1546 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1548 filename = options.file
1550 filename = os.sep.join([self.options.sfi_dir, '{}.gid'.format(target_hrn)])
1551 self.logger.info("writing {} gid to {}".format(target_hrn, filename))
1552 GID(string=gid).save_to_file(filename)
1553 # xxx should analyze result
1556 ####################
1557 @declare_command("to_hrn", """$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser
1559 will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser
1560 the set of credentials in the scope for this call would be
1561 (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred
1563 (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred
1565 (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred
1566 (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred
1567 because of the two -s options
1570 def delegate (self, options, args):
1572 (locally) create delegate credential for use by given hrn
1573 make sure to check for 'sfi myslice' instead if you plan
1580 # support for several delegations in the same call
1581 # so first we gather the things to do
1583 for slice_hrn in options.delegate_slices:
1584 message = "{}.slice".format(slice_hrn)
1585 original = self.slice_credential_string(slice_hrn)
1586 tuples.append ( (message, original,) )
1587 if options.delegate_pi:
1588 my_authority = self.authority
1589 message = "{}.pi".format(my_authority)
1590 original = self.my_authority_credential_string()
1591 tuples.append ( (message, original,) )
1592 for auth_hrn in options.delegate_auths:
1593 message = "{}.auth".format(auth_hrn)
1594 original = self.authority_credential_string(auth_hrn)
1595 tuples.append ( (message, original, ) )
1596 # if nothing was specified at all at this point, let's assume -u
1598 options.delegate_user = True
1600 if options.delegate_user:
1601 message = "{}.user".format(self.user)
1602 original = self.my_credential_string
1603 tuples.append ( (message, original, ) )
1605 # default type for beneficial is user unless -A
1606 to_type = 'authority' if options.delegate_to_authority else 'user'
1608 # let's now handle all this
1609 # it's all in the filenaming scheme
1610 for (message, original) in tuples:
1611 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1612 delegated_credential = Credential (string=delegated_string)
1613 filename = os.path.join(self.options.sfi_dir,
1614 "{}_for_{}.{}.cred".format(message, to_hrn, to_type))
1615 delegated_credential.save_to_file(filename, save_parents=True)
1616 self.logger.info("delegated credential for {} to {} and wrote to {}"
1617 .format(message, to_hrn, filename))
1619 ####################
1620 @declare_command("", """$ less +/myslice sfi_config
1622 backend = http://manifold.pl.sophia.inria.fr:7080
1623 # the HRN that myslice uses, so that we are delegating to
1624 delegate = ple.upmc.slicebrowser
1625 # platform - this is a myslice concept
1627 # username - as of this writing (May 2013) a simple login name
1631 will first collect the slices that you are part of, then make sure
1632 all your credentials are up-to-date (read: refresh expired ones)
1633 then compute delegated credentials for user 'ple.upmc.slicebrowser'
1634 and upload them all on myslice backend, using 'platform' and 'user'.
1635 A password will be prompted for the upload part.
1637 $ sfi -v myslice -- or sfi -vv myslice
1638 same but with more and more verbosity
1640 $ sfi m -b http://mymanifold.foo.com:7080/
1641 is synonym to sfi myslice as no other command starts with an 'm'
1642 and uses a custom backend for this one call
1645 def myslice (self, options, args):
1647 """ This helper is for refreshing your credentials at myslice; it will
1648 * compute all the slices that you currently have credentials on
1649 * refresh all your credentials (you as a user and pi, your slices)
1650 * upload them to the manifold backend server
1651 for last phase, sfi_config is read to look for the [myslice] section,
1652 and namely the 'backend', 'delegate' and 'user' settings"""
1658 # enable info by default
1659 self.logger.setLevelFromOptVerbose(self.options.verbose+1)
1660 ### the rough sketch goes like this
1661 # (0) produce a p12 file
1662 self.client_bootstrap.my_pkcs12()
1664 # (a) rain check for sufficient config in sfi_config
1666 myslice_keys = [ 'backend', 'delegate', 'platform', 'username']
1667 for key in myslice_keys:
1669 # oct 2013 - I'm finding myself juggling with config files
1670 # so a couple of command-line options can now override config
1671 if hasattr(options, key) and getattr(options, key) is not None:
1672 value = getattr(options, key)
1674 full_key = "MYSLICE_" + key.upper()
1675 value = getattr(self.config_instance, full_key, None)
1677 myslice_dict[key] = value
1679 print("Unsufficient config, missing key {} in [myslice] section of sfi_config"
1681 if len(myslice_dict) != len(myslice_keys):
1684 # (b) figure whether we are PI for the authority where we belong
1685 self.logger.info("Resolving our own id {}".format(self.user))
1686 my_records = self.registry().Resolve(self.user, self.my_credential_string)
1687 if len(my_records) != 1:
1688 print("Cannot Resolve {} -- exiting".format(self.user))
1690 my_record = my_records[0]
1691 my_auths_all = my_record['reg-pi-authorities']
1692 self.logger.info("Found {} authorities that we are PI for".format(len(my_auths_all)))
1693 self.logger.debug("They are {}".format(my_auths_all))
1695 my_auths = my_auths_all
1696 if options.delegate_auths:
1697 my_auths = list(set(my_auths_all).intersection(set(options.delegate_auths)))
1698 self.logger.debug("Restricted to user-provided auths {}".format(my_auths))
1700 # (c) get the set of slices that we are in
1701 my_slices_all = my_record['reg-slices']
1702 self.logger.info("Found {} slices that we are member of".format(len(my_slices_all)))
1703 self.logger.debug("They are: {}".format(my_slices_all))
1705 my_slices = my_slices_all
1706 # if user provided slices, deal only with these - if they are found
1707 if options.delegate_slices:
1708 my_slices = list(set(my_slices_all).intersection(set(options.delegate_slices)))
1709 self.logger.debug("Restricted to user-provided slices: {}".format(my_slices))
1711 # (d) make sure we have *valid* credentials for all these
1712 hrn_credentials = []
1713 hrn_credentials.append ( (self.user, 'user', self.my_credential_string,) )
1714 for auth_hrn in my_auths:
1715 hrn_credentials.append ( (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),) )
1716 for slice_hrn in my_slices:
1717 hrn_credentials.append ( (slice_hrn, 'slice', self.slice_credential_string (slice_hrn),) )
1719 # (e) check for the delegated version of these
1720 # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever
1721 # switch to myslice using an authority instead of a user
1722 delegatee_type = 'user'
1723 delegatee_hrn = myslice_dict['delegate']
1724 hrn_delegated_credentials = []
1725 for (hrn, htype, credential) in hrn_credentials:
1726 delegated_credential = self.client_bootstrap.delegate_credential_string (credential, delegatee_hrn, delegatee_type)
1727 # save these so user can monitor what she's uploaded
1728 filename = os.path.join ( self.options.sfi_dir,
1729 "{}.{}_for_{}.{}.cred"\
1730 .format(hrn, htype, delegatee_hrn, delegatee_type))
1731 with file(filename, 'w') as f:
1732 f.write(delegated_credential)
1733 self.logger.debug("(Over)wrote {}".format(filename))
1734 hrn_delegated_credentials.append ((hrn, htype, delegated_credential, filename, ))
1736 # (f) and finally upload them to manifold server
1737 # xxx todo add an option so the password can be set on the command line
1738 # (but *NOT* in the config file) so other apps can leverage this
1739 self.logger.info("Uploading on backend at {}".format(myslice_dict['backend']))
1740 uploader = ManifoldUploader (logger=self.logger,
1741 url=myslice_dict['backend'],
1742 platform=myslice_dict['platform'],
1743 username=myslice_dict['username'],
1744 password=options.password)
1745 uploader.prompt_all()
1746 (count_all, count_success) = (0, 0)
1747 for (hrn, htype, delegated_credential, filename) in hrn_delegated_credentials:
1749 inspect = Credential(string=delegated_credential)
1750 expire_datetime = inspect.get_expiration()
1751 message = "{} ({}) [exp:{}]".format(hrn, htype, expire_datetime)
1752 if uploader.upload(delegated_credential, message=message):
1755 self.logger.info("Successfully uploaded {}/{} credentials"
1756 .format(count_success, count_all))
1758 # at first I thought we would want to save these,
1759 # like 'sfi delegate does' but on second thought
1760 # it is probably not helpful as people would not
1761 # need to run 'sfi delegate' at all anymore
1762 if count_success != count_all:
1764 # xxx should analyze result
1767 @declare_command("cred", "")
1768 def trusted(self, options, args):
1770 return the trusted certs at this interface (get_trusted_certs)
1772 if options.registry_interface:
1773 server = self.registry()
1775 server = self.sliceapi()
1776 cred = self.my_authority_credential_string()
1777 trusted_certs = server.get_trusted_certs(cred)
1778 if not options.registry_interface:
1779 trusted_certs = ReturnValue.get_value(trusted_certs)
1781 for trusted_cert in trusted_certs:
1782 print("\n===========================================================\n")
1783 gid = GID(string=trusted_cert)
1785 cert = Certificate(string=trusted_cert)
1786 self.logger.debug('Sfi.trusted -> {}'.format(cert.get_subject()))
1787 print("Certificate:\n{}\n\n".format(trusted_cert))
1788 # xxx should analyze result