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"),
280 def print_command_help (self, options):
281 verbose=getattr(options,'verbose')
282 format3="%18s %-15s %s"
285 print format3%("command","cmd_args","description")
289 self.create_parser().print_help()
290 for command in self.available_names:
291 args=self.available_dict[command]
292 method=getattr(self,command,None)
294 if method: doc=getattr(method,'__doc__',"")
295 if not doc: doc="*** no doc found ***"
296 doc=doc.strip(" \t\n")
297 doc=doc.replace("\n","\n"+35*' ')
300 print format3%(command,args,doc)
302 self.create_command_parser(command).print_help()
304 def create_command_parser(self, command):
305 if command not in self.available_dict:
306 msg="Invalid command\n"
308 msg += ','.join(self.available_names)
309 self.logger.critical(msg)
312 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
313 % (command, self.available_dict[command]))
315 if command in ("add", "update"):
316 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
317 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
318 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
319 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
321 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
322 default='', type="str", action='callback', callback=optparse_listvalue_callback)
323 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
324 help='Set/replace slice researchers', default='', type="str", action='callback',
325 callback=optparse_listvalue_callback)
326 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
327 default='', type="str", action='callback', callback=optparse_listvalue_callback)
328 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
329 action="callback", callback=optparse_dictvalue_callback, nargs=1,
330 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
332 # user specifies remote aggregate/sm/component
333 if command in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision",
334 "action", "shutdown", "renew", "status"):
335 parser.add_option("-d", "--delegate", dest="delegate", default=None,
337 help="Include a credential delegated to the user's root"+\
338 "authority in set of credentials for this call")
340 # show_credential option
341 if command in ("list","resources", "describe", "provision", "allocate", "add","update","remove","slices","delete","status","renew"):
342 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
343 help="show credential(s) used in human-readable form")
344 # registy filter option
345 if command in ("list", "show", "remove"):
346 parser.add_option("-t", "--type", dest="type", type="choice",
347 help="type filter ([all]|user|slice|authority|node|aggregate)",
348 choices=("all", "user", "slice", "authority", "node", "aggregate"),
350 if command in ("show"):
351 parser.add_option("-k","--key",dest="keys",action="append",default=[],
352 help="specify specific keys to be displayed from record")
353 if command in ("resources", "describe"):
355 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
356 help="schema type and version of resulting RSpec")
357 # disable/enable cached rspecs
358 parser.add_option("-c", "--current", dest="current", default=False,
360 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
362 parser.add_option("-f", "--format", dest="format", type="choice",
363 help="display format ([xml]|dns|ip)", default="xml",
364 choices=("xml", "dns", "ip"))
365 #panos: a new option to define the type of information about resources a user is interested in
366 parser.add_option("-i", "--info", dest="info",
367 help="optional component information", default=None)
368 # a new option to retreive or not reservation-oriented RSpecs (leases)
369 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
370 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
371 choices=("all", "resources", "leases"), default="resources")
374 if command in ("resources", "describe", "allocate", "provision", "show", "list", "gid"):
375 parser.add_option("-o", "--output", dest="file",
376 help="output XML to file", metavar="FILE", default=None)
378 if command in ("show", "list"):
379 parser.add_option("-f", "--format", dest="format", type="choice",
380 help="display format ([text]|xml)", default="text",
381 choices=("text", "xml"))
383 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
384 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
385 choices=("xml", "xmllist", "hrnlist"))
386 if command == 'list':
387 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
388 help="list all child records", default=False)
389 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
390 help="gives details, like user keys", default=False)
391 if command in ("delegate"):
392 parser.add_option("-u", "--user",
393 action="store_true", dest="delegate_user", default=False,
394 help="delegate your own credentials; default if no other option is provided")
395 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
396 metavar="slice_hrn", help="delegate cred. for slice HRN")
397 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
398 metavar='auth_hrn', help="delegate cred for auth HRN")
399 # this primarily is a shorthand for -a my_hrn^
400 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
401 help="delegate your PI credentials, so s.t. like -a your_hrn^")
402 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
403 help="""by default the mandatory argument is expected to be a user,
404 use this if you mean an authority instead""")
406 if command in ("version"):
407 parser.add_option("-R","--registry-version",
408 action="store_true", dest="version_registry", default=False,
409 help="probe registry version instead of sliceapi")
410 parser.add_option("-l","--local",
411 action="store_true", dest="version_local", default=False,
412 help="display version of the local client")
417 def create_parser(self):
419 # Generate command line parser
420 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
421 description="Commands: %s"%(" ".join(self.available_names)))
422 parser.add_option("-r", "--registry", dest="registry",
423 help="root registry", metavar="URL", default=None)
424 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
425 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
426 parser.add_option("-R", "--raw", dest="raw", default=None,
427 help="Save raw, unparsed server response to a file")
428 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
429 help="raw file format ([text]|pickled|json)", default="text",
430 choices=("text","pickled","json"))
431 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
432 help="text string to write before and after raw output")
433 parser.add_option("-d", "--dir", dest="sfi_dir",
434 help="config & working directory - default is %default",
435 metavar="PATH", default=Sfi.default_sfi_dir())
436 parser.add_option("-u", "--user", dest="user",
437 help="user name", metavar="HRN", default=None)
438 parser.add_option("-a", "--auth", dest="auth",
439 help="authority name", metavar="HRN", default=None)
440 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
441 help="verbose mode - cumulative")
442 parser.add_option("-D", "--debug",
443 action="store_true", dest="debug", default=False,
444 help="Debug (xml-rpc) protocol messages")
445 # would it make sense to use ~/.ssh/id_rsa as a default here ?
446 parser.add_option("-k", "--private-key",
447 action="store", dest="user_private_key", default=None,
448 help="point to the private key file to use if not yet installed in sfi_dir")
449 parser.add_option("-t", "--timeout", dest="timeout", default=None,
450 help="Amout of time to wait before timing out the request")
451 parser.add_option("-?", "--commands",
452 action="store_true", dest="command_help", default=False,
453 help="one page summary on commands & exit")
454 parser.disable_interspersed_args()
459 def print_help (self):
460 print "==================== Generic sfi usage"
461 self.sfi_parser.print_help()
462 print "==================== Specific command usage"
463 self.command_parser.print_help()
466 # Main: parse arguments and dispatch to command
468 def dispatch(self, command, command_options, command_args):
469 method=getattr(self, command,None)
471 print "Unknown command %s"%command
473 return method(command_options, command_args)
476 self.sfi_parser = self.create_parser()
477 (options, args) = self.sfi_parser.parse_args()
478 if options.command_help:
479 self.print_command_help(options)
481 self.options = options
483 self.logger.setLevelFromOptVerbose(self.options.verbose)
486 self.logger.critical("No command given. Use -h for help.")
487 self.print_command_help(options)
490 # complete / find unique match with command set
491 command_candidates = Candidates (self.available_names)
493 command = command_candidates.only_match(input)
495 self.print_command_help(options)
497 # second pass options parsing
498 self.command_parser = self.create_command_parser(command)
499 (command_options, command_args) = self.command_parser.parse_args(args[1:])
500 self.command_options = command_options
504 self.logger.debug("Command=%s" % command)
507 self.dispatch(command, command_options, command_args)
509 self.logger.log_exc ("sfi command %s failed"%command)
515 def read_config(self):
516 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
517 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
519 if Config.is_ini(config_file):
520 config = Config (config_file)
522 # try upgrading from shell config format
523 fp, fn = mkstemp(suffix='sfi_config', text=True)
525 # we need to preload the sections we want parsed
526 # from the shell config
527 config.add_section('sfi')
528 config.add_section('sface')
529 config.load(config_file)
531 shutil.move(config_file, shell_config_file)
533 config.save(config_file)
536 self.logger.critical("Failed to read configuration file %s"%config_file)
537 self.logger.info("Make sure to remove the export clauses and to add quotes")
538 if self.options.verbose==0:
539 self.logger.info("Re-run with -v for more details")
541 self.logger.log_exc("Could not read config file %s"%config_file)
546 if (self.options.sm is not None):
547 self.sm_url = self.options.sm
548 elif hasattr(config, "SFI_SM"):
549 self.sm_url = config.SFI_SM
551 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
555 if (self.options.registry is not None):
556 self.reg_url = self.options.registry
557 elif hasattr(config, "SFI_REGISTRY"):
558 self.reg_url = config.SFI_REGISTRY
560 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
564 if (self.options.user is not None):
565 self.user = self.options.user
566 elif hasattr(config, "SFI_USER"):
567 self.user = config.SFI_USER
569 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
573 if (self.options.auth is not None):
574 self.authority = self.options.auth
575 elif hasattr(config, "SFI_AUTH"):
576 self.authority = config.SFI_AUTH
578 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
581 self.config_file=config_file
585 def show_config (self):
586 print "From configuration file %s"%self.config_file
589 ('SFI_AUTH','authority'),
591 ('SFI_REGISTRY','reg_url'),
593 for (external_name, internal_name) in flags:
594 print "%s='%s'"%(external_name,getattr(self,internal_name))
597 # Get various credential and spec files
599 # Establishes limiting conventions
600 # - conflates MAs and SAs
601 # - assumes last token in slice name is unique
603 # Bootstraps credentials
604 # - bootstrap user credential from self-signed certificate
605 # - bootstrap authority credential from user credential
606 # - bootstrap slice credential from user credential
609 # init self-signed cert, user credentials and gid
610 def bootstrap (self):
611 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
613 # if -k is provided, use this to initialize private key
614 if self.options.user_private_key:
615 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
617 # trigger legacy compat code if needed
618 # the name has changed from just <leaf>.pkey to <hrn>.pkey
619 if not os.path.isfile(client_bootstrap.private_key_filename()):
620 self.logger.info ("private key not found, trying legacy name")
622 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
623 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
624 client_bootstrap.init_private_key_if_missing (legacy_private_key)
625 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
627 self.logger.log_exc("Can't find private key ")
631 client_bootstrap.bootstrap_my_gid()
632 # extract what's needed
633 self.private_key = client_bootstrap.private_key()
634 self.my_credential_string = client_bootstrap.my_credential_string ()
635 self.my_credential = {'geni_type': 'geni_sfa',
636 'geni_version': '3.0',
637 'geni_value': self.my_credential_string}
638 self.my_gid = client_bootstrap.my_gid ()
639 self.client_bootstrap = client_bootstrap
642 def my_authority_credential_string(self):
643 if not self.authority:
644 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
646 return self.client_bootstrap.authority_credential_string (self.authority)
648 def authority_credential_string(self, auth_hrn):
649 return self.client_bootstrap.authority_credential_string (auth_hrn)
651 def slice_credential_string(self, name):
652 return self.client_bootstrap.slice_credential_string (name)
654 def slice_credential(self, name):
655 return {'geni_type': 'geni_sfa',
656 'geni_version': '3.0',
657 'geni_value': self.slice_credential_string(name)}
659 # xxx should be supported by sfaclientbootstrap as well
660 def delegate_cred(self, object_cred, hrn, type='authority'):
661 # the gid and hrn of the object we are delegating
662 if isinstance(object_cred, str):
663 object_cred = Credential(string=object_cred)
664 object_gid = object_cred.get_gid_object()
665 object_hrn = object_gid.get_hrn()
667 if not object_cred.get_privileges().get_all_delegate():
668 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
671 # the delegating user's gid
672 caller_gidfile = self.my_gid()
674 # the gid of the user who will be delegated to
675 delegee_gid = self.client_bootstrap.gid(hrn,type)
676 delegee_hrn = delegee_gid.get_hrn()
677 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
678 return dcred.save_to_string(save_parents=True)
681 # Management of the servers
686 if not hasattr (self, 'registry_proxy'):
687 self.logger.info("Contacting Registry at: %s"%self.reg_url)
688 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
689 timeout=self.options.timeout, verbose=self.options.debug)
690 return self.registry_proxy
694 if not hasattr (self, 'sliceapi_proxy'):
695 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
696 if hasattr(self.command_options,'component') and self.command_options.component:
697 # resolve the hrn at the registry
698 node_hrn = self.command_options.component
699 records = self.registry().Resolve(node_hrn, self.my_credential_string)
700 records = filter_records('node', records)
702 self.logger.warning("No such component:%r"% opts.component)
704 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
705 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
707 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
708 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
709 self.sm_url = 'http://' + self.sm_url
710 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
711 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
712 timeout=self.options.timeout, verbose=self.options.debug)
713 return self.sliceapi_proxy
715 def get_cached_server_version(self, server):
716 # check local cache first
719 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
720 cache_key = server.url + "-version"
722 cache = Cache(cache_file)
725 self.logger.info("Local cache not found at: %s" % cache_file)
728 version = cache.get(cache_key)
731 result = server.GetVersion()
732 version= ReturnValue.get_value(result)
733 # cache version for 20 minutes
734 cache.add(cache_key, version, ttl= 60*20)
735 self.logger.info("Updating cache file %s" % cache_file)
736 cache.save_to_file(cache_file)
740 ### resurrect this temporarily so we can support V1 aggregates for a while
741 def server_supports_options_arg(self, server):
743 Returns true if server support the optional call_id arg, false otherwise.
745 server_version = self.get_cached_server_version(server)
747 # xxx need to rewrite this
748 if int(server_version.get('geni_api')) >= 2:
752 def server_supports_call_id_arg(self, server):
753 server_version = self.get_cached_server_version(server)
755 if 'sfa' in server_version and 'code_tag' in server_version:
756 code_tag = server_version['code_tag']
757 code_tag_parts = code_tag.split("-")
758 version_parts = code_tag_parts[0].split(".")
759 major, minor = version_parts[0], version_parts[1]
760 rev = code_tag_parts[1]
761 if int(major) == 1 and minor == 0 and build >= 22:
765 ### ois = options if supported
766 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
767 def ois (self, server, option_dict):
768 if self.server_supports_options_arg (server):
770 elif self.server_supports_call_id_arg (server):
771 return [ unique_call_id () ]
775 ### cis = call_id if supported - like ois
776 def cis (self, server):
777 if self.server_supports_call_id_arg (server):
778 return [ unique_call_id ]
782 ######################################## miscell utilities
783 def get_rspec_file(self, rspec):
784 if (os.path.isabs(rspec)):
787 file = os.path.join(self.options.sfi_dir, rspec)
788 if (os.path.isfile(file)):
791 self.logger.critical("No such rspec file %s"%rspec)
794 def get_record_file(self, record):
795 if (os.path.isabs(record)):
798 file = os.path.join(self.options.sfi_dir, record)
799 if (os.path.isfile(file)):
802 self.logger.critical("No such registry record file %s"%record)
806 #==========================================================================
807 # Following functions implement the commands
809 # Registry-related commands
810 #==========================================================================
812 def version(self, options, args):
814 display an SFA server version (GetVersion)
815 or version information about sfi itself
817 if options.version_local:
818 version=version_core()
820 if options.version_registry:
821 server=self.registry()
823 server = self.sliceapi()
824 result = server.GetVersion()
825 version = ReturnValue.get_value(result)
827 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
829 pprinter = PrettyPrinter(indent=4)
830 pprinter.pprint(version)
832 def list(self, options, args):
834 list entries in named authority registry (List)
841 if options.recursive:
842 opts['recursive'] = options.recursive
844 if options.show_credential:
845 show_credentials(self.my_credential_string)
847 list = self.registry().List(hrn, self.my_credential_string, options)
849 raise Exception, "Not enough parameters for the 'list' command"
851 # filter on person, slice, site, node, etc.
852 # This really should be in the self.filter_records funct def comment...
853 list = filter_records(options.type, list)
854 terminal_render (list, options)
856 save_records_to_file(options.file, list, options.fileformat)
859 def show(self, options, args):
861 show details about named registry record (Resolve)
867 # explicitly require Resolve to run in details mode
868 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
869 record_dicts = filter_records(options.type, record_dicts)
871 self.logger.error("No record of type %s"% options.type)
873 # user has required to focus on some keys
875 def project (record):
877 for key in options.keys:
878 try: projected[key]=record[key]
881 record_dicts = [ project (record) for record in record_dicts ]
882 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
883 for record in records:
884 if (options.format == "text"): record.dump(sort=True)
885 else: print record.save_as_xml()
887 save_records_to_file(options.file, record_dicts, options.fileformat)
890 def add(self, options, args):
891 "add record into registry by using the command options (Recommended) or from xml file (Register)"
892 auth_cred = self.my_authority_credential_string()
893 if options.show_credential:
894 show_credentials(auth_cred)
901 record_filepath = args[0]
902 rec_file = self.get_record_file(record_filepath)
903 record_dict.update(load_record_from_file(rec_file).todict())
905 print "Cannot load record file %s"%record_filepath
908 record_dict.update(load_record_from_opts(options).todict())
909 # we should have a type by now
910 if 'type' not in record_dict :
913 # this is still planetlab dependent.. as plc will whine without that
914 # also, it's only for adding
915 if record_dict['type'] == 'user':
916 if not 'first_name' in record_dict:
917 record_dict['first_name'] = record_dict['hrn']
918 if 'last_name' not in record_dict:
919 record_dict['last_name'] = record_dict['hrn']
920 return self.registry().Register(record_dict, auth_cred)
922 def update(self, options, args):
923 "update record into registry by using the command options (Recommended) or from xml file (Update)"
926 record_filepath = args[0]
927 rec_file = self.get_record_file(record_filepath)
928 record_dict.update(load_record_from_file(rec_file).todict())
930 record_dict.update(load_record_from_opts(options).todict())
931 # at the very least we need 'type' here
932 if 'type' not in record_dict:
936 # don't translate into an object, as this would possibly distort
937 # user-provided data; e.g. add an 'email' field to Users
938 if record_dict['type'] == "user":
939 if record_dict['hrn'] == self.user:
940 cred = self.my_credential_string
942 cred = self.my_authority_credential_string()
943 elif record_dict['type'] in ["slice"]:
945 cred = self.slice_credential_string(record_dict['hrn'])
946 except ServerException, e:
947 # XXX smbaker -- once we have better error return codes, update this
948 # to do something better than a string compare
949 if "Permission error" in e.args[0]:
950 cred = self.my_authority_credential_string()
953 elif record_dict['type'] in ["authority"]:
954 cred = self.my_authority_credential_string()
955 elif record_dict['type'] == 'node':
956 cred = self.my_authority_credential_string()
958 raise "unknown record type" + record_dict['type']
959 if options.show_credential:
960 show_credentials(cred)
961 return self.registry().Update(record_dict, cred)
963 def remove(self, options, args):
964 "remove registry record by name (Remove)"
965 auth_cred = self.my_authority_credential_string()
973 if options.show_credential:
974 show_credentials(auth_cred)
975 return self.registry().Remove(hrn, auth_cred, type)
977 # ==================================================================
978 # Slice-related commands
979 # ==================================================================
981 def slices(self, options, args):
982 "list instantiated slices (ListSlices) - returns urn's"
983 server = self.sliceapi()
985 creds = [self.my_credential_string]
986 # options and call_id when supported
988 api_options['call_id']=unique_call_id()
989 if options.show_credential:
990 show_credentials(creds)
991 result = server.ListSlices(creds, *self.ois(server,api_options))
992 value = ReturnValue.get_value(result)
994 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
999 # show rspec for named slice
1000 def resources(self, options, args):
1002 discover available resources (ListResources)
1004 server = self.sliceapi()
1007 creds = [self.my_credential]
1008 if options.delegate:
1009 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1010 if options.show_credential:
1011 show_credentials(creds)
1013 # no need to check if server accepts the options argument since the options has
1014 # been a required argument since v1 API
1016 # always send call_id to v2 servers
1017 api_options ['call_id'] = unique_call_id()
1018 # ask for cached value if available
1019 api_options ['cached'] = True
1021 api_options['info'] = options.info
1022 if options.list_leases:
1023 api_options['list_leases'] = options.list_leases
1025 if options.current == True:
1026 api_options['cached'] = False
1028 api_options['cached'] = True
1029 if options.rspec_version:
1030 version_manager = VersionManager()
1031 server_version = self.get_cached_server_version(server)
1032 if 'sfa' in server_version:
1033 # just request the version the client wants
1034 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1036 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1038 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1039 result = server.ListResources (creds, api_options)
1040 value = ReturnValue.get_value(result)
1041 if self.options.raw:
1042 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1043 if options.file is not None:
1044 save_rspec_to_file(value, options.file)
1045 if (self.options.raw is None) and (options.file is None):
1046 display_rspec(value, options.format)
1050 def describe(self, options, args):
1052 shows currently allocated/provisioned resources of the named slice or set of slivers (Describe)
1054 server = self.sliceapi()
1057 creds = [self.slice_credential(args[0])]
1058 if options.delegate:
1059 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1060 if options.show_credential:
1061 show_credentials(creds)
1063 api_options = {'call_id': unique_call_id(),
1065 'info': options.info,
1066 'list_leases': options.list_leases,
1067 'geni_rspec_version': {'type': 'geni', 'version': '3.0'},
1069 if options.rspec_version:
1070 version_manager = VersionManager()
1071 server_version = self.get_cached_server_version(server)
1072 if 'sfa' in server_version:
1073 # just request the version the client wants
1074 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1076 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1077 urn = Xrn(args[0], type='slice').get_urn()
1078 result = server.Describe([urn], creds, api_options)
1079 value = ReturnValue.get_value(result)
1080 if self.options.raw:
1081 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1082 if options.file is not None:
1083 save_rspec_to_file(value, options.file)
1084 if (self.options.raw is None) and (options.file is None):
1085 display_rspec(value, options.format)
1089 def delete(self, options, args):
1091 de-allocate and de-provision all or named slivers of the slice (Delete)
1093 server = self.sliceapi()
1097 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1100 slice_cred = self.slice_credential(slice_hrn)
1101 creds = [slice_cred]
1103 # options and call_id when supported
1105 api_options ['call_id'] = unique_call_id()
1106 if options.show_credential:
1107 show_credentials(creds)
1108 result = server.Delete([slice_urn], creds, *self.ois(server, api_options ) )
1109 value = ReturnValue.get_value(result)
1110 if self.options.raw:
1111 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1116 def allocate(self, options, args):
1118 allocate resources to the named slice (Allocate)
1120 server = self.sliceapi()
1121 server_version = self.get_cached_server_version(server)
1123 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1126 creds = [self.slice_credential(slice_hrn)]
1128 delegated_cred = None
1129 if server_version.get('interface') == 'slicemgr':
1130 # delegate our cred to the slice manager
1131 # do not delegate cred to slicemgr...not working at the moment
1133 #if server_version.get('hrn'):
1134 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1135 #elif server_version.get('urn'):
1136 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1138 if options.show_credential:
1139 show_credentials(creds)
1142 rspec_file = self.get_rspec_file(args[1])
1143 rspec = open(rspec_file).read()
1145 api_options ['call_id'] = unique_call_id()
1146 result = server.Allocate(slice_urn, creds, rspec, api_options)
1147 value = ReturnValue.get_value(result)
1148 if self.options.raw:
1149 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1150 if options.file is not None:
1151 save_rspec_to_file (value, options.file)
1152 if (self.options.raw is None) and (options.file is None):
1158 def provision(self, options, args):
1160 provision already allocated resources of named slice (Provision)
1162 server = self.sliceapi()
1163 server_version = self.get_cached_server_version(server)
1165 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1168 creds = [self.slice_credential(slice_hrn)]
1169 delegated_cred = None
1170 if server_version.get('interface') == 'slicemgr':
1171 # delegate our cred to the slice manager
1172 # do not delegate cred to slicemgr...not working at the moment
1174 #if server_version.get('hrn'):
1175 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1176 #elif server_version.get('urn'):
1177 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1179 if options.show_credential:
1180 show_credentials(creds)
1183 api_options ['call_id'] = unique_call_id()
1185 # set the requtested rspec version
1186 version_manager = VersionManager()
1187 rspec_version = version_manager._get_version('geni', '3.0').to_dict()
1188 api_options['geni_rspec_version'] = rspec_version
1191 # need to pass along user keys to the aggregate.
1193 # { urn: urn:publicid:IDN+emulab.net+user+alice
1194 # keys: [<ssh key A>, <ssh key B>]
1197 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1198 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1199 slice_record = slice_records[0]
1200 user_hrns = slice_record['researcher']
1201 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1202 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1203 users = pg_users_arg(user_records)
1205 api_options['geni_users'] = users
1206 result = server.Provision([slice_urn], creds, api_options)
1207 value = ReturnValue.get_value(result)
1208 if self.options.raw:
1209 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1210 if options.file is not None:
1211 save_rspec_to_file (value, options.file)
1212 if (self.options.raw is None) and (options.file is None):
1216 def status(self, options, args):
1218 retrieve the status of the slivers belonging to tne named slice (Status)
1220 server = self.sliceapi()
1224 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1227 slice_cred = self.slice_credential(slice_hrn)
1228 creds = [slice_cred]
1230 # options and call_id when supported
1232 api_options['call_id']=unique_call_id()
1233 if options.show_credential:
1234 show_credentials(creds)
1235 result = server.Status([slice_urn], creds, *self.ois(server,api_options))
1236 value = ReturnValue.get_value(result)
1237 if self.options.raw:
1238 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1243 def action(self, options, args):
1245 Perform the named operational action on the named slivers
1247 server = self.sliceapi()
1252 slice_urn = Xrn(slice_hrn, type='slice').get_urn()
1254 slice_cred = self.slice_credential(args[0])
1255 creds = [slice_cred]
1256 if options.delegate:
1257 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1258 creds.append(delegated_cred)
1260 result = server.PerformOperationalAction([slice_urn], creds, action , api_options)
1261 value = ReturnValue.get_value(result)
1262 if self.options.raw:
1263 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1268 def renew(self, options, args):
1270 renew slice (RenewSliver)
1272 server = self.sliceapi()
1276 [ slice_hrn, input_time ] = args
1278 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1279 # time: don't try to be smart on the time format, server-side will
1281 slice_cred = self.slice_credential(args[0])
1282 creds = [slice_cred]
1283 # options and call_id when supported
1285 api_options['call_id']=unique_call_id()
1286 if options.show_credential:
1287 show_credentials(creds)
1288 result = server.Renew([slice_urn], creds, input_time, *self.ois(server,api_options))
1289 value = ReturnValue.get_value(result)
1290 if self.options.raw:
1291 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1297 def shutdown(self, options, args):
1299 shutdown named slice (Shutdown)
1301 server = self.sliceapi()
1304 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1306 slice_cred = self.slice_credential(slice_hrn)
1307 creds = [slice_cred]
1308 result = server.Shutdown(slice_urn, creds)
1309 value = ReturnValue.get_value(result)
1310 if self.options.raw:
1311 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1317 def gid(self, options, args):
1319 Create a GID (CreateGid)
1324 target_hrn = args[0]
1325 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1326 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1328 filename = options.file
1330 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1331 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1332 GID(string=gid).save_to_file(filename)
1335 def delegate (self, options, args):
1337 (locally) create delegate credential for use by given hrn
1343 # support for several delegations in the same call
1344 # so first we gather the things to do
1346 for slice_hrn in options.delegate_slices:
1347 message="%s.slice"%slice_hrn
1348 original = self.slice_credential_string(slice_hrn)
1349 tuples.append ( (message, original,) )
1350 if options.delegate_pi:
1351 my_authority=self.authority
1352 message="%s.pi"%my_authority
1353 original = self.my_authority_credential_string()
1354 tuples.append ( (message, original,) )
1355 for auth_hrn in options.delegate_auths:
1356 message="%s.auth"%auth_hrn
1357 original=self.authority_credential_string(auth_hrn)
1358 tuples.append ( (message, original, ) )
1359 # if nothing was specified at all at this point, let's assume -u
1360 if not tuples: options.delegate_user=True
1362 if options.delegate_user:
1363 message="%s.user"%self.user
1364 original = self.my_credential_string
1365 tuples.append ( (message, original, ) )
1367 # default type for beneficial is user unless -A
1368 if options.delegate_to_authority: to_type='authority'
1369 else: to_type='user'
1371 # let's now handle all this
1372 # it's all in the filenaming scheme
1373 for (message,original) in tuples:
1374 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1375 delegated_credential = Credential (string=delegated_string)
1376 filename = os.path.join ( self.options.sfi_dir,
1377 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1378 delegated_credential.save_to_file(filename, save_parents=True)
1379 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1381 def trusted(self, options, args):
1383 return uhe trusted certs at this interface (get_trusted_certs)
1385 trusted_certs = self.registry().get_trusted_certs()
1386 for trusted_cert in trusted_certs:
1387 gid = GID(string=trusted_cert)
1389 cert = Certificate(string=trusted_cert)
1390 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1393 def config (self, options, args):
1394 "Display contents of current config"