2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
17 from lxml import etree
18 from StringIO import StringIO
19 from optparse import OptionParser
20 from pprint import PrettyPrinter
21 from tempfile import mkstemp
23 from sfa.trust.certificate import Keypair, Certificate
24 from sfa.trust.gid import GID
25 from sfa.trust.credential import Credential
26 from sfa.trust.sfaticket import SfaTicket
28 from sfa.util.faults import SfaInvalidArgument
29 from sfa.util.sfalogging import sfi_logger
30 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
31 from sfa.util.config import Config
32 from sfa.util.version import version_core
33 from sfa.util.cache import Cache
35 from sfa.storage.record import Record
37 from sfa.rspecs.rspec import RSpec
38 from sfa.rspecs.rspec_converter import RSpecConverter
39 from sfa.rspecs.version_manager import VersionManager
41 from sfa.client.sfaclientlib import SfaClientBootstrap
42 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
43 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
44 from sfa.client.return_value import ReturnValue
45 from sfa.client.candidates import Candidates
49 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
50 terminal_render, filter_records
53 def display_rspec(rspec, format='rspec'):
55 tree = etree.parse(StringIO(rspec))
57 result = root.xpath("./network/site/node/hostname/text()")
58 elif format in ['ip']:
59 # The IP address is not yet part of the new RSpec
60 # so this doesn't do anything yet.
61 tree = etree.parse(StringIO(rspec))
63 result = root.xpath("./network/site/node/ipv4/text()")
70 def display_list(results):
71 for result in results:
74 def display_records(recordList, dump=False):
75 ''' Print all fields in the record'''
76 for record in recordList:
77 display_record(record, dump)
79 def display_record(record, dump=False):
81 record.dump(sort=True)
83 info = record.getdict()
84 print "%s (%s)" % (info['hrn'], info['type'])
88 def filter_records(type, records):
90 for record in records:
91 if (record['type'] == type) or (type == "all"):
92 filtered_records.append(record)
93 return filtered_records
96 def credential_printable (cred):
97 credential=Credential(cred=cred)
99 result += credential.get_summary_tostring()
101 rights = credential.get_privileges()
102 result += "type=%s\n" % credential.type
103 result += "version=%s\n" % credential.version
104 result += "rights=%s\n"%rights
107 def show_credentials (cred_s):
108 if not isinstance (cred_s,list): cred_s = [cred_s]
110 print "Using Credential %s"%credential_printable(cred)
113 def save_raw_to_file(var, filename, format="text", banner=None):
115 # if filename is "-", send it to stdout
118 f = open(filename, "w")
123 elif format == "pickled":
124 f.write(pickle.dumps(var))
125 elif format == "json":
126 if hasattr(json, "dumps"):
127 f.write(json.dumps(var)) # python 2.6
129 f.write(json.write(var)) # python 2.5
131 # this should never happen
132 print "unknown output format", format
134 f.write('\n'+banner+"\n")
136 def save_rspec_to_file(rspec, filename):
137 if not filename.endswith(".rspec"):
138 filename = filename + ".rspec"
139 f = open(filename, 'w')
144 def save_records_to_file(filename, record_dicts, format="xml"):
147 for record_dict in record_dicts:
149 save_record_to_file(filename + "." + str(index), record_dict)
151 save_record_to_file(filename, record_dict)
153 elif format == "xmllist":
154 f = open(filename, "w")
155 f.write("<recordlist>\n")
156 for record_dict in record_dicts:
157 record_obj=Record(dict=record_dict)
158 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
159 f.write("</recordlist>\n")
161 elif format == "hrnlist":
162 f = open(filename, "w")
163 for record_dict in record_dicts:
164 record_obj=Record(dict=record_dict)
165 f.write(record_obj.hrn + "\n")
168 # this should never happen
169 print "unknown output format", format
171 def save_record_to_file(filename, record_dict):
172 record = Record(dict=record_dict)
173 xml = record.save_as_xml()
174 f=codecs.open(filename, encoding='utf-8',mode="w")
179 # minimally check a key argument
180 def check_ssh_key (key):
181 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
182 return re.match(good_ssh_key, key, re.IGNORECASE)
185 def load_record_from_opts(options):
187 if hasattr(options, 'xrn') and options.xrn:
188 if hasattr(options, 'type') and options.type:
189 xrn = Xrn(options.xrn, options.type)
191 xrn = Xrn(options.xrn)
192 record_dict['urn'] = xrn.get_urn()
193 record_dict['hrn'] = xrn.get_hrn()
194 record_dict['type'] = xrn.get_type()
195 if hasattr(options, 'key') and options.key:
197 pubkey = open(options.key, 'r').read()
200 if not check_ssh_key (pubkey):
201 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
202 record_dict['keys'] = [pubkey]
203 if hasattr(options, 'slices') and options.slices:
204 record_dict['slices'] = options.slices
205 if hasattr(options, 'researchers') and options.researchers:
206 record_dict['researcher'] = options.researchers
207 if hasattr(options, 'email') and options.email:
208 record_dict['email'] = options.email
209 if hasattr(options, 'pis') and options.pis:
210 record_dict['pi'] = options.pis
212 # handle extra settings
213 record_dict.update(options.extras)
215 return Record(dict=record_dict)
217 def load_record_from_file(filename):
218 f=codecs.open(filename, encoding="utf-8", mode="r")
219 xml_string = f.read()
221 return Record(xml=xml_string)
225 def unique_call_id(): return uuid.uuid4().urn
229 # dirty hack to make this class usable from the outside
230 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
233 def default_sfi_dir ():
234 if os.path.isfile("./sfi_config"):
237 return os.path.expanduser("~/.sfi/")
239 # dummy to meet Sfi's expectations for its 'options' field
240 # i.e. s/t we can do setattr on
244 def __init__ (self,options=None):
245 if options is None: options=Sfi.DummyOptions()
246 for opt in Sfi.required_options:
247 if not hasattr(options,opt): setattr(options,opt,None)
248 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
249 self.options = options
251 self.authority = None
252 self.logger = sfi_logger
253 self.logger.enable_console()
254 self.available_names = [ tuple[0] for tuple in Sfi.available ]
255 self.available_dict = dict (Sfi.available)
257 # tuples command-name expected-args in the order in which they should appear in the help
260 ("list", "authority"),
263 ("update", "[record]"),
266 ("describe", "slice_hrn"),
267 ("allocate", "slice_hrn rspec"),
268 ("provision", "slice_hrn"),
269 ("action", "slice_hrn action"),
270 ("delete", "slice_hrn"),
271 ("status", "slice_hrn"),
272 ("renew", "slice_hrn time"),
273 ("shutdown", "slice_hrn"),
274 ("delegate", "to_hrn"),
282 $ less +/myslice myslice sfi_config
284 backend = 'http://manifold.pl.sophia.inria.fr:7080'
285 delegate = 'ple.upmc.slicebrowser'
289 Will make sure all your credentials are up-to-date (that is: refresh expired ones)
290 then compute delegated credentials for user 'ple.upmc.slicebrowser'
291 and upload them all on myslice backend, using manifold id as specified in 'user'
295 def print_command_help (self, options):
296 verbose=getattr(options,'verbose')
297 format3="%18s %-15s %s"
300 print format3%("command","cmd_args","description")
304 self.create_parser().print_help()
305 for command in self.available_names:
306 args=self.available_dict[command]
307 method=getattr(self,command,None)
309 if method: doc=getattr(method,'__doc__',"")
310 if not doc: doc="*** no doc found ***"
311 doc=doc.strip(" \t\n")
312 doc=doc.replace("\n","\n"+35*' ')
315 print format3%(command,args,doc)
317 self.create_command_parser(command).print_help()
319 def create_command_parser(self, command):
320 if command not in self.available_dict:
321 msg="Invalid command\n"
323 msg += ','.join(self.available_names)
324 self.logger.critical(msg)
327 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
328 % (command, self.available_dict[command]))
330 if command in ("add", "update"):
331 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
332 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
333 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
334 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
336 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
337 default='', type="str", action='callback', callback=optparse_listvalue_callback)
338 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
339 help='Set/replace slice researchers', default='', type="str", action='callback',
340 callback=optparse_listvalue_callback)
341 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
342 default='', type="str", action='callback', callback=optparse_listvalue_callback)
343 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
344 action="callback", callback=optparse_dictvalue_callback, nargs=1,
345 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
347 # user specifies remote aggregate/sm/component
348 if command in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
349 "action", "shutdown", "renew", "status"):
350 parser.add_option("-d", "--delegate", dest="delegate", default=None,
352 help="Include a credential delegated to the user's root"+\
353 "authority in set of credentials for this call")
355 # show_credential option
356 if command in ("list","resources", "describe", "provision", "allocate", "add","update","remove","slices","delete","status","renew"):
357 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
358 help="show credential(s) used in human-readable form")
359 # registy filter option
360 if command in ("list", "show", "remove"):
361 parser.add_option("-t", "--type", dest="type", type="choice",
362 help="type filter ([all]|user|slice|authority|node|aggregate)",
363 choices=("all", "user", "slice", "authority", "node", "aggregate"),
365 if command in ("show"):
366 parser.add_option("-k","--key",dest="keys",action="append",default=[],
367 help="specify specific keys to be displayed from record")
368 if command in ("resources", "describe"):
370 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
371 help="schema type and version of resulting RSpec")
372 # disable/enable cached rspecs
373 parser.add_option("-c", "--current", dest="current", default=False,
375 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
377 parser.add_option("-f", "--format", dest="format", type="choice",
378 help="display format ([xml]|dns|ip)", default="xml",
379 choices=("xml", "dns", "ip"))
380 #panos: a new option to define the type of information about resources a user is interested in
381 parser.add_option("-i", "--info", dest="info",
382 help="optional component information", default=None)
383 # a new option to retreive or not reservation-oriented RSpecs (leases)
384 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
385 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
386 choices=("all", "resources", "leases"), default="resources")
389 if command in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
390 parser.add_option("-o", "--output", dest="file",
391 help="output XML to file", metavar="FILE", default=None)
393 if command in ("show", "list"):
394 parser.add_option("-f", "--format", dest="format", type="choice",
395 help="display format ([text]|xml)", default="text",
396 choices=("text", "xml"))
398 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
399 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
400 choices=("xml", "xmllist", "hrnlist"))
401 if command == 'list':
402 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
403 help="list all child records", default=False)
404 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
405 help="gives details, like user keys", default=False)
406 if command in ("delegate"):
407 parser.add_option("-u", "--user",
408 action="store_true", dest="delegate_user", default=False,
409 help="delegate your own credentials; default if no other option is provided")
410 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
411 metavar="slice_hrn", help="delegate cred. for slice HRN")
412 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
413 metavar='auth_hrn', help="delegate cred for auth HRN")
414 # this primarily is a shorthand for -a my_hrn^
415 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
416 help="delegate your PI credentials, so s.t. like -a your_hrn^")
417 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
418 help="""by default the mandatory argument is expected to be a user,
419 use this if you mean an authority instead""")
421 if command in ("version"):
422 parser.add_option("-R","--registry-version",
423 action="store_true", dest="version_registry", default=False,
424 help="probe registry version instead of sliceapi")
425 parser.add_option("-l","--local",
426 action="store_true", dest="version_local", default=False,
427 help="display version of the local client")
432 def create_parser(self):
434 # Generate command line parser
435 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
436 description="Commands: %s"%(" ".join(self.available_names)))
437 parser.add_option("-r", "--registry", dest="registry",
438 help="root registry", metavar="URL", default=None)
439 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
440 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
441 parser.add_option("-R", "--raw", dest="raw", default=None,
442 help="Save raw, unparsed server response to a file")
443 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
444 help="raw file format ([text]|pickled|json)", default="text",
445 choices=("text","pickled","json"))
446 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
447 help="text string to write before and after raw output")
448 parser.add_option("-d", "--dir", dest="sfi_dir",
449 help="config & working directory - default is %default",
450 metavar="PATH", default=Sfi.default_sfi_dir())
451 parser.add_option("-u", "--user", dest="user",
452 help="user name", metavar="HRN", default=None)
453 parser.add_option("-a", "--auth", dest="auth",
454 help="authority name", metavar="HRN", default=None)
455 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
456 help="verbose mode - cumulative")
457 parser.add_option("-D", "--debug",
458 action="store_true", dest="debug", default=False,
459 help="Debug (xml-rpc) protocol messages")
460 # would it make sense to use ~/.ssh/id_rsa as a default here ?
461 parser.add_option("-k", "--private-key",
462 action="store", dest="user_private_key", default=None,
463 help="point to the private key file to use if not yet installed in sfi_dir")
464 parser.add_option("-t", "--timeout", dest="timeout", default=None,
465 help="Amout of time to wait before timing out the request")
466 parser.add_option("-?", "--commands",
467 action="store_true", dest="command_help", default=False,
468 help="one page summary on commands & exit")
469 parser.disable_interspersed_args()
474 def print_help (self):
475 print "==================== Generic sfi usage"
476 self.sfi_parser.print_help()
477 print "==================== Specific command usage"
478 self.command_parser.print_help()
479 if self.command in Sfi.examples:
480 print "==================== Example"
481 print Sfi.examples[self.command]
484 # Main: parse arguments and dispatch to command
486 def dispatch(self, command, command_options, command_args):
487 method=getattr(self, command, None)
489 print "Unknown command %s"%command
491 return method(command_options, command_args)
494 self.sfi_parser = self.create_parser()
495 (options, args) = self.sfi_parser.parse_args()
496 if options.command_help:
497 self.print_command_help(options)
499 self.options = options
501 self.logger.setLevelFromOptVerbose(self.options.verbose)
504 self.logger.critical("No command given. Use -h for help.")
505 self.print_command_help(options)
508 # complete / find unique match with command set
509 command_candidates = Candidates (self.available_names)
511 command = command_candidates.only_match(input)
513 self.print_command_help(options)
515 # second pass options parsing
517 self.command_parser = self.create_command_parser(command)
518 (command_options, command_args) = self.command_parser.parse_args(args[1:])
519 self.command_options = command_options
523 self.logger.debug("Command=%s" % self.command)
526 self.dispatch(command, command_options, command_args)
530 self.logger.log_exc ("sfi command %s failed"%command)
536 def read_config(self):
537 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
538 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
540 if Config.is_ini(config_file):
541 config = Config (config_file)
543 # try upgrading from shell config format
544 fp, fn = mkstemp(suffix='sfi_config', text=True)
546 # we need to preload the sections we want parsed
547 # from the shell config
548 config.add_section('sfi')
549 # sface users should be able to use this same file to configure their stuff
550 config.add_section('sface')
551 # manifold users should be able to specify their backend server here for sfi delegate
552 config.add_section('myslice')
553 config.load(config_file)
555 shutil.move(config_file, shell_config_file)
557 config.save(config_file)
560 self.logger.critical("Failed to read configuration file %s"%config_file)
561 self.logger.info("Make sure to remove the export clauses and to add quotes")
562 if self.options.verbose==0:
563 self.logger.info("Re-run with -v for more details")
565 self.logger.log_exc("Could not read config file %s"%config_file)
570 if (self.options.sm is not None):
571 self.sm_url = self.options.sm
572 elif hasattr(config, "SFI_SM"):
573 self.sm_url = config.SFI_SM
575 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
579 if (self.options.registry is not None):
580 self.reg_url = self.options.registry
581 elif hasattr(config, "SFI_REGISTRY"):
582 self.reg_url = config.SFI_REGISTRY
584 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
588 if (self.options.user is not None):
589 self.user = self.options.user
590 elif hasattr(config, "SFI_USER"):
591 self.user = config.SFI_USER
593 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
597 if (self.options.auth is not None):
598 self.authority = self.options.auth
599 elif hasattr(config, "SFI_AUTH"):
600 self.authority = config.SFI_AUTH
602 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
605 self.config_file=config_file
609 def show_config (self):
610 print "From configuration file %s"%self.config_file
613 ('SFI_AUTH','authority'),
615 ('SFI_REGISTRY','reg_url'),
617 for (external_name, internal_name) in flags:
618 print "%s='%s'"%(external_name,getattr(self,internal_name))
621 # Get various credential and spec files
623 # Establishes limiting conventions
624 # - conflates MAs and SAs
625 # - assumes last token in slice name is unique
627 # Bootstraps credentials
628 # - bootstrap user credential from self-signed certificate
629 # - bootstrap authority credential from user credential
630 # - bootstrap slice credential from user credential
633 # init self-signed cert, user credentials and gid
634 def bootstrap (self):
635 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
637 # if -k is provided, use this to initialize private key
638 if self.options.user_private_key:
639 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
641 # trigger legacy compat code if needed
642 # the name has changed from just <leaf>.pkey to <hrn>.pkey
643 if not os.path.isfile(client_bootstrap.private_key_filename()):
644 self.logger.info ("private key not found, trying legacy name")
646 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
647 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
648 client_bootstrap.init_private_key_if_missing (legacy_private_key)
649 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
651 self.logger.log_exc("Can't find private key ")
655 client_bootstrap.bootstrap_my_gid()
656 # extract what's needed
657 self.private_key = client_bootstrap.private_key()
658 self.my_credential_string = client_bootstrap.my_credential_string ()
659 self.my_credential = {'geni_type': 'geni_sfa',
660 'geni_version': '3.0',
661 'geni_value': self.my_credential_string}
662 self.my_gid = client_bootstrap.my_gid ()
663 self.client_bootstrap = client_bootstrap
666 def my_authority_credential_string(self):
667 if not self.authority:
668 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
670 return self.client_bootstrap.authority_credential_string (self.authority)
672 def authority_credential_string(self, auth_hrn):
673 return self.client_bootstrap.authority_credential_string (auth_hrn)
675 def slice_credential_string(self, name):
676 return self.client_bootstrap.slice_credential_string (name)
678 def slice_credential(self, name):
679 return {'geni_type': 'geni_sfa',
680 'geni_version': '3.0',
681 'geni_value': self.slice_credential_string(name)}
683 # xxx should be supported by sfaclientbootstrap as well
684 def delegate_cred(self, object_cred, hrn, type='authority'):
685 # the gid and hrn of the object we are delegating
686 if isinstance(object_cred, str):
687 object_cred = Credential(string=object_cred)
688 object_gid = object_cred.get_gid_object()
689 object_hrn = object_gid.get_hrn()
691 if not object_cred.get_privileges().get_all_delegate():
692 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
695 # the delegating user's gid
696 caller_gidfile = self.my_gid()
698 # the gid of the user who will be delegated to
699 delegee_gid = self.client_bootstrap.gid(hrn,type)
700 delegee_hrn = delegee_gid.get_hrn()
701 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
702 return dcred.save_to_string(save_parents=True)
705 # Management of the servers
710 if not hasattr (self, 'registry_proxy'):
711 self.logger.info("Contacting Registry at: %s"%self.reg_url)
712 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
713 timeout=self.options.timeout, verbose=self.options.debug)
714 return self.registry_proxy
718 if not hasattr (self, 'sliceapi_proxy'):
719 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
720 if hasattr(self.command_options,'component') and self.command_options.component:
721 # resolve the hrn at the registry
722 node_hrn = self.command_options.component
723 records = self.registry().Resolve(node_hrn, self.my_credential_string)
724 records = filter_records('node', records)
726 self.logger.warning("No such component:%r"% opts.component)
728 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
729 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
731 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
732 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
733 self.sm_url = 'http://' + self.sm_url
734 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
735 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
736 timeout=self.options.timeout, verbose=self.options.debug)
737 return self.sliceapi_proxy
739 def get_cached_server_version(self, server):
740 # check local cache first
743 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
744 cache_key = server.url + "-version"
746 cache = Cache(cache_file)
749 self.logger.info("Local cache not found at: %s" % cache_file)
752 version = cache.get(cache_key)
755 result = server.GetVersion()
756 version= ReturnValue.get_value(result)
757 # cache version for 20 minutes
758 cache.add(cache_key, version, ttl= 60*20)
759 self.logger.info("Updating cache file %s" % cache_file)
760 cache.save_to_file(cache_file)
764 ### resurrect this temporarily so we can support V1 aggregates for a while
765 def server_supports_options_arg(self, server):
767 Returns true if server support the optional call_id arg, false otherwise.
769 server_version = self.get_cached_server_version(server)
771 # xxx need to rewrite this
772 if int(server_version.get('geni_api')) >= 2:
776 def server_supports_call_id_arg(self, server):
777 server_version = self.get_cached_server_version(server)
779 if 'sfa' in server_version and 'code_tag' in server_version:
780 code_tag = server_version['code_tag']
781 code_tag_parts = code_tag.split("-")
782 version_parts = code_tag_parts[0].split(".")
783 major, minor = version_parts[0], version_parts[1]
784 rev = code_tag_parts[1]
785 if int(major) == 1 and minor == 0 and build >= 22:
789 ### ois = options if supported
790 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
791 def ois (self, server, option_dict):
792 if self.server_supports_options_arg (server):
794 elif self.server_supports_call_id_arg (server):
795 return [ unique_call_id () ]
799 ### cis = call_id if supported - like ois
800 def cis (self, server):
801 if self.server_supports_call_id_arg (server):
802 return [ unique_call_id ]
806 ######################################## miscell utilities
807 def get_rspec_file(self, rspec):
808 if (os.path.isabs(rspec)):
811 file = os.path.join(self.options.sfi_dir, rspec)
812 if (os.path.isfile(file)):
815 self.logger.critical("No such rspec file %s"%rspec)
818 def get_record_file(self, record):
819 if (os.path.isabs(record)):
822 file = os.path.join(self.options.sfi_dir, record)
823 if (os.path.isfile(file)):
826 self.logger.critical("No such registry record file %s"%record)
830 #==========================================================================
831 # Following functions implement the commands
833 # Registry-related commands
834 #==========================================================================
836 def version(self, options, args):
838 display an SFA server version (GetVersion)
839 or version information about sfi itself
841 if options.version_local:
842 version=version_core()
844 if options.version_registry:
845 server=self.registry()
847 server = self.sliceapi()
848 result = server.GetVersion()
849 version = ReturnValue.get_value(result)
851 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
853 pprinter = PrettyPrinter(indent=4)
854 pprinter.pprint(version)
856 def list(self, options, args):
858 list entries in named authority registry (List)
865 if options.recursive:
866 opts['recursive'] = options.recursive
868 if options.show_credential:
869 show_credentials(self.my_credential_string)
871 list = self.registry().List(hrn, self.my_credential_string, options)
873 raise Exception, "Not enough parameters for the 'list' command"
875 # filter on person, slice, site, node, etc.
876 # This really should be in the self.filter_records funct def comment...
877 list = filter_records(options.type, list)
878 terminal_render (list, options)
880 save_records_to_file(options.file, list, options.fileformat)
883 def show(self, options, args):
885 show details about named registry record (Resolve)
891 # explicitly require Resolve to run in details mode
892 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
893 record_dicts = filter_records(options.type, record_dicts)
895 self.logger.error("No record of type %s"% options.type)
897 # user has required to focus on some keys
899 def project (record):
901 for key in options.keys:
902 try: projected[key]=record[key]
905 record_dicts = [ project (record) for record in record_dicts ]
906 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
907 for record in records:
908 if (options.format == "text"): record.dump(sort=True)
909 else: print record.save_as_xml()
911 save_records_to_file(options.file, record_dicts, options.fileformat)
914 def add(self, options, args):
915 "add record into registry by using the command options (Recommended) or from xml file (Register)"
916 auth_cred = self.my_authority_credential_string()
917 if options.show_credential:
918 show_credentials(auth_cred)
925 record_filepath = args[0]
926 rec_file = self.get_record_file(record_filepath)
927 record_dict.update(load_record_from_file(rec_file).todict())
929 print "Cannot load record file %s"%record_filepath
932 record_dict.update(load_record_from_opts(options).todict())
933 # we should have a type by now
934 if 'type' not in record_dict :
937 # this is still planetlab dependent.. as plc will whine without that
938 # also, it's only for adding
939 if record_dict['type'] == 'user':
940 if not 'first_name' in record_dict:
941 record_dict['first_name'] = record_dict['hrn']
942 if 'last_name' not in record_dict:
943 record_dict['last_name'] = record_dict['hrn']
944 return self.registry().Register(record_dict, auth_cred)
946 def update(self, options, args):
947 "update record into registry by using the command options (Recommended) or from xml file (Update)"
950 record_filepath = args[0]
951 rec_file = self.get_record_file(record_filepath)
952 record_dict.update(load_record_from_file(rec_file).todict())
954 record_dict.update(load_record_from_opts(options).todict())
955 # at the very least we need 'type' here
956 if 'type' not in record_dict:
960 # don't translate into an object, as this would possibly distort
961 # user-provided data; e.g. add an 'email' field to Users
962 if record_dict['type'] == "user":
963 if record_dict['hrn'] == self.user:
964 cred = self.my_credential_string
966 cred = self.my_authority_credential_string()
967 elif record_dict['type'] in ["slice"]:
969 cred = self.slice_credential_string(record_dict['hrn'])
970 except ServerException, e:
971 # XXX smbaker -- once we have better error return codes, update this
972 # to do something better than a string compare
973 if "Permission error" in e.args[0]:
974 cred = self.my_authority_credential_string()
977 elif record_dict['type'] in ["authority"]:
978 cred = self.my_authority_credential_string()
979 elif record_dict['type'] == 'node':
980 cred = self.my_authority_credential_string()
982 raise "unknown record type" + record_dict['type']
983 if options.show_credential:
984 show_credentials(cred)
985 return self.registry().Update(record_dict, cred)
987 def remove(self, options, args):
988 "remove registry record by name (Remove)"
989 auth_cred = self.my_authority_credential_string()
997 if options.show_credential:
998 show_credentials(auth_cred)
999 return self.registry().Remove(hrn, auth_cred, type)
1001 # ==================================================================
1002 # Slice-related commands
1003 # ==================================================================
1005 def slices(self, options, args):
1006 "list instantiated slices (ListSlices) - returns urn's"
1007 server = self.sliceapi()
1009 creds = [self.my_credential_string]
1010 # options and call_id when supported
1012 api_options['call_id']=unique_call_id()
1013 if options.show_credential:
1014 show_credentials(creds)
1015 result = server.ListSlices(creds, *self.ois(server,api_options))
1016 value = ReturnValue.get_value(result)
1017 if self.options.raw:
1018 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1023 # show rspec for named slice
1024 def resources(self, options, args):
1026 discover available resources (ListResources)
1028 server = self.sliceapi()
1031 creds = [self.my_credential]
1032 if options.delegate:
1033 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1034 if options.show_credential:
1035 show_credentials(creds)
1037 # no need to check if server accepts the options argument since the options has
1038 # been a required argument since v1 API
1040 # always send call_id to v2 servers
1041 api_options ['call_id'] = unique_call_id()
1042 # ask for cached value if available
1043 api_options ['cached'] = True
1045 api_options['info'] = options.info
1046 if options.list_leases:
1047 api_options['list_leases'] = options.list_leases
1049 if options.current == True:
1050 api_options['cached'] = False
1052 api_options['cached'] = True
1053 if options.rspec_version:
1054 version_manager = VersionManager()
1055 server_version = self.get_cached_server_version(server)
1056 if 'sfa' in server_version:
1057 # just request the version the client wants
1058 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1060 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1062 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1063 result = server.ListResources (creds, api_options)
1064 value = ReturnValue.get_value(result)
1065 if self.options.raw:
1066 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1067 if options.file is not None:
1068 save_rspec_to_file(value, options.file)
1069 if (self.options.raw is None) and (options.file is None):
1070 display_rspec(value, options.format)
1074 def describe(self, options, args):
1076 shows currently allocated/provisioned resources of the named slice or set of slivers (Describe)
1078 server = self.sliceapi()
1081 creds = [self.slice_credential(args[0])]
1082 if options.delegate:
1083 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1084 if options.show_credential:
1085 show_credentials(creds)
1087 api_options = {'call_id': unique_call_id(),
1089 'info': options.info,
1090 'list_leases': options.list_leases,
1091 'geni_rspec_version': {'type': 'geni', 'version': '3.0'},
1093 if options.rspec_version:
1094 version_manager = VersionManager()
1095 server_version = self.get_cached_server_version(server)
1096 if 'sfa' in server_version:
1097 # just request the version the client wants
1098 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1100 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1101 urn = Xrn(args[0], type='slice').get_urn()
1102 result = server.Describe([urn], creds, api_options)
1103 value = ReturnValue.get_value(result)
1104 if self.options.raw:
1105 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1106 if options.file is not None:
1107 save_rspec_to_file(value, options.file)
1108 if (self.options.raw is None) and (options.file is None):
1109 display_rspec(value, options.format)
1113 def delete(self, options, args):
1115 de-allocate and de-provision all or named slivers of the slice (Delete)
1117 server = self.sliceapi()
1121 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1124 slice_cred = self.slice_credential(slice_hrn)
1125 creds = [slice_cred]
1127 # options and call_id when supported
1129 api_options ['call_id'] = unique_call_id()
1130 if options.show_credential:
1131 show_credentials(creds)
1132 result = server.Delete([slice_urn], creds, *self.ois(server, api_options ) )
1133 value = ReturnValue.get_value(result)
1134 if self.options.raw:
1135 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1140 def allocate(self, options, args):
1142 allocate resources to the named slice (Allocate)
1144 server = self.sliceapi()
1145 server_version = self.get_cached_server_version(server)
1147 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1150 creds = [self.slice_credential(slice_hrn)]
1152 delegated_cred = None
1153 if server_version.get('interface') == 'slicemgr':
1154 # delegate our cred to the slice manager
1155 # do not delegate cred to slicemgr...not working at the moment
1157 #if server_version.get('hrn'):
1158 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1159 #elif server_version.get('urn'):
1160 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1162 if options.show_credential:
1163 show_credentials(creds)
1166 rspec_file = self.get_rspec_file(args[1])
1167 rspec = open(rspec_file).read()
1169 api_options ['call_id'] = unique_call_id()
1170 result = server.Allocate(slice_urn, creds, rspec, api_options)
1171 value = ReturnValue.get_value(result)
1172 if self.options.raw:
1173 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1174 if options.file is not None:
1175 save_rspec_to_file (value, options.file)
1176 if (self.options.raw is None) and (options.file is None):
1182 def provision(self, options, args):
1184 provision already allocated resources of named slice (Provision)
1186 server = self.sliceapi()
1187 server_version = self.get_cached_server_version(server)
1189 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1192 creds = [self.slice_credential(slice_hrn)]
1193 delegated_cred = None
1194 if server_version.get('interface') == 'slicemgr':
1195 # delegate our cred to the slice manager
1196 # do not delegate cred to slicemgr...not working at the moment
1198 #if server_version.get('hrn'):
1199 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1200 #elif server_version.get('urn'):
1201 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1203 if options.show_credential:
1204 show_credentials(creds)
1207 api_options ['call_id'] = unique_call_id()
1209 # set the requtested rspec version
1210 version_manager = VersionManager()
1211 rspec_version = version_manager._get_version('geni', '3.0').to_dict()
1212 api_options['geni_rspec_version'] = rspec_version
1215 # need to pass along user keys to the aggregate.
1217 # { urn: urn:publicid:IDN+emulab.net+user+alice
1218 # keys: [<ssh key A>, <ssh key B>]
1221 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1222 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1223 slice_record = slice_records[0]
1224 user_hrns = slice_record['researcher']
1225 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1226 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1227 users = pg_users_arg(user_records)
1229 api_options['geni_users'] = users
1230 result = server.Provision([slice_urn], creds, api_options)
1231 value = ReturnValue.get_value(result)
1232 if self.options.raw:
1233 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1234 if options.file is not None:
1235 save_rspec_to_file (value, options.file)
1236 if (self.options.raw is None) and (options.file is None):
1240 def status(self, options, args):
1242 retrieve the status of the slivers belonging to tne named slice (Status)
1244 server = self.sliceapi()
1248 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1251 slice_cred = self.slice_credential(slice_hrn)
1252 creds = [slice_cred]
1254 # options and call_id when supported
1256 api_options['call_id']=unique_call_id()
1257 if options.show_credential:
1258 show_credentials(creds)
1259 result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1260 value = ReturnValue.get_value(result)
1261 if self.options.raw:
1262 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1267 def action(self, options, args):
1269 Perform the named operational action on the named slivers
1271 server = self.sliceapi()
1276 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1278 slice_cred = self.slice_credential(args[0])
1279 creds = [slice_cred]
1280 if options.delegate:
1281 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1282 creds.append(delegated_cred)
1284 result = server.PerformOperationalAction([slice_urn], creds, action , api_options)
1285 value = ReturnValue.get_value(result)
1286 if self.options.raw:
1287 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1292 def renew(self, options, args):
1294 renew slice (RenewSliver)
1296 server = self.sliceapi()
1300 [ slice_hrn, input_time ] = args
1302 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1303 # time: don't try to be smart on the time format, server-side will
1305 slice_cred = self.slice_credential(args[0])
1306 creds = [slice_cred]
1307 # options and call_id when supported
1309 api_options['call_id']=unique_call_id()
1310 if options.show_credential:
1311 show_credentials(creds)
1312 result = server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
1313 value = ReturnValue.get_value(result)
1314 if self.options.raw:
1315 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1321 def shutdown(self, options, args):
1323 shutdown named slice (Shutdown)
1325 server = self.sliceapi()
1328 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1330 slice_cred = self.slice_credential(slice_hrn)
1331 creds = [slice_cred]
1332 result = server.Shutdown(slice_urn, creds)
1333 value = ReturnValue.get_value(result)
1334 if self.options.raw:
1335 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1341 def gid(self, options, args):
1343 Create a GID (CreateGid)
1348 target_hrn = args[0]
1349 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1350 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1352 filename = options.file
1354 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1355 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1356 GID(string=gid).save_to_file(filename)
1359 def delegate (self, options, args):
1361 (locally) create delegate credential for use by given hrn
1367 # support for several delegations in the same call
1368 # so first we gather the things to do
1370 for slice_hrn in options.delegate_slices:
1371 message="%s.slice"%slice_hrn
1372 original = self.slice_credential_string(slice_hrn)
1373 tuples.append ( (message, original,) )
1374 if options.delegate_pi:
1375 my_authority=self.authority
1376 message="%s.pi"%my_authority
1377 original = self.my_authority_credential_string()
1378 tuples.append ( (message, original,) )
1379 for auth_hrn in options.delegate_auths:
1380 message="%s.auth"%auth_hrn
1381 original=self.authority_credential_string(auth_hrn)
1382 tuples.append ( (message, original, ) )
1383 # if nothing was specified at all at this point, let's assume -u
1384 if not tuples: options.delegate_user=True
1386 if options.delegate_user:
1387 message="%s.user"%self.user
1388 original = self.my_credential_string
1389 tuples.append ( (message, original, ) )
1391 # default type for beneficial is user unless -A
1392 if options.delegate_to_authority: to_type='authority'
1393 else: to_type='user'
1395 # let's now handle all this
1396 # it's all in the filenaming scheme
1397 for (message,original) in tuples:
1398 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1399 delegated_credential = Credential (string=delegated_string)
1400 filename = os.path.join ( self.options.sfi_dir,
1401 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1402 delegated_credential.save_to_file(filename, save_parents=True)
1403 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1405 def myslice (self, options, args):
1406 """ This helper is for refreshing your credentials at myslice; it will
1407 * compute all the slices that you currently have credentials on
1408 * refresh all your credentials (you as a user and pi, your slices)
1409 * upload them to the manifold backend server
1410 for that last phase, sfi_config is read to look for the [myslice] section, and
1411 namely the 'backend', 'delegate' and 'user' settings"""
1414 def trusted(self, options, args):
1416 return the trusted certs at this interface (get_trusted_certs)
1418 trusted_certs = self.registry().get_trusted_certs()
1419 for trusted_cert in trusted_certs:
1420 gid = GID(string=trusted_cert)
1422 cert = Certificate(string=trusted_cert)
1423 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1426 def config (self, options, args):
1427 "Display contents of current config"