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 credential_printable (credential_string):
89 credential=Credential(string=credential_string)
91 result += credential.get_summary_tostring()
93 rights = credential.get_privileges()
94 result += "rights=%s"%rights
98 def show_credentials (cred_s):
99 if not isinstance (cred_s,list): cred_s = [cred_s]
101 print "Using Credential %s"%credential_printable(cred)
104 def save_raw_to_file(var, filename, format="text", banner=None):
106 # if filename is "-", send it to stdout
109 f = open(filename, "w")
114 elif format == "pickled":
115 f.write(pickle.dumps(var))
116 elif format == "json":
117 if hasattr(json, "dumps"):
118 f.write(json.dumps(var)) # python 2.6
120 f.write(json.write(var)) # python 2.5
122 # this should never happen
123 print "unknown output format", format
125 f.write('\n'+banner+"\n")
127 def save_rspec_to_file(rspec, filename):
128 if not filename.endswith(".rspec"):
129 filename = filename + ".rspec"
130 f = open(filename, 'w')
135 def save_records_to_file(filename, record_dicts, format="xml"):
138 for record_dict in record_dicts:
140 save_record_to_file(filename + "." + str(index), record_dict)
142 save_record_to_file(filename, record_dict)
144 elif format == "xmllist":
145 f = open(filename, "w")
146 f.write("<recordlist>\n")
147 for record_dict in record_dicts:
148 record_obj=Record(dict=record_dict)
149 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
150 f.write("</recordlist>\n")
152 elif format == "hrnlist":
153 f = open(filename, "w")
154 for record_dict in record_dicts:
155 record_obj=Record(dict=record_dict)
156 f.write(record_obj.hrn + "\n")
159 # this should never happen
160 print "unknown output format", format
162 def save_record_to_file(filename, record_dict):
163 record = Record(dict=record_dict)
164 xml = record.save_as_xml()
165 f=codecs.open(filename, encoding='utf-8',mode="w")
170 # minimally check a key argument
171 def check_ssh_key (key):
172 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
173 return re.match(good_ssh_key, key, re.IGNORECASE)
176 def load_record_from_opts(options):
178 if hasattr(options, 'xrn') and options.xrn:
179 if hasattr(options, 'type') and options.type:
180 xrn = Xrn(options.xrn, options.type)
182 xrn = Xrn(options.xrn)
183 record_dict['urn'] = xrn.get_urn()
184 record_dict['hrn'] = xrn.get_hrn()
185 record_dict['type'] = xrn.get_type()
186 if hasattr(options, 'key') and options.key:
188 pubkey = open(options.key, 'r').read()
191 if not check_ssh_key (pubkey):
192 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
193 record_dict['keys'] = [pubkey]
194 if hasattr(options, 'slices') and options.slices:
195 record_dict['slices'] = options.slices
196 if hasattr(options, 'researchers') and options.researchers:
197 record_dict['researcher'] = options.researchers
198 if hasattr(options, 'email') and options.email:
199 record_dict['email'] = options.email
200 if hasattr(options, 'pis') and options.pis:
201 record_dict['pi'] = options.pis
203 # handle extra settings
204 record_dict.update(options.extras)
206 return Record(dict=record_dict)
208 def load_record_from_file(filename):
209 f=codecs.open(filename, encoding="utf-8", mode="r")
210 xml_string = f.read()
212 return Record(xml=xml_string)
216 def unique_call_id(): return uuid.uuid4().urn
220 # dirty hack to make this class usable from the outside
221 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
224 def default_sfi_dir ():
225 if os.path.isfile("./sfi_config"):
228 return os.path.expanduser("~/.sfi/")
230 # dummy to meet Sfi's expectations for its 'options' field
231 # i.e. s/t we can do setattr on
235 def __init__ (self,options=None):
236 if options is None: options=Sfi.DummyOptions()
237 for opt in Sfi.required_options:
238 if not hasattr(options,opt): setattr(options,opt,None)
239 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
240 self.options = options
242 self.authority = None
243 self.logger = sfi_logger
244 self.logger.enable_console()
245 self.available_names = [ tuple[0] for tuple in Sfi.available ]
246 self.available_dict = dict (Sfi.available)
248 # tuples command-name expected-args in the order in which they should appear in the help
251 ("list", "authority"),
254 ("update", "[record]"),
257 ("resources", "[slice_hrn]"),
258 ("create", "slice_hrn rspec"),
259 ("delete", "slice_hrn"),
260 ("status", "slice_hrn"),
261 ("start", "slice_hrn"),
262 ("stop", "slice_hrn"),
263 ("reset", "slice_hrn"),
264 ("renew", "slice_hrn time"),
265 ("shutdown", "slice_hrn"),
266 ("get_ticket", "slice_hrn rspec"),
267 ("redeem_ticket", "ticket"),
268 ("delegate", "to_hrn"),
274 def print_command_help (self, options):
275 verbose=getattr(options,'verbose')
276 format3="%18s %-15s %s"
279 print format3%("command","cmd_args","description")
283 self.create_parser().print_help()
284 for command in self.available_names:
285 args=self.available_dict[command]
286 method=getattr(self,command,None)
288 if method: doc=getattr(method,'__doc__',"")
289 if not doc: doc="*** no doc found ***"
290 doc=doc.strip(" \t\n")
291 doc=doc.replace("\n","\n"+35*' ')
294 print format3%(command,args,doc)
296 self.create_command_parser(command).print_help()
298 def create_command_parser(self, command):
299 if command not in self.available_dict:
300 msg="Invalid command\n"
302 msg += ','.join(self.available_names)
303 self.logger.critical(msg)
306 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
307 % (command, self.available_dict[command]))
309 if command in ("add", "update"):
310 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
311 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
312 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
313 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
315 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
316 default='', type="str", action='callback', callback=optparse_listvalue_callback)
317 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
318 help='Set/replace slice researchers', default='', type="str", action='callback',
319 callback=optparse_listvalue_callback)
320 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
321 default='', type="str", action='callback', callback=optparse_listvalue_callback)
322 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
323 action="callback", callback=optparse_dictvalue_callback, nargs=1,
324 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
326 # show_credential option
327 if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
328 parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
329 help="show credential(s) used in human-readable form")
330 # registy filter option
331 if command in ("list", "show", "remove"):
332 parser.add_option("-t", "--type", dest="type", type="choice",
333 help="type filter ([all]|user|slice|authority|node|aggregate)",
334 choices=("all", "user", "slice", "authority", "node", "aggregate"),
336 if command in ("show"):
337 parser.add_option("-k","--key",dest="keys",action="append",default=[],
338 help="specify specific keys to be displayed from record")
339 if command in ("resources"):
341 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
342 help="schema type and version of resulting RSpec")
343 # disable/enable cached rspecs
344 parser.add_option("-c", "--current", dest="current", default=False,
346 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
348 parser.add_option("-f", "--format", dest="format", type="choice",
349 help="display format ([xml]|dns|ip)", default="xml",
350 choices=("xml", "dns", "ip"))
351 #panos: a new option to define the type of information about resources a user is interested in
352 parser.add_option("-i", "--info", dest="info",
353 help="optional component information", default=None)
354 # a new option to retreive or not reservation-oriented RSpecs (leases)
355 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
356 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
357 choices=("all", "resources", "leases"), default="resources")
360 # 'create' does return the new rspec, makes sense to save that too
361 if command in ("resources", "show", "list", "gid", 'create'):
362 parser.add_option("-o", "--output", dest="file",
363 help="output XML to file", metavar="FILE", default=None)
365 if command in ("show", "list"):
366 parser.add_option("-f", "--format", dest="format", type="choice",
367 help="display format ([text]|xml)", default="text",
368 choices=("text", "xml"))
370 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
371 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
372 choices=("xml", "xmllist", "hrnlist"))
373 if command == 'list':
374 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
375 help="list all child records", default=False)
376 parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
377 help="gives details, like user keys", default=False)
378 if command in ("delegate"):
379 parser.add_option("-u", "--user",
380 action="store_true", dest="delegate_user", default=False,
381 help="delegate your own credentials; default if no other option is provided")
382 parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
383 metavar="slice_hrn", help="delegate cred. for slice HRN")
384 parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
385 metavar='auth_hrn', help="delegate cred for auth HRN")
386 # this primarily is a shorthand for -a my_hrn^
387 parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
388 help="delegate your PI credentials, so s.t. like -a your_hrn^")
389 parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
390 help="""by default the mandatory argument is expected to be a user,
391 use this if you mean an authority instead""")
393 if command in ("version"):
394 parser.add_option("-R","--registry-version",
395 action="store_true", dest="version_registry", default=False,
396 help="probe registry version instead of sliceapi")
397 parser.add_option("-l","--local",
398 action="store_true", dest="version_local", default=False,
399 help="display version of the local client")
404 def create_parser(self):
406 # Generate command line parser
407 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
408 description="Commands: %s"%(" ".join(self.available_names)))
409 parser.add_option("-r", "--registry", dest="registry",
410 help="root registry", metavar="URL", default=None)
411 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
412 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
413 parser.add_option("-R", "--raw", dest="raw", default=None,
414 help="Save raw, unparsed server response to a file")
415 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
416 help="raw file format ([text]|pickled|json)", default="text",
417 choices=("text","pickled","json"))
418 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
419 help="text string to write before and after raw output")
420 parser.add_option("-d", "--dir", dest="sfi_dir",
421 help="config & working directory - default is %default",
422 metavar="PATH", default=Sfi.default_sfi_dir())
423 parser.add_option("-u", "--user", dest="user",
424 help="user name", metavar="HRN", default=None)
425 parser.add_option("-a", "--auth", dest="auth",
426 help="authority name", metavar="HRN", default=None)
427 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
428 help="verbose mode - cumulative")
429 parser.add_option("-D", "--debug",
430 action="store_true", dest="debug", default=False,
431 help="Debug (xml-rpc) protocol messages")
432 # would it make sense to use ~/.ssh/id_rsa as a default here ?
433 parser.add_option("-k", "--private-key",
434 action="store", dest="user_private_key", default=None,
435 help="point to the private key file to use if not yet installed in sfi_dir")
436 parser.add_option("-t", "--timeout", dest="timeout", default=None,
437 help="Amout of time to wait before timing out the request")
438 parser.add_option("-?", "--commands",
439 action="store_true", dest="command_help", default=False,
440 help="one page summary on commands & exit")
441 parser.disable_interspersed_args()
446 def print_help (self):
447 print "==================== Generic sfi usage"
448 self.sfi_parser.print_help()
449 print "==================== Specific command usage"
450 self.command_parser.print_help()
453 # Main: parse arguments and dispatch to command
455 def dispatch(self, command, command_options, command_args):
456 method=getattr(self, command,None)
458 print "Unknown command %s"%command
460 return method(command_options, command_args)
463 self.sfi_parser = self.create_parser()
464 (options, args) = self.sfi_parser.parse_args()
465 if options.command_help:
466 self.print_command_help(options)
468 self.options = options
470 self.logger.setLevelFromOptVerbose(self.options.verbose)
473 self.logger.critical("No command given. Use -h for help.")
474 self.print_command_help(options)
477 # complete / find unique match with command set
478 command_candidates = Candidates (self.available_names)
480 command = command_candidates.only_match(input)
482 self.print_command_help(options)
484 # second pass options parsing
485 self.command_parser = self.create_command_parser(command)
486 (command_options, command_args) = self.command_parser.parse_args(args[1:])
487 self.command_options = command_options
491 self.logger.debug("Command=%s" % command)
494 self.dispatch(command, command_options, command_args)
496 self.logger.log_exc ("sfi command %s failed"%command)
502 def read_config(self):
503 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
504 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
506 if Config.is_ini(config_file):
507 config = Config (config_file)
509 # try upgrading from shell config format
510 fp, fn = mkstemp(suffix='sfi_config', text=True)
512 # we need to preload the sections we want parsed
513 # from the shell config
514 config.add_section('sfi')
515 config.add_section('sface')
516 config.load(config_file)
518 shutil.move(config_file, shell_config_file)
520 config.save(config_file)
523 self.logger.critical("Failed to read configuration file %s"%config_file)
524 self.logger.info("Make sure to remove the export clauses and to add quotes")
525 if self.options.verbose==0:
526 self.logger.info("Re-run with -v for more details")
528 self.logger.log_exc("Could not read config file %s"%config_file)
533 if (self.options.sm is not None):
534 self.sm_url = self.options.sm
535 elif hasattr(config, "SFI_SM"):
536 self.sm_url = config.SFI_SM
538 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
542 if (self.options.registry is not None):
543 self.reg_url = self.options.registry
544 elif hasattr(config, "SFI_REGISTRY"):
545 self.reg_url = config.SFI_REGISTRY
547 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
551 if (self.options.user is not None):
552 self.user = self.options.user
553 elif hasattr(config, "SFI_USER"):
554 self.user = config.SFI_USER
556 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
560 if (self.options.auth is not None):
561 self.authority = self.options.auth
562 elif hasattr(config, "SFI_AUTH"):
563 self.authority = config.SFI_AUTH
565 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
568 self.config_file=config_file
572 def show_config (self):
573 print "From configuration file %s"%self.config_file
576 ('SFI_AUTH','authority'),
578 ('SFI_REGISTRY','reg_url'),
580 for (external_name, internal_name) in flags:
581 print "%s='%s'"%(external_name,getattr(self,internal_name))
584 # Get various credential and spec files
586 # Establishes limiting conventions
587 # - conflates MAs and SAs
588 # - assumes last token in slice name is unique
590 # Bootstraps credentials
591 # - bootstrap user credential from self-signed certificate
592 # - bootstrap authority credential from user credential
593 # - bootstrap slice credential from user credential
596 # init self-signed cert, user credentials and gid
597 def bootstrap (self):
598 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
600 # if -k is provided, use this to initialize private key
601 if self.options.user_private_key:
602 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
604 # trigger legacy compat code if needed
605 # the name has changed from just <leaf>.pkey to <hrn>.pkey
606 if not os.path.isfile(client_bootstrap.private_key_filename()):
607 self.logger.info ("private key not found, trying legacy name")
609 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
610 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
611 client_bootstrap.init_private_key_if_missing (legacy_private_key)
612 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
614 self.logger.log_exc("Can't find private key ")
618 client_bootstrap.bootstrap_my_gid()
619 # extract what's needed
620 self.private_key = client_bootstrap.private_key()
621 self.my_credential_string = client_bootstrap.my_credential_string ()
622 self.my_gid = client_bootstrap.my_gid ()
623 self.client_bootstrap = client_bootstrap
626 def my_authority_credential_string(self):
627 if not self.authority:
628 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
630 return self.client_bootstrap.authority_credential_string (self.authority)
632 def authority_credential_string(self, auth_hrn):
633 return self.client_bootstrap.authority_credential_string (auth_hrn)
635 def slice_credential_string(self, name):
636 return self.client_bootstrap.slice_credential_string (name)
639 # Management of the servers
644 if not hasattr (self, 'registry_proxy'):
645 self.logger.info("Contacting Registry at: %s"%self.reg_url)
646 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
647 timeout=self.options.timeout, verbose=self.options.debug)
648 return self.registry_proxy
652 if not hasattr (self, 'sliceapi_proxy'):
653 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
654 if hasattr(self.command_options,'component') and self.command_options.component:
655 # resolve the hrn at the registry
656 node_hrn = self.command_options.component
657 records = self.registry().Resolve(node_hrn, self.my_credential_string)
658 records = filter_records('node', records)
660 self.logger.warning("No such component:%r"% opts.component)
662 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
663 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
665 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
666 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
667 self.sm_url = 'http://' + self.sm_url
668 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
669 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
670 timeout=self.options.timeout, verbose=self.options.debug)
671 return self.sliceapi_proxy
673 def get_cached_server_version(self, server):
674 # check local cache first
677 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
678 cache_key = server.url + "-version"
680 cache = Cache(cache_file)
683 self.logger.info("Local cache not found at: %s" % cache_file)
686 version = cache.get(cache_key)
689 result = server.GetVersion()
690 version= ReturnValue.get_value(result)
691 # cache version for 20 minutes
692 cache.add(cache_key, version, ttl= 60*20)
693 self.logger.info("Updating cache file %s" % cache_file)
694 cache.save_to_file(cache_file)
698 ### resurrect this temporarily so we can support V1 aggregates for a while
699 def server_supports_options_arg(self, server):
701 Returns true if server support the optional call_id arg, false otherwise.
703 server_version = self.get_cached_server_version(server)
705 # xxx need to rewrite this
706 if int(server_version.get('geni_api')) >= 2:
710 def server_supports_call_id_arg(self, server):
711 server_version = self.get_cached_server_version(server)
713 if 'sfa' in server_version and 'code_tag' in server_version:
714 code_tag = server_version['code_tag']
715 code_tag_parts = code_tag.split("-")
716 version_parts = code_tag_parts[0].split(".")
717 major, minor = version_parts[0], version_parts[1]
718 rev = code_tag_parts[1]
719 if int(major) == 1 and minor == 0 and build >= 22:
723 ### ois = options if supported
724 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
725 def ois (self, server, option_dict):
726 if self.server_supports_options_arg (server):
728 elif self.server_supports_call_id_arg (server):
729 return [ unique_call_id () ]
733 ### cis = call_id if supported - like ois
734 def cis (self, server):
735 if self.server_supports_call_id_arg (server):
736 return [ unique_call_id ]
740 ######################################## miscell utilities
741 def get_rspec_file(self, rspec):
742 if (os.path.isabs(rspec)):
745 file = os.path.join(self.options.sfi_dir, rspec)
746 if (os.path.isfile(file)):
749 self.logger.critical("No such rspec file %s"%rspec)
752 def get_record_file(self, record):
753 if (os.path.isabs(record)):
756 file = os.path.join(self.options.sfi_dir, record)
757 if (os.path.isfile(file)):
760 self.logger.critical("No such registry record file %s"%record)
764 #==========================================================================
765 # Following functions implement the commands
767 # Registry-related commands
768 #==========================================================================
770 def version(self, options, args):
772 display an SFA server version (GetVersion)
773 or version information about sfi itself
775 if options.version_local:
776 version=version_core()
778 if options.version_registry:
779 server=self.registry()
781 server = self.sliceapi()
782 result = server.GetVersion()
783 version = ReturnValue.get_value(result)
785 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
787 pprinter = PrettyPrinter(indent=4)
788 pprinter.pprint(version)
790 def list(self, options, args):
792 list entries in named authority registry (List)
799 if options.recursive:
800 opts['recursive'] = options.recursive
802 if options.show_credential:
803 show_credentials(self.my_credential_string)
805 list = self.registry().List(hrn, self.my_credential_string, options)
807 raise Exception, "Not enough parameters for the 'list' command"
809 # filter on person, slice, site, node, etc.
810 # This really should be in the self.filter_records funct def comment...
811 list = filter_records(options.type, list)
812 terminal_render (list, options)
814 save_records_to_file(options.file, list, options.fileformat)
817 def show(self, options, args):
819 show details about named registry record (Resolve)
825 # explicitly require Resolve to run in details mode
826 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
827 record_dicts = filter_records(options.type, record_dicts)
829 self.logger.error("No record of type %s"% options.type)
831 # user has required to focus on some keys
833 def project (record):
835 for key in options.keys:
836 try: projected[key]=record[key]
839 record_dicts = [ project (record) for record in record_dicts ]
840 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
841 for record in records:
842 if (options.format == "text"): record.dump(sort=True)
843 else: print record.save_as_xml()
845 save_records_to_file(options.file, record_dicts, options.fileformat)
848 def add(self, options, args):
849 "add record into registry by using the command options (Recommended) or from xml file (Register)"
850 auth_cred = self.my_authority_credential_string()
851 if options.show_credential:
852 show_credentials(auth_cred)
859 record_filepath = args[0]
860 rec_file = self.get_record_file(record_filepath)
861 record_dict.update(load_record_from_file(rec_file).todict())
863 print "Cannot load record file %s"%record_filepath
866 record_dict.update(load_record_from_opts(options).todict())
867 # we should have a type by now
868 if 'type' not in record_dict :
871 # this is still planetlab dependent.. as plc will whine without that
872 # also, it's only for adding
873 if record_dict['type'] == 'user':
874 if not 'first_name' in record_dict:
875 record_dict['first_name'] = record_dict['hrn']
876 if 'last_name' not in record_dict:
877 record_dict['last_name'] = record_dict['hrn']
878 return self.registry().Register(record_dict, auth_cred)
880 def update(self, options, args):
881 "update record into registry by using the command options (Recommended) or from xml file (Update)"
884 record_filepath = args[0]
885 rec_file = self.get_record_file(record_filepath)
886 record_dict.update(load_record_from_file(rec_file).todict())
888 record_dict.update(load_record_from_opts(options).todict())
889 # at the very least we need 'type' here
890 if 'type' not in record_dict:
894 # don't translate into an object, as this would possibly distort
895 # user-provided data; e.g. add an 'email' field to Users
896 if record_dict['type'] == "user":
897 if record_dict['hrn'] == self.user:
898 cred = self.my_credential_string
900 cred = self.my_authority_credential_string()
901 elif record_dict['type'] in ["slice"]:
903 cred = self.slice_credential_string(record_dict['hrn'])
904 except ServerException, e:
905 # XXX smbaker -- once we have better error return codes, update this
906 # to do something better than a string compare
907 if "Permission error" in e.args[0]:
908 cred = self.my_authority_credential_string()
911 elif record_dict['type'] in ["authority"]:
912 cred = self.my_authority_credential_string()
913 elif record_dict['type'] == 'node':
914 cred = self.my_authority_credential_string()
916 raise "unknown record type" + record_dict['type']
917 if options.show_credential:
918 show_credentials(cred)
919 return self.registry().Update(record_dict, cred)
921 def remove(self, options, args):
922 "remove registry record by name (Remove)"
923 auth_cred = self.my_authority_credential_string()
931 if options.show_credential:
932 show_credentials(auth_cred)
933 return self.registry().Remove(hrn, auth_cred, type)
935 # ==================================================================
936 # Slice-related commands
937 # ==================================================================
939 def slices(self, options, args):
940 "list instantiated slices (ListSlices) - returns urn's"
941 server = self.sliceapi()
943 creds = [self.my_credential_string]
944 # options and call_id when supported
946 api_options['call_id']=unique_call_id()
947 if options.show_credential:
948 show_credentials(creds)
949 result = server.ListSlices(creds, *self.ois(server,api_options))
950 value = ReturnValue.get_value(result)
952 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
957 # show rspec for named slice
958 def resources(self, options, args):
960 with no arg, discover available resources, (ListResources)
961 or with an slice hrn, shows currently provisioned resources
963 server = self.sliceapi()
968 the_credential=self.slice_credential_string(args[0])
969 creds.append(the_credential)
971 the_credential=self.my_credential_string
972 creds.append(the_credential)
973 if options.show_credential:
974 show_credentials(creds)
976 # no need to check if server accepts the options argument since the options has
977 # been a required argument since v1 API
979 # always send call_id to v2 servers
980 api_options ['call_id'] = unique_call_id()
981 # ask for cached value if available
982 api_options ['cached'] = True
985 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
987 api_options['info'] = options.info
988 if options.list_leases:
989 api_options['list_leases'] = options.list_leases
991 if options.current == True:
992 api_options['cached'] = False
994 api_options['cached'] = True
995 if options.rspec_version:
996 version_manager = VersionManager()
997 server_version = self.get_cached_server_version(server)
998 if 'sfa' in server_version:
999 # just request the version the client wants
1000 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1002 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1004 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1005 result = server.ListResources (creds, api_options)
1006 value = ReturnValue.get_value(result)
1007 if self.options.raw:
1008 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1009 if options.file is not None:
1010 save_rspec_to_file(value, options.file)
1011 if (self.options.raw is None) and (options.file is None):
1012 display_rspec(value, options.format)
1016 def create(self, options, args):
1018 create or update named slice with given rspec
1020 server = self.sliceapi()
1022 # xxx do we need to check usage (len(args)) ?
1025 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1028 creds = [self.slice_credential_string(slice_hrn)]
1030 delegated_cred = None
1031 server_version = self.get_cached_server_version(server)
1032 if server_version.get('interface') == 'slicemgr':
1033 # delegate our cred to the slice manager
1034 # do not delegate cred to slicemgr...not working at the moment
1036 #if server_version.get('hrn'):
1037 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1038 #elif server_version.get('urn'):
1039 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1041 if options.show_credential:
1042 show_credentials(creds)
1045 rspec_file = self.get_rspec_file(args[1])
1046 rspec = open(rspec_file).read()
1049 # need to pass along user keys to the aggregate.
1051 # { urn: urn:publicid:IDN+emulab.net+user+alice
1052 # keys: [<ssh key A>, <ssh key B>]
1055 # xxx Thierry 2012 sept. 21
1056 # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here
1057 # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again
1058 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1059 # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True})
1060 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']:
1061 slice_record = slice_records[0]
1062 user_hrns = slice_record['reg-researchers']
1063 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1064 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1066 if 'sfa' not in server_version:
1067 users = pg_users_arg(user_records)
1068 rspec = RSpec(rspec)
1069 rspec.filter({'component_manager_id': server_version['urn']})
1070 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1072 users = sfa_users_arg(user_records, slice_record)
1074 # do not append users, keys, or slice tags. Anything
1075 # not contained in this request will be removed from the slice
1077 # CreateSliver has supported the options argument for a while now so it should
1078 # be safe to assume this server support it
1080 api_options ['append'] = False
1081 api_options ['call_id'] = unique_call_id()
1082 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1083 value = ReturnValue.get_value(result)
1084 if self.options.raw:
1085 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1086 if options.file is not None:
1087 save_rspec_to_file (value, options.file)
1088 if (self.options.raw is None) and (options.file is None):
1093 def delete(self, options, args):
1095 delete named slice (DeleteSliver)
1097 server = self.sliceapi()
1101 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1104 slice_cred = self.slice_credential_string(slice_hrn)
1105 creds = [slice_cred]
1107 # options and call_id when supported
1109 api_options ['call_id'] = unique_call_id()
1110 if options.show_credential:
1111 show_credentials(creds)
1112 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1113 value = ReturnValue.get_value(result)
1114 if self.options.raw:
1115 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1120 def status(self, options, args):
1122 retrieve slice status (SliverStatus)
1124 server = self.sliceapi()
1128 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1131 slice_cred = self.slice_credential_string(slice_hrn)
1132 creds = [slice_cred]
1134 # options and call_id when supported
1136 api_options['call_id']=unique_call_id()
1137 if options.show_credential:
1138 show_credentials(creds)
1139 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1140 value = ReturnValue.get_value(result)
1141 if self.options.raw:
1142 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1146 def start(self, options, args):
1148 start named slice (Start)
1150 server = self.sliceapi()
1154 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1157 slice_cred = self.slice_credential_string(args[0])
1158 creds = [slice_cred]
1159 # xxx Thierry - does this not need an api_options as well ?
1160 result = server.Start(slice_urn, creds)
1161 value = ReturnValue.get_value(result)
1162 if self.options.raw:
1163 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1168 def stop(self, options, args):
1170 stop named slice (Stop)
1172 server = self.sliceapi()
1175 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1177 slice_cred = self.slice_credential_string(args[0])
1178 creds = [slice_cred]
1179 result = server.Stop(slice_urn, creds)
1180 value = ReturnValue.get_value(result)
1181 if self.options.raw:
1182 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1188 def reset(self, options, args):
1190 reset named slice (reset_slice)
1192 server = self.sliceapi()
1195 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1197 slice_cred = self.slice_credential_string(args[0])
1198 creds = [slice_cred]
1199 result = server.reset_slice(creds, slice_urn)
1200 value = ReturnValue.get_value(result)
1201 if self.options.raw:
1202 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1207 def renew(self, options, args):
1209 renew slice (RenewSliver)
1211 server = self.sliceapi()
1215 [ slice_hrn, input_time ] = args
1217 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1218 # time: don't try to be smart on the time format, server-side will
1220 slice_cred = self.slice_credential_string(args[0])
1221 creds = [slice_cred]
1222 # options and call_id when supported
1224 api_options['call_id']=unique_call_id()
1225 if options.show_credential:
1226 show_credentials(creds)
1227 result = server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1228 value = ReturnValue.get_value(result)
1229 if self.options.raw:
1230 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1236 def shutdown(self, options, args):
1238 shutdown named slice (Shutdown)
1240 server = self.sliceapi()
1243 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1245 slice_cred = self.slice_credential_string(slice_hrn)
1246 creds = [slice_cred]
1247 result = server.Shutdown(slice_urn, creds)
1248 value = ReturnValue.get_value(result)
1249 if self.options.raw:
1250 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1256 def get_ticket(self, options, args):
1258 get a ticket for the specified slice
1260 server = self.sliceapi()
1262 slice_hrn, rspec_path = args[0], args[1]
1263 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1265 slice_cred = self.slice_credential_string(slice_hrn)
1266 creds = [slice_cred]
1268 rspec_file = self.get_rspec_file(rspec_path)
1269 rspec = open(rspec_file).read()
1270 # options and call_id when supported
1272 api_options['call_id']=unique_call_id()
1273 # get ticket at the server
1274 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1276 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1277 self.logger.info("writing ticket to %s"%file)
1278 ticket = SfaTicket(string=ticket_string)
1279 ticket.save_to_file(filename=file, save_parents=True)
1281 def redeem_ticket(self, options, args):
1283 Connects to nodes in a slice and redeems a ticket
1284 (slice hrn is retrieved from the ticket)
1286 ticket_file = args[0]
1288 # get slice hrn from the ticket
1289 # use this to get the right slice credential
1290 ticket = SfaTicket(filename=ticket_file)
1292 ticket_string = ticket.save_to_string(save_parents=True)
1294 slice_hrn = ticket.gidObject.get_hrn()
1295 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1296 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1297 slice_cred = self.slice_credential_string(slice_hrn)
1299 # get a list of node hostnames from the RSpec
1300 tree = etree.parse(StringIO(ticket.rspec))
1301 root = tree.getroot()
1302 hostnames = root.xpath("./network/site/node/hostname/text()")
1304 # create an xmlrpc connection to the component manager at each of these
1305 # components and gall redeem_ticket
1307 for hostname in hostnames:
1309 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1310 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1311 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1312 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1313 timeout=self.options.timeout, verbose=self.options.debug)
1314 server.RedeemTicket(ticket_string, slice_cred)
1315 self.logger.info("Success")
1316 except socket.gaierror:
1317 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1318 except Exception, e:
1319 self.logger.log_exc(e.message)
1322 def gid(self, options, args):
1324 Create a GID (CreateGid)
1329 target_hrn = args[0]
1330 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1331 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1333 filename = options.file
1335 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1336 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1337 GID(string=gid).save_to_file(filename)
1340 def delegate (self, options, args):
1342 (locally) create delegate credential for use by given hrn
1348 # support for several delegations in the same call
1349 # so first we gather the things to do
1351 for slice_hrn in options.delegate_slices:
1352 message="%s.slice"%slice_hrn
1353 original = self.slice_credential_string(slice_hrn)
1354 tuples.append ( (message, original,) )
1355 if options.delegate_pi:
1356 my_authority=self.authority
1357 message="%s.pi"%my_authority
1358 original = self.my_authority_credential_string()
1359 tuples.append ( (message, original,) )
1360 for auth_hrn in options.delegate_auths:
1361 message="%s.auth"%auth_hrn
1362 original=self.authority_credential_string(auth_hrn)
1363 tuples.append ( (message, original, ) )
1364 # if nothing was specified at all at this point, let's assume -u
1365 if not tuples: options.delegate_user=True
1367 if options.delegate_user:
1368 message="%s.user"%self.user
1369 original = self.my_credential_string
1370 tuples.append ( (message, original, ) )
1372 # default type for beneficial is user unless -A
1373 if options.delegate_to_authority: to_type='authority'
1374 else: to_type='user'
1376 # let's now handle all this
1377 # it's all in the filenaming scheme
1378 for (message,original) in tuples:
1379 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1380 delegated_credential = Credential (string=delegated_string)
1381 filename = os.path.join ( self.options.sfi_dir,
1382 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1383 delegated_credential.save_to_file(filename, save_parents=True)
1384 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1386 def trusted(self, options, args):
1388 return uhe trusted certs at this interface (get_trusted_certs)
1390 trusted_certs = self.registry().get_trusted_certs()
1391 for trusted_cert in trusted_certs:
1392 gid = GID(string=trusted_cert)
1394 cert = Certificate(string=trusted_cert)
1395 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1398 def config (self, options, args):
1399 "Display contents of current config"