2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
16 from lxml import etree
17 from StringIO import StringIO
18 from optparse import OptionParser
19 from pprint import PrettyPrinter
21 from sfa.trust.certificate import Keypair, Certificate
22 from sfa.trust.gid import GID
23 from sfa.trust.credential import Credential
24 from sfa.trust.sfaticket import SfaTicket
26 from sfa.util.faults import SfaInvalidArgument
27 from sfa.util.sfalogging import sfi_logger
28 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
29 from sfa.util.config import Config
30 from sfa.util.version import version_core
31 from sfa.util.cache import Cache
33 from sfa.storage.record import Record
35 from sfa.rspecs.rspec import RSpec
36 from sfa.rspecs.rspec_converter import RSpecConverter
37 from sfa.rspecs.version_manager import VersionManager
39 from sfa.client.sfaclientlib import SfaClientBootstrap
40 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
41 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
42 from sfa.client.return_value import ReturnValue
43 from sfa.client.candidates import Candidates
47 # utility methods here
48 def optparse_listvalue_callback(option, option_string, value, parser):
49 setattr(parser.values, option.dest, value.split(','))
51 # a code fragment that could be helpful for argparse which unfortunately is
52 # available with 2.7 only, so this feels like too strong a requirement for the client side
53 #class ExtraArgAction (argparse.Action):
54 # def __call__ (self, parser, namespace, values, option_string=None):
55 # would need a try/except of course
56 # (k,v)=values.split('=')
57 # d=getattr(namespace,self.dest)
60 #parser.add_argument ("-X","--extra",dest='extras', default={}, action=ExtraArgAction,
61 # help="set extra flags, testbed dependent, e.g. --extra enabled=true")
63 def optparse_dictvalue_callback (option, option_string, value, parser):
65 (k,v)=value.split('=',1)
66 d=getattr(parser.values, option.dest)
73 def display_rspec(rspec, format='rspec'):
75 tree = etree.parse(StringIO(rspec))
77 result = root.xpath("./network/site/node/hostname/text()")
78 elif format in ['ip']:
79 # The IP address is not yet part of the new RSpec
80 # so this doesn't do anything yet.
81 tree = etree.parse(StringIO(rspec))
83 result = root.xpath("./network/site/node/ipv4/text()")
90 def display_list(results):
91 for result in results:
94 def display_records(recordList, dump=False):
95 ''' Print all fields in the record'''
96 for record in recordList:
97 display_record(record, dump)
99 def display_record(record, dump=False):
101 record.dump(sort=True)
103 info = record.getdict()
104 print "%s (%s)" % (info['hrn'], info['type'])
108 def filter_records(type, records):
109 filtered_records = []
110 for record in records:
111 if (record['type'] == type) or (type == "all"):
112 filtered_records.append(record)
113 return filtered_records
116 def credential_printable (credential_string):
117 credential=Credential(string=credential_string)
119 result += credential.get_summary_tostring()
121 rights = credential.get_privileges()
122 result += "rights=%s"%rights
126 def show_credentials (cred_s):
127 if not isinstance (cred_s,list): cred_s = [cred_s]
129 print "Using Credential %s"%credential_printable(cred)
132 def save_raw_to_file(var, filename, format="text", banner=None):
134 # if filename is "-", send it to stdout
137 f = open(filename, "w")
142 elif format == "pickled":
143 f.write(pickle.dumps(var))
144 elif format == "json":
145 if hasattr(json, "dumps"):
146 f.write(json.dumps(var)) # python 2.6
148 f.write(json.write(var)) # python 2.5
150 # this should never happen
151 print "unknown output format", format
153 f.write('\n'+banner+"\n")
155 def save_rspec_to_file(rspec, filename):
156 if not filename.endswith(".rspec"):
157 filename = filename + ".rspec"
158 f = open(filename, 'w')
163 def save_records_to_file(filename, record_dicts, format="xml"):
166 for record_dict in record_dicts:
168 save_record_to_file(filename + "." + str(index), record_dict)
170 save_record_to_file(filename, record_dict)
172 elif format == "xmllist":
173 f = open(filename, "w")
174 f.write("<recordlist>\n")
175 for record_dict in record_dicts:
176 record_obj=Record(dict=record_dict)
177 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
178 f.write("</recordlist>\n")
180 elif format == "hrnlist":
181 f = open(filename, "w")
182 for record_dict in record_dicts:
183 record_obj=Record(dict=record_dict)
184 f.write(record_obj.hrn + "\n")
187 # this should never happen
188 print "unknown output format", format
190 def save_record_to_file(filename, record_dict):
191 record = Record(dict=record_dict)
192 xml = record.save_as_xml()
193 f=codecs.open(filename, encoding='utf-8',mode="w")
198 # minimally check a key argument
199 def check_ssh_key (key):
200 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
201 return re.match(good_ssh_key, key, re.IGNORECASE)
204 def load_record_from_opts(options):
206 if hasattr(options, 'xrn') and options.xrn:
207 if hasattr(options, 'type') and options.type:
208 xrn = Xrn(options.xrn, options.type)
210 xrn = Xrn(options.xrn)
211 record_dict['urn'] = xrn.get_urn()
212 record_dict['hrn'] = xrn.get_hrn()
213 record_dict['type'] = xrn.get_type()
214 if hasattr(options, 'key') and options.key:
216 pubkey = open(options.key, 'r').read()
219 if not check_ssh_key (pubkey):
220 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
221 record_dict['keys'] = [pubkey]
222 if hasattr(options, 'slices') and options.slices:
223 record_dict['slices'] = options.slices
224 if hasattr(options, 'researchers') and options.researchers:
225 record_dict['researcher'] = options.researchers
226 if hasattr(options, 'email') and options.email:
227 record_dict['email'] = options.email
228 if hasattr(options, 'pis') and options.pis:
229 record_dict['pi'] = options.pis
231 # handle extra settings
232 record_dict.update(options.extras)
234 return Record(dict=record_dict)
236 def load_record_from_file(filename):
237 f=codecs.open(filename, encoding="utf-8", mode="r")
238 xml_string = f.read()
240 return Record(xml=xml_string)
244 def unique_call_id(): return uuid.uuid4().urn
248 # dirty hack to make this class usable from the outside
249 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
252 def default_sfi_dir ():
253 if os.path.isfile("./sfi_config"):
256 return os.path.expanduser("~/.sfi/")
258 # dummy to meet Sfi's expectations for its 'options' field
259 # i.e. s/t we can do setattr on
263 def __init__ (self,options=None):
264 if options is None: options=Sfi.DummyOptions()
265 for opt in Sfi.required_options:
266 if not hasattr(options,opt): setattr(options,opt,None)
267 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
268 self.options = options
270 self.authority = None
271 self.logger = sfi_logger
272 self.logger.enable_console()
273 self.available_names = [ tuple[0] for tuple in Sfi.available ]
274 self.available_dict = dict (Sfi.available)
276 # tuples command-name expected-args in the order in which they should appear in the help
279 ("list", "authority"),
282 ("update", "record"),
285 ("resources", "[slice_hrn]"),
286 ("create", "slice_hrn rspec"),
287 ("delete", "slice_hrn"),
288 ("status", "slice_hrn"),
289 ("start", "slice_hrn"),
290 ("stop", "slice_hrn"),
291 ("reset", "slice_hrn"),
292 ("renew", "slice_hrn time"),
293 ("shutdown", "slice_hrn"),
294 ("get_ticket", "slice_hrn rspec"),
295 ("redeem_ticket", "ticket"),
296 ("delegate", "name"),
302 def print_command_help (self, options):
303 verbose=getattr(options,'verbose')
304 format3="%18s %-15s %s"
307 print format3%("command","cmd_args","description")
311 self.create_parser().print_help()
312 for command in self.available_names:
313 args=self.available_dict[command]
314 method=getattr(self,command,None)
316 if method: doc=getattr(method,'__doc__',"")
317 if not doc: doc="*** no doc found ***"
318 doc=doc.strip(" \t\n")
319 doc=doc.replace("\n","\n"+35*' ')
322 print format3%(command,args,doc)
324 self.create_command_parser(command).print_help()
326 def create_command_parser(self, command):
327 if command not in self.available_dict:
328 msg="Invalid command\n"
330 msg += ','.join(self.available_names)
331 self.logger.critical(msg)
334 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
335 % (command, self.available_dict[command]))
337 if command in ("add", "update"):
338 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
339 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
340 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
341 # use --extra instead
342 # parser.add_option('-u', '--url', dest='url', metavar='<url>', default=None, help="URL, useful for slices")
343 # parser.add_option('-d', '--description', dest='description', metavar='<description>',
344 # help='Description, useful for slices', default=None)
345 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
347 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='slice xrns',
348 default='', type="str", action='callback', callback=optparse_listvalue_callback)
349 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
350 help='slice researchers', default='', type="str", action='callback',
351 callback=optparse_listvalue_callback)
352 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Principal Investigators/Project Managers',
353 default='', type="str", action='callback', callback=optparse_listvalue_callback)
354 # use --extra instead
355 # parser.add_option('-f', '--firstname', dest='firstname', metavar='<firstname>', help='user first name')
356 # parser.add_option('-l', '--lastname', dest='lastname', metavar='<lastname>', help='user last name')
357 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
358 action="callback", callback=optparse_dictvalue_callback, nargs=1,
359 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
361 # user specifies remote aggregate/sm/component
362 if command in ("resources", "slices", "create", "delete", "start", "stop",
363 "restart", "shutdown", "get_ticket", "renew", "status"):
364 parser.add_option("-d", "--delegate", dest="delegate", default=None,
366 help="Include a credential delegated to the user's root"+\
367 "authority in set of credentials for this call")
369 # show_credential option
370 if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
371 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
372 help="show credential(s) used in human-readable form")
373 # registy filter option
374 if command in ("list", "show", "remove"):
375 parser.add_option("-t", "--type", dest="type", type="choice",
376 help="type filter ([all]|user|slice|authority|node|aggregate)",
377 choices=("all", "user", "slice", "authority", "node", "aggregate"),
379 if command in ("show"):
380 parser.add_option("-k","--key",dest="keys",action="append",default=[],
381 help="specify specific keys to be displayed from record")
382 if command in ("resources"):
384 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
385 help="schema type and version of resulting RSpec")
386 # disable/enable cached rspecs
387 parser.add_option("-c", "--current", dest="current", default=False,
389 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
391 parser.add_option("-f", "--format", dest="format", type="choice",
392 help="display format ([xml]|dns|ip)", default="xml",
393 choices=("xml", "dns", "ip"))
394 #panos: a new option to define the type of information about resources a user is interested in
395 parser.add_option("-i", "--info", dest="info",
396 help="optional component information", default=None)
397 # a new option to retreive or not reservation-oriented RSpecs (leases)
398 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
399 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
400 choices=("all", "resources", "leases"), default="resources")
403 # 'create' does return the new rspec, makes sense to save that too
404 if command in ("resources", "show", "list", "gid", 'create'):
405 parser.add_option("-o", "--output", dest="file",
406 help="output XML to file", metavar="FILE", default=None)
408 if command in ("show", "list"):
409 parser.add_option("-f", "--format", dest="format", type="choice",
410 help="display format ([text]|xml)", default="text",
411 choices=("text", "xml"))
413 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
414 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
415 choices=("xml", "xmllist", "hrnlist"))
416 if command == 'list':
417 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
418 help="list all child records", default=False)
419 if command in ("delegate"):
420 parser.add_option("-u", "--user",
421 action="store_true", dest="delegate_user", default=False,
422 help="delegate user credential")
423 parser.add_option("-s", "--slice", dest="delegate_slice",
424 help="delegate slice credential", metavar="HRN", default=None)
426 if command in ("version"):
427 parser.add_option("-R","--registry-version",
428 action="store_true", dest="version_registry", default=False,
429 help="probe registry version instead of sliceapi")
430 parser.add_option("-l","--local",
431 action="store_true", dest="version_local", default=False,
432 help="display version of the local client")
437 def create_parser(self):
439 # Generate command line parser
440 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
441 description="Commands: %s"%(" ".join(self.available_names)))
442 parser.add_option("-r", "--registry", dest="registry",
443 help="root registry", metavar="URL", default=None)
444 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
445 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
446 parser.add_option("-R", "--raw", dest="raw", default=None,
447 help="Save raw, unparsed server response to a file")
448 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
449 help="raw file format ([text]|pickled|json)", default="text",
450 choices=("text","pickled","json"))
451 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
452 help="text string to write before and after raw output")
453 parser.add_option("-d", "--dir", dest="sfi_dir",
454 help="config & working directory - default is %default",
455 metavar="PATH", default=Sfi.default_sfi_dir())
456 parser.add_option("-u", "--user", dest="user",
457 help="user name", metavar="HRN", default=None)
458 parser.add_option("-a", "--auth", dest="auth",
459 help="authority name", metavar="HRN", default=None)
460 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
461 help="verbose mode - cumulative")
462 parser.add_option("-D", "--debug",
463 action="store_true", dest="debug", default=False,
464 help="Debug (xml-rpc) protocol messages")
465 # would it make sense to use ~/.ssh/id_rsa as a default here ?
466 parser.add_option("-k", "--private-key",
467 action="store", dest="user_private_key", default=None,
468 help="point to the private key file to use if not yet installed in sfi_dir")
469 parser.add_option("-t", "--timeout", dest="timeout", default=None,
470 help="Amout of time to wait before timing out the request")
471 parser.add_option("-?", "--commands",
472 action="store_true", dest="command_help", default=False,
473 help="one page summary on commands & exit")
474 parser.disable_interspersed_args()
479 def print_help (self):
480 print "==================== Generic sfi usage"
481 self.sfi_parser.print_help()
482 print "==================== Specific command usage"
483 self.command_parser.print_help()
486 # Main: parse arguments and dispatch to command
488 def dispatch(self, command, command_options, command_args):
489 return getattr(self, command)(command_options, command_args)
492 self.sfi_parser = self.create_parser()
493 (options, args) = self.sfi_parser.parse_args()
494 if options.command_help:
495 self.print_command_help(options)
497 self.options = options
499 self.logger.setLevelFromOptVerbose(self.options.verbose)
502 self.logger.critical("No command given. Use -h for help.")
503 self.print_command_help(options)
506 # complete / find unique match with command set
507 command_candidates = Candidates (self.available_names)
509 command = command_candidates.only_match(input)
511 self.print_command_help(options)
513 # second pass options parsing
514 self.command_parser = self.create_command_parser(command)
515 (command_options, command_args) = self.command_parser.parse_args(args[1:])
516 self.command_options = command_options
520 self.logger.debug("Command=%s" % command)
523 self.dispatch(command, command_options, command_args)
525 self.logger.critical ("Unknown command %s"%command)
531 def read_config(self):
532 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
534 config = Config (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.errors("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.errors("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"%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_gid = client_bootstrap.my_gid ()
636 self.client_bootstrap = client_bootstrap
639 def my_authority_credential_string(self):
640 if not self.authority:
641 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
643 return self.client_bootstrap.authority_credential_string (self.authority)
645 def slice_credential_string(self, name):
646 return self.client_bootstrap.slice_credential_string (name)
648 # xxx should be supported by sfaclientbootstrap as well
649 def delegate_cred(self, object_cred, hrn, type='authority'):
650 # the gid and hrn of the object we are delegating
651 if isinstance(object_cred, str):
652 object_cred = Credential(string=object_cred)
653 object_gid = object_cred.get_gid_object()
654 object_hrn = object_gid.get_hrn()
656 if not object_cred.get_privileges().get_all_delegate():
657 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
660 # the delegating user's gid
661 caller_gidfile = self.my_gid()
663 # the gid of the user who will be delegated to
664 delegee_gid = self.client_bootstrap.gid(hrn,type)
665 delegee_hrn = delegee_gid.get_hrn()
666 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
667 return dcred.save_to_string(save_parents=True)
670 # Management of the servers
675 if not hasattr (self, 'registry_proxy'):
676 self.logger.info("Contacting Registry at: %s"%self.reg_url)
677 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
678 timeout=self.options.timeout, verbose=self.options.debug)
679 return self.registry_proxy
683 if not hasattr (self, 'sliceapi_proxy'):
684 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
685 if hasattr(self.command_options,'component') and self.command_options.component:
686 # resolve the hrn at the registry
687 node_hrn = self.command_options.component
688 records = self.registry().Resolve(node_hrn, self.my_credential_string)
689 records = filter_records('node', records)
691 self.logger.warning("No such component:%r"% opts.component)
693 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
694 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
696 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
697 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
698 self.sm_url = 'http://' + self.sm_url
699 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
700 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
701 timeout=self.options.timeout, verbose=self.options.debug)
702 return self.sliceapi_proxy
704 def get_cached_server_version(self, server):
705 # check local cache first
708 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
709 cache_key = server.url + "-version"
711 cache = Cache(cache_file)
714 self.logger.info("Local cache not found at: %s" % cache_file)
717 version = cache.get(cache_key)
720 result = server.GetVersion()
721 version= ReturnValue.get_value(result)
722 # cache version for 20 minutes
723 cache.add(cache_key, version, ttl= 60*20)
724 self.logger.info("Updating cache file %s" % cache_file)
725 cache.save_to_file(cache_file)
729 ### resurrect this temporarily so we can support V1 aggregates for a while
730 def server_supports_options_arg(self, server):
732 Returns true if server support the optional call_id arg, false otherwise.
734 server_version = self.get_cached_server_version(server)
736 # xxx need to rewrite this
737 if int(server_version.get('geni_api')) >= 2:
741 def server_supports_call_id_arg(self, server):
742 server_version = self.get_cached_server_version(server)
744 if 'sfa' in server_version and 'code_tag' in server_version:
745 code_tag = server_version['code_tag']
746 code_tag_parts = code_tag.split("-")
747 version_parts = code_tag_parts[0].split(".")
748 major, minor = version_parts[0], version_parts[1]
749 rev = code_tag_parts[1]
750 if int(major) == 1 and minor == 0 and build >= 22:
754 ### ois = options if supported
755 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
756 def ois (self, server, option_dict):
757 if self.server_supports_options_arg (server):
759 elif self.server_supports_call_id_arg (server):
760 return [ unique_call_id () ]
764 ### cis = call_id if supported - like ois
765 def cis (self, server):
766 if self.server_supports_call_id_arg (server):
767 return [ unique_call_id ]
771 ######################################## miscell utilities
772 def get_rspec_file(self, rspec):
773 if (os.path.isabs(rspec)):
776 file = os.path.join(self.options.sfi_dir, rspec)
777 if (os.path.isfile(file)):
780 self.logger.critical("No such rspec file %s"%rspec)
783 def get_record_file(self, record):
784 if (os.path.isabs(record)):
787 file = os.path.join(self.options.sfi_dir, record)
788 if (os.path.isfile(file)):
791 self.logger.critical("No such registry record file %s"%record)
795 #==========================================================================
796 # Following functions implement the commands
798 # Registry-related commands
799 #==========================================================================
801 def version(self, options, args):
803 display an SFA server version (GetVersion)
804 or version information about sfi itself
806 if options.version_local:
807 version=version_core()
809 if options.version_registry:
810 server=self.registry()
812 server = self.sliceapi()
813 result = server.GetVersion()
814 version = ReturnValue.get_value(result)
816 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
818 pprinter = PrettyPrinter(indent=4)
819 pprinter.pprint(version)
821 def list(self, options, args):
823 list entries in named authority registry (List)
830 if options.recursive:
831 opts['recursive'] = options.recursive
833 if options.show_credential:
834 show_credentials(self.my_credential_string)
836 list = self.registry().List(hrn, self.my_credential_string, options)
838 raise Exception, "Not enough parameters for the 'list' command"
840 # filter on person, slice, site, node, etc.
841 # THis really should be in the self.filter_records funct def comment...
842 list = filter_records(options.type, list)
844 print "%s (%s)" % (record['hrn'], record['type'])
846 save_records_to_file(options.file, list, options.fileformat)
849 def show(self, options, args):
851 show details about named registry record (Resolve)
857 record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
858 record_dicts = filter_records(options.type, record_dicts)
860 self.logger.error("No record of type %s"% options.type)
862 # user has required to focus on some keys
864 def project (record):
866 for key in options.keys:
867 try: projected[key]=record[key]
870 record_dicts = [ project (record) for record in record_dicts ]
871 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
872 for record in records:
873 if (options.format == "text"): record.dump(sort=True)
874 else: print record.save_as_xml()
876 save_records_to_file(options.file, record_dicts, options.fileformat)
879 def add(self, options, args):
880 "add record into registry from xml file (Register)"
881 auth_cred = self.my_authority_credential_string()
882 if options.show_credential:
883 show_credentials(auth_cred)
886 record_filepath = args[0]
887 rec_file = self.get_record_file(record_filepath)
888 record_dict.update(load_record_from_file(rec_file).todict())
890 record_dict.update(load_record_from_opts(options).todict())
891 # we should have a type by now
892 if 'type' not in record_dict :
895 # this is still planetlab dependent.. as plc will whine without that
896 # also, it's only for adding
897 if record_dict['type'] == 'user':
898 if not 'first_name' in record_dict:
899 record_dict['first_name'] = record_dict['hrn']
900 if 'last_name' not in record_dict:
901 record_dict['last_name'] = record_dict['hrn']
902 return self.registry().Register(record_dict, auth_cred)
904 def update(self, options, args):
905 "update record into registry from xml file (Update)"
908 record_filepath = args[0]
909 rec_file = self.get_record_file(record_filepath)
910 record_dict.update(load_record_from_file(rec_file).todict())
912 record_dict.update(load_record_from_opts(options).todict())
913 # at the very least we need 'type' here
914 if 'type' not in record_dict:
918 # don't translate into an object, as this would possibly distort
919 # user-provided data; e.g. add an 'email' field to Users
920 if record_dict['type'] == "user":
921 if record_dict['hrn'] == self.user:
922 cred = self.my_credential_string
924 cred = self.my_authority_credential_string()
925 elif record_dict['type'] in ["slice"]:
927 cred = self.slice_credential_string(record_dict['hrn'])
928 except ServerException, e:
929 # XXX smbaker -- once we have better error return codes, update this
930 # to do something better than a string compare
931 if "Permission error" in e.args[0]:
932 cred = self.my_authority_credential_string()
935 elif record_dict['type'] in ["authority"]:
936 cred = self.my_authority_credential_string()
937 elif record_dict['type'] == 'node':
938 cred = self.my_authority_credential_string()
940 raise "unknown record type" + record_dict['type']
941 if options.show_credential:
942 show_credentials(cred)
943 return self.registry().Update(record_dict, cred)
945 def remove(self, options, args):
946 "remove registry record by name (Remove)"
947 auth_cred = self.my_authority_credential_string()
955 if options.show_credential:
956 show_credentials(auth_cred)
957 return self.registry().Remove(hrn, auth_cred, type)
959 # ==================================================================
960 # Slice-related commands
961 # ==================================================================
963 def slices(self, options, args):
964 "list instantiated slices (ListSlices) - returns urn's"
965 server = self.sliceapi()
967 creds = [self.my_credential_string]
969 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
970 creds.append(delegated_cred)
971 # options and call_id when supported
973 api_options['call_id']=unique_call_id()
974 if options.show_credential:
975 show_credentials(creds)
976 result = server.ListSlices(creds, *self.ois(server,api_options))
977 value = ReturnValue.get_value(result)
979 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
984 # show rspec for named slice
985 def resources(self, options, args):
987 with no arg, discover available resources, (ListResources)
988 or with an slice hrn, shows currently provisioned resources
990 server = self.sliceapi()
995 creds.append(self.slice_credential_string(args[0]))
997 creds.append(self.my_credential_string)
999 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
1000 if options.show_credential:
1001 show_credentials(creds)
1003 # no need to check if server accepts the options argument since the options has
1004 # been a required argument since v1 API
1006 # always send call_id to v2 servers
1007 api_options ['call_id'] = unique_call_id()
1008 # ask for cached value if available
1009 api_options ['cached'] = True
1012 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
1014 api_options['info'] = options.info
1015 if options.list_leases:
1016 api_options['list_leases'] = options.list_leases
1018 if options.current == True:
1019 api_options['cached'] = False
1021 api_options['cached'] = True
1022 if options.rspec_version:
1023 version_manager = VersionManager()
1024 server_version = self.get_cached_server_version(server)
1025 if 'sfa' in server_version:
1026 # just request the version the client wants
1027 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1029 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1031 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1032 result = server.ListResources (creds, api_options)
1033 value = ReturnValue.get_value(result)
1034 if self.options.raw:
1035 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1036 if options.file is not None:
1037 save_rspec_to_file(value, options.file)
1038 if (self.options.raw is None) and (options.file is None):
1039 display_rspec(value, options.format)
1043 def create(self, options, args):
1045 create or update named slice with given rspec
1047 server = self.sliceapi()
1049 # xxx do we need to check usage (len(args)) ?
1052 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1055 creds = [self.slice_credential_string(slice_hrn)]
1057 delegated_cred = None
1058 server_version = self.get_cached_server_version(server)
1059 if server_version.get('interface') == 'slicemgr':
1060 # delegate our cred to the slice manager
1061 # do not delegate cred to slicemgr...not working at the moment
1063 #if server_version.get('hrn'):
1064 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1065 #elif server_version.get('urn'):
1066 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1068 if options.show_credential:
1069 show_credentials(creds)
1072 rspec_file = self.get_rspec_file(args[1])
1073 rspec = open(rspec_file).read()
1076 # need to pass along user keys to the aggregate.
1078 # { urn: urn:publicid:IDN+emulab.net+user+alice
1079 # keys: [<ssh key A>, <ssh key B>]
1082 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1083 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1084 slice_record = slice_records[0]
1085 user_hrns = slice_record['researcher']
1086 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1087 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1089 if 'sfa' not in server_version:
1090 users = pg_users_arg(user_records)
1091 rspec = RSpec(rspec)
1092 rspec.filter({'component_manager_id': server_version['urn']})
1093 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1095 users = sfa_users_arg(user_records, slice_record)
1097 # do not append users, keys, or slice tags. Anything
1098 # not contained in this request will be removed from the slice
1100 # CreateSliver has supported the options argument for a while now so it should
1101 # be safe to assume this server support it
1103 api_options ['append'] = False
1104 api_options ['call_id'] = unique_call_id()
1105 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1106 value = ReturnValue.get_value(result)
1107 if self.options.raw:
1108 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1109 if options.file is not None:
1110 save_rspec_to_file (value, options.file)
1111 if (self.options.raw is None) and (options.file is None):
1116 def delete(self, options, args):
1118 delete named slice (DeleteSliver)
1120 server = self.sliceapi()
1124 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1127 slice_cred = self.slice_credential_string(slice_hrn)
1128 creds = [slice_cred]
1129 if options.delegate:
1130 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1131 creds.append(delegated_cred)
1133 # options and call_id when supported
1135 api_options ['call_id'] = unique_call_id()
1136 if options.show_credential:
1137 show_credentials(creds)
1138 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1139 value = ReturnValue.get_value(result)
1140 if self.options.raw:
1141 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1146 def status(self, options, args):
1148 retrieve slice status (SliverStatus)
1150 server = self.sliceapi()
1154 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1157 slice_cred = self.slice_credential_string(slice_hrn)
1158 creds = [slice_cred]
1159 if options.delegate:
1160 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1161 creds.append(delegated_cred)
1163 # options and call_id when supported
1165 api_options['call_id']=unique_call_id()
1166 if options.show_credential:
1167 show_credentials(creds)
1168 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1169 value = ReturnValue.get_value(result)
1170 if self.options.raw:
1171 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1175 def start(self, options, args):
1177 start named slice (Start)
1179 server = self.sliceapi()
1183 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1186 slice_cred = self.slice_credential_string(args[0])
1187 creds = [slice_cred]
1188 if options.delegate:
1189 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1190 creds.append(delegated_cred)
1191 # xxx Thierry - does this not need an api_options as well ?
1192 result = server.Start(slice_urn, creds)
1193 value = ReturnValue.get_value(result)
1194 if self.options.raw:
1195 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1200 def stop(self, options, args):
1202 stop named slice (Stop)
1204 server = self.sliceapi()
1207 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1209 slice_cred = self.slice_credential_string(args[0])
1210 creds = [slice_cred]
1211 if options.delegate:
1212 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1213 creds.append(delegated_cred)
1214 result = server.Stop(slice_urn, creds)
1215 value = ReturnValue.get_value(result)
1216 if self.options.raw:
1217 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1223 def reset(self, options, args):
1225 reset named slice (reset_slice)
1227 server = self.sliceapi()
1230 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1232 slice_cred = self.slice_credential_string(args[0])
1233 creds = [slice_cred]
1234 if options.delegate:
1235 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1236 creds.append(delegated_cred)
1237 result = server.reset_slice(creds, slice_urn)
1238 value = ReturnValue.get_value(result)
1239 if self.options.raw:
1240 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1245 def renew(self, options, args):
1247 renew slice (RenewSliver)
1249 server = self.sliceapi()
1253 [ slice_hrn, input_time ] = args
1255 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1256 # time: don't try to be smart on the time format, server-side will
1258 slice_cred = self.slice_credential_string(args[0])
1259 creds = [slice_cred]
1260 if options.delegate:
1261 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1262 creds.append(delegated_cred)
1263 # options and call_id when supported
1265 api_options['call_id']=unique_call_id()
1266 if options.show_credential:
1267 show_credentials(creds)
1268 result = server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1269 value = ReturnValue.get_value(result)
1270 if self.options.raw:
1271 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1277 def shutdown(self, options, args):
1279 shutdown named slice (Shutdown)
1281 server = self.sliceapi()
1284 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1286 slice_cred = self.slice_credential_string(slice_hrn)
1287 creds = [slice_cred]
1288 if options.delegate:
1289 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1290 creds.append(delegated_cred)
1291 result = server.Shutdown(slice_urn, creds)
1292 value = ReturnValue.get_value(result)
1293 if self.options.raw:
1294 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1300 def get_ticket(self, options, args):
1302 get a ticket for the specified slice
1304 server = self.sliceapi()
1306 slice_hrn, rspec_path = args[0], args[1]
1307 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1309 slice_cred = self.slice_credential_string(slice_hrn)
1310 creds = [slice_cred]
1311 if options.delegate:
1312 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1313 creds.append(delegated_cred)
1315 rspec_file = self.get_rspec_file(rspec_path)
1316 rspec = open(rspec_file).read()
1317 # options and call_id when supported
1319 api_options['call_id']=unique_call_id()
1320 # get ticket at the server
1321 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1323 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1324 self.logger.info("writing ticket to %s"%file)
1325 ticket = SfaTicket(string=ticket_string)
1326 ticket.save_to_file(filename=file, save_parents=True)
1328 def redeem_ticket(self, options, args):
1330 Connects to nodes in a slice and redeems a ticket
1331 (slice hrn is retrieved from the ticket)
1333 ticket_file = args[0]
1335 # get slice hrn from the ticket
1336 # use this to get the right slice credential
1337 ticket = SfaTicket(filename=ticket_file)
1339 ticket_string = ticket.save_to_string(save_parents=True)
1341 slice_hrn = ticket.gidObject.get_hrn()
1342 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1343 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1344 slice_cred = self.slice_credential_string(slice_hrn)
1346 # get a list of node hostnames from the RSpec
1347 tree = etree.parse(StringIO(ticket.rspec))
1348 root = tree.getroot()
1349 hostnames = root.xpath("./network/site/node/hostname/text()")
1351 # create an xmlrpc connection to the component manager at each of these
1352 # components and gall redeem_ticket
1354 for hostname in hostnames:
1356 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1357 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1358 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1359 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1360 timeout=self.options.timeout, verbose=self.options.debug)
1361 server.RedeemTicket(ticket_string, slice_cred)
1362 self.logger.info("Success")
1363 except socket.gaierror:
1364 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1365 except Exception, e:
1366 self.logger.log_exc(e.message)
1369 def gid(self, options, args):
1371 Create a GID (CreateGid)
1376 target_hrn = args[0]
1377 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1379 filename = options.file
1381 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1382 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1383 GID(string=gid).save_to_file(filename)
1386 def delegate(self, options, args):
1388 (locally) create delegate credential for use by given hrn
1390 delegee_hrn = args[0]
1391 if options.delegate_user:
1392 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1393 elif options.delegate_slice:
1394 slice_cred = self.slice_credential_string(options.delegate_slice)
1395 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1397 self.logger.warning("Must specify either --user or --slice <hrn>")
1399 delegated_cred = Credential(string=cred)
1400 object_hrn = delegated_cred.get_gid_object().get_hrn()
1401 if options.delegate_user:
1402 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1403 + get_leaf(object_hrn) + ".cred")
1404 elif options.delegate_slice:
1405 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1406 + get_leaf(object_hrn) + ".cred")
1408 delegated_cred.save_to_file(dest_fn, save_parents=True)
1410 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1412 def trusted(self, options, args):
1414 return uhe trusted certs at this interface (get_trusted_certs)
1416 trusted_certs = self.registry().get_trusted_certs()
1417 for trusted_cert in trusted_certs:
1418 gid = GID(string=trusted_cert)
1420 cert = Certificate(string=trusted_cert)
1421 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1424 def config (self, options, args):
1425 "Display contents of current config"