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)
498 self.logger.log_exc ("sfi command %s failed"%command)
504 def read_config(self):
505 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
506 shell_config_file = os.path.join(self.options.sfi_dir,"sfi_config.sh")
508 if Config.is_ini(config_file):
509 config = Config (config_file)
511 # try upgrading from shell config format
512 fp, fn = mkstemp(suffix='sfi_config', text=True)
514 # we need to preload the sections we want parsed
515 # from the shell config
516 config.add_section('sfi')
517 config.add_section('sface')
518 config.load(config_file)
520 shutil.move(config_file, shell_config_file)
522 config.save(config_file)
525 self.logger.critical("Failed to read configuration file %s"%config_file)
526 self.logger.info("Make sure to remove the export clauses and to add quotes")
527 if self.options.verbose==0:
528 self.logger.info("Re-run with -v for more details")
530 self.logger.log_exc("Could not read config file %s"%config_file)
535 if (self.options.sm is not None):
536 self.sm_url = self.options.sm
537 elif hasattr(config, "SFI_SM"):
538 self.sm_url = config.SFI_SM
540 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
544 if (self.options.registry is not None):
545 self.reg_url = self.options.registry
546 elif hasattr(config, "SFI_REGISTRY"):
547 self.reg_url = config.SFI_REGISTRY
549 self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
553 if (self.options.user is not None):
554 self.user = self.options.user
555 elif hasattr(config, "SFI_USER"):
556 self.user = config.SFI_USER
558 self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
562 if (self.options.auth is not None):
563 self.authority = self.options.auth
564 elif hasattr(config, "SFI_AUTH"):
565 self.authority = config.SFI_AUTH
567 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
570 self.config_file=config_file
574 def show_config (self):
575 print "From configuration file %s"%self.config_file
578 ('SFI_AUTH','authority'),
580 ('SFI_REGISTRY','reg_url'),
582 for (external_name, internal_name) in flags:
583 print "%s='%s'"%(external_name,getattr(self,internal_name))
586 # Get various credential and spec files
588 # Establishes limiting conventions
589 # - conflates MAs and SAs
590 # - assumes last token in slice name is unique
592 # Bootstraps credentials
593 # - bootstrap user credential from self-signed certificate
594 # - bootstrap authority credential from user credential
595 # - bootstrap slice credential from user credential
598 # init self-signed cert, user credentials and gid
599 def bootstrap (self):
600 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
602 # if -k is provided, use this to initialize private key
603 if self.options.user_private_key:
604 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
606 # trigger legacy compat code if needed
607 # the name has changed from just <leaf>.pkey to <hrn>.pkey
608 if not os.path.isfile(client_bootstrap.private_key_filename()):
609 self.logger.info ("private key not found, trying legacy name")
611 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
612 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
613 client_bootstrap.init_private_key_if_missing (legacy_private_key)
614 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
616 self.logger.log_exc("Can't find private key ")
620 client_bootstrap.bootstrap_my_gid()
621 # extract what's needed
622 self.private_key = client_bootstrap.private_key()
623 self.my_credential_string = client_bootstrap.my_credential_string ()
624 self.my_gid = client_bootstrap.my_gid ()
625 self.client_bootstrap = client_bootstrap
628 def my_authority_credential_string(self):
629 if not self.authority:
630 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
632 return self.client_bootstrap.authority_credential_string (self.authority)
634 def authority_credential_string(self, auth_hrn):
635 return self.client_bootstrap.authority_credential_string (auth_hrn)
637 def slice_credential_string(self, name):
638 return self.client_bootstrap.slice_credential_string (name)
641 # Management of the servers
646 if not hasattr (self, 'registry_proxy'):
647 self.logger.info("Contacting Registry at: %s"%self.reg_url)
648 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
649 timeout=self.options.timeout, verbose=self.options.debug)
650 return self.registry_proxy
654 if not hasattr (self, 'sliceapi_proxy'):
655 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
656 if hasattr(self.command_options,'component') and self.command_options.component:
657 # resolve the hrn at the registry
658 node_hrn = self.command_options.component
659 records = self.registry().Resolve(node_hrn, self.my_credential_string)
660 records = filter_records('node', records)
662 self.logger.warning("No such component:%r"% opts.component)
664 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
665 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
667 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
668 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
669 self.sm_url = 'http://' + self.sm_url
670 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
671 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
672 timeout=self.options.timeout, verbose=self.options.debug)
673 return self.sliceapi_proxy
675 def get_cached_server_version(self, server):
676 # check local cache first
679 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
680 cache_key = server.url + "-version"
682 cache = Cache(cache_file)
685 self.logger.info("Local cache not found at: %s" % cache_file)
688 version = cache.get(cache_key)
691 result = server.GetVersion()
692 version= ReturnValue.get_value(result)
693 # cache version for 20 minutes
694 cache.add(cache_key, version, ttl= 60*20)
695 self.logger.info("Updating cache file %s" % cache_file)
696 cache.save_to_file(cache_file)
700 ### resurrect this temporarily so we can support V1 aggregates for a while
701 def server_supports_options_arg(self, server):
703 Returns true if server support the optional call_id arg, false otherwise.
705 server_version = self.get_cached_server_version(server)
707 # xxx need to rewrite this
708 if int(server_version.get('geni_api')) >= 2:
712 def server_supports_call_id_arg(self, server):
713 server_version = self.get_cached_server_version(server)
715 if 'sfa' in server_version and 'code_tag' in server_version:
716 code_tag = server_version['code_tag']
717 code_tag_parts = code_tag.split("-")
718 version_parts = code_tag_parts[0].split(".")
719 major, minor = version_parts[0], version_parts[1]
720 rev = code_tag_parts[1]
721 if int(major) == 1 and minor == 0 and build >= 22:
725 ### ois = options if supported
726 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
727 def ois (self, server, option_dict):
728 if self.server_supports_options_arg (server):
730 elif self.server_supports_call_id_arg (server):
731 return [ unique_call_id () ]
735 ### cis = call_id if supported - like ois
736 def cis (self, server):
737 if self.server_supports_call_id_arg (server):
738 return [ unique_call_id ]
742 ######################################## miscell utilities
743 def get_rspec_file(self, rspec):
744 if (os.path.isabs(rspec)):
747 file = os.path.join(self.options.sfi_dir, rspec)
748 if (os.path.isfile(file)):
751 self.logger.critical("No such rspec file %s"%rspec)
754 def get_record_file(self, record):
755 if (os.path.isabs(record)):
758 file = os.path.join(self.options.sfi_dir, record)
759 if (os.path.isfile(file)):
762 self.logger.critical("No such registry record file %s"%record)
766 #==========================================================================
767 # Following functions implement the commands
769 # Registry-related commands
770 #==========================================================================
772 def version(self, options, args):
774 display an SFA server version (GetVersion)
775 or version information about sfi itself
777 if options.version_local:
778 version=version_core()
780 if options.version_registry:
781 server=self.registry()
783 server = self.sliceapi()
784 result = server.GetVersion()
785 version = ReturnValue.get_value(result)
787 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
789 pprinter = PrettyPrinter(indent=4)
790 pprinter.pprint(version)
792 def list(self, options, args):
794 list entries in named authority registry (List)
801 if options.recursive:
802 opts['recursive'] = options.recursive
804 if options.show_credential:
805 show_credentials(self.my_credential_string)
807 list = self.registry().List(hrn, self.my_credential_string, options)
809 raise Exception, "Not enough parameters for the 'list' command"
811 # filter on person, slice, site, node, etc.
812 # This really should be in the self.filter_records funct def comment...
813 list = filter_records(options.type, list)
814 terminal_render (list, options)
816 save_records_to_file(options.file, list, options.fileformat)
819 def show(self, options, args):
821 show details about named registry record (Resolve)
827 # explicitly require Resolve to run in details mode
828 record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
829 record_dicts = filter_records(options.type, record_dicts)
831 self.logger.error("No record of type %s"% options.type)
833 # user has required to focus on some keys
835 def project (record):
837 for key in options.keys:
838 try: projected[key]=record[key]
841 record_dicts = [ project (record) for record in record_dicts ]
842 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
843 for record in records:
844 if (options.format == "text"): record.dump(sort=True)
845 else: print record.save_as_xml()
847 save_records_to_file(options.file, record_dicts, options.fileformat)
850 def add(self, options, args):
851 "add record into registry by using the command options (Recommended) or from xml file (Register)"
852 auth_cred = self.my_authority_credential_string()
853 if options.show_credential:
854 show_credentials(auth_cred)
861 record_filepath = args[0]
862 rec_file = self.get_record_file(record_filepath)
863 record_dict.update(load_record_from_file(rec_file).todict())
865 print "Cannot load record file %s"%record_filepath
868 record_dict.update(load_record_from_opts(options).todict())
869 # we should have a type by now
870 if 'type' not in record_dict :
873 # this is still planetlab dependent.. as plc will whine without that
874 # also, it's only for adding
875 if record_dict['type'] == 'user':
876 if not 'first_name' in record_dict:
877 record_dict['first_name'] = record_dict['hrn']
878 if 'last_name' not in record_dict:
879 record_dict['last_name'] = record_dict['hrn']
880 return self.registry().Register(record_dict, auth_cred)
882 def update(self, options, args):
883 "update record into registry by using the command options (Recommended) or from xml file (Update)"
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 # at the very least we need 'type' here
892 if 'type' not in record_dict:
896 # don't translate into an object, as this would possibly distort
897 # user-provided data; e.g. add an 'email' field to Users
898 if record_dict['type'] == "user":
899 if record_dict['hrn'] == self.user:
900 cred = self.my_credential_string
902 cred = self.my_authority_credential_string()
903 elif record_dict['type'] in ["slice"]:
905 cred = self.slice_credential_string(record_dict['hrn'])
906 except ServerException, e:
907 # XXX smbaker -- once we have better error return codes, update this
908 # to do something better than a string compare
909 if "Permission error" in e.args[0]:
910 cred = self.my_authority_credential_string()
913 elif record_dict['type'] in ["authority"]:
914 cred = self.my_authority_credential_string()
915 elif record_dict['type'] == 'node':
916 cred = self.my_authority_credential_string()
918 raise "unknown record type" + record_dict['type']
919 if options.show_credential:
920 show_credentials(cred)
921 return self.registry().Update(record_dict, cred)
923 def remove(self, options, args):
924 "remove registry record by name (Remove)"
925 auth_cred = self.my_authority_credential_string()
933 if options.show_credential:
934 show_credentials(auth_cred)
935 return self.registry().Remove(hrn, auth_cred, type)
937 # ==================================================================
938 # Slice-related commands
939 # ==================================================================
941 def slices(self, options, args):
942 "list instantiated slices (ListSlices) - returns urn's"
943 server = self.sliceapi()
945 creds = [self.my_credential_string]
946 # options and call_id when supported
948 api_options['call_id']=unique_call_id()
949 if options.show_credential:
950 show_credentials(creds)
951 result = server.ListSlices(creds, *self.ois(server,api_options))
952 value = ReturnValue.get_value(result)
954 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
959 # show rspec for named slice
960 def resources(self, options, args):
962 with no arg, discover available resources, (ListResources)
963 or with an slice hrn, shows currently provisioned resources
965 server = self.sliceapi()
970 the_credential=self.slice_credential_string(args[0])
971 creds.append(the_credential)
973 the_credential=self.my_credential_string
974 creds.append(the_credential)
975 if options.show_credential:
976 show_credentials(creds)
978 # no need to check if server accepts the options argument since the options has
979 # been a required argument since v1 API
981 # always send call_id to v2 servers
982 api_options ['call_id'] = unique_call_id()
983 # ask for cached value if available
984 api_options ['cached'] = True
987 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
989 api_options['info'] = options.info
990 if options.list_leases:
991 api_options['list_leases'] = options.list_leases
993 if options.current == True:
994 api_options['cached'] = False
996 api_options['cached'] = True
997 if options.rspec_version:
998 version_manager = VersionManager()
999 server_version = self.get_cached_server_version(server)
1000 if 'sfa' in server_version:
1001 # just request the version the client wants
1002 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1004 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1006 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1007 result = server.ListResources (creds, api_options)
1008 value = ReturnValue.get_value(result)
1009 if self.options.raw:
1010 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1011 if options.file is not None:
1012 save_rspec_to_file(value, options.file)
1013 if (self.options.raw is None) and (options.file is None):
1014 display_rspec(value, options.format)
1018 def create(self, options, args):
1020 create or update named slice with given rspec
1022 server = self.sliceapi()
1024 # xxx do we need to check usage (len(args)) ?
1027 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1030 creds = [self.slice_credential_string(slice_hrn)]
1032 delegated_cred = None
1033 server_version = self.get_cached_server_version(server)
1034 if server_version.get('interface') == 'slicemgr':
1035 # delegate our cred to the slice manager
1036 # do not delegate cred to slicemgr...not working at the moment
1038 #if server_version.get('hrn'):
1039 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1040 #elif server_version.get('urn'):
1041 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1043 if options.show_credential:
1044 show_credentials(creds)
1047 rspec_file = self.get_rspec_file(args[1])
1048 rspec = open(rspec_file).read()
1051 # need to pass along user keys to the aggregate.
1053 # { urn: urn:publicid:IDN+emulab.net+user+alice
1054 # keys: [<ssh key A>, <ssh key B>]
1057 # xxx Thierry 2012 sept. 21
1058 # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here
1059 # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again
1060 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1061 # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True})
1062 if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']:
1063 slice_record = slice_records[0]
1064 user_hrns = slice_record['reg-researchers']
1065 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1066 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1068 if 'sfa' not in server_version:
1069 users = pg_users_arg(user_records)
1070 rspec = RSpec(rspec)
1071 rspec.filter({'component_manager_id': server_version['urn']})
1072 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1074 users = sfa_users_arg(user_records, slice_record)
1076 # do not append users, keys, or slice tags. Anything
1077 # not contained in this request will be removed from the slice
1079 # CreateSliver has supported the options argument for a while now so it should
1080 # be safe to assume this server support it
1082 api_options ['append'] = False
1083 api_options ['call_id'] = unique_call_id()
1084 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1085 value = ReturnValue.get_value(result)
1086 if self.options.raw:
1087 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1088 if options.file is not None:
1089 save_rspec_to_file (value, options.file)
1090 if (self.options.raw is None) and (options.file is None):
1095 def delete(self, options, args):
1097 delete named slice (DeleteSliver)
1099 server = self.sliceapi()
1103 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1106 slice_cred = self.slice_credential_string(slice_hrn)
1107 creds = [slice_cred]
1109 # options and call_id when supported
1111 api_options ['call_id'] = unique_call_id()
1112 if options.show_credential:
1113 show_credentials(creds)
1114 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1115 value = ReturnValue.get_value(result)
1116 if self.options.raw:
1117 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1122 def status(self, options, args):
1124 retrieve slice status (SliverStatus)
1126 server = self.sliceapi()
1130 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1133 slice_cred = self.slice_credential_string(slice_hrn)
1134 creds = [slice_cred]
1136 # options and call_id when supported
1138 api_options['call_id']=unique_call_id()
1139 if options.show_credential:
1140 show_credentials(creds)
1141 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1142 value = ReturnValue.get_value(result)
1143 if self.options.raw:
1144 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1148 def start(self, options, args):
1150 start named slice (Start)
1152 server = self.sliceapi()
1156 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1159 slice_cred = self.slice_credential_string(args[0])
1160 creds = [slice_cred]
1161 # xxx Thierry - does this not need an api_options as well ?
1162 result = server.Start(slice_urn, creds)
1163 value = ReturnValue.get_value(result)
1164 if self.options.raw:
1165 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1170 def stop(self, options, args):
1172 stop named slice (Stop)
1174 server = self.sliceapi()
1177 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1179 slice_cred = self.slice_credential_string(args[0])
1180 creds = [slice_cred]
1181 result = server.Stop(slice_urn, creds)
1182 value = ReturnValue.get_value(result)
1183 if self.options.raw:
1184 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1190 def reset(self, options, args):
1192 reset named slice (reset_slice)
1194 server = self.sliceapi()
1197 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1199 slice_cred = self.slice_credential_string(args[0])
1200 creds = [slice_cred]
1201 result = server.reset_slice(creds, slice_urn)
1202 value = ReturnValue.get_value(result)
1203 if self.options.raw:
1204 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1209 def renew(self, options, args):
1211 renew slice (RenewSliver)
1213 server = self.sliceapi()
1217 [ slice_hrn, input_time ] = args
1219 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1220 # time: don't try to be smart on the time format, server-side will
1222 slice_cred = self.slice_credential_string(args[0])
1223 creds = [slice_cred]
1224 # options and call_id when supported
1226 api_options['call_id']=unique_call_id()
1227 if options.show_credential:
1228 show_credentials(creds)
1229 result = server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1230 value = ReturnValue.get_value(result)
1231 if self.options.raw:
1232 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1238 def shutdown(self, options, args):
1240 shutdown named slice (Shutdown)
1242 server = self.sliceapi()
1245 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1247 slice_cred = self.slice_credential_string(slice_hrn)
1248 creds = [slice_cred]
1249 result = server.Shutdown(slice_urn, creds)
1250 value = ReturnValue.get_value(result)
1251 if self.options.raw:
1252 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1258 def get_ticket(self, options, args):
1260 get a ticket for the specified slice
1262 server = self.sliceapi()
1264 slice_hrn, rspec_path = args[0], args[1]
1265 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1267 slice_cred = self.slice_credential_string(slice_hrn)
1268 creds = [slice_cred]
1270 rspec_file = self.get_rspec_file(rspec_path)
1271 rspec = open(rspec_file).read()
1272 # options and call_id when supported
1274 api_options['call_id']=unique_call_id()
1275 # get ticket at the server
1276 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1278 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1279 self.logger.info("writing ticket to %s"%file)
1280 ticket = SfaTicket(string=ticket_string)
1281 ticket.save_to_file(filename=file, save_parents=True)
1283 def redeem_ticket(self, options, args):
1285 Connects to nodes in a slice and redeems a ticket
1286 (slice hrn is retrieved from the ticket)
1288 ticket_file = args[0]
1290 # get slice hrn from the ticket
1291 # use this to get the right slice credential
1292 ticket = SfaTicket(filename=ticket_file)
1294 ticket_string = ticket.save_to_string(save_parents=True)
1296 slice_hrn = ticket.gidObject.get_hrn()
1297 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1298 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1299 slice_cred = self.slice_credential_string(slice_hrn)
1301 # get a list of node hostnames from the RSpec
1302 tree = etree.parse(StringIO(ticket.rspec))
1303 root = tree.getroot()
1304 hostnames = root.xpath("./network/site/node/hostname/text()")
1306 # create an xmlrpc connection to the component manager at each of these
1307 # components and gall redeem_ticket
1309 for hostname in hostnames:
1311 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1312 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1313 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1314 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1315 timeout=self.options.timeout, verbose=self.options.debug)
1316 server.RedeemTicket(ticket_string, slice_cred)
1317 self.logger.info("Success")
1318 except socket.gaierror:
1319 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1320 except Exception, e:
1321 self.logger.log_exc(e.message)
1324 def gid(self, options, args):
1326 Create a GID (CreateGid)
1331 target_hrn = args[0]
1332 my_gid_string = open(self.client_bootstrap.my_gid()).read()
1333 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1335 filename = options.file
1337 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1338 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1339 GID(string=gid).save_to_file(filename)
1342 def delegate (self, options, args):
1344 (locally) create delegate credential for use by given hrn
1350 # support for several delegations in the same call
1351 # so first we gather the things to do
1353 for slice_hrn in options.delegate_slices:
1354 message="%s.slice"%slice_hrn
1355 original = self.slice_credential_string(slice_hrn)
1356 tuples.append ( (message, original,) )
1357 if options.delegate_pi:
1358 my_authority=self.authority
1359 message="%s.pi"%my_authority
1360 original = self.my_authority_credential_string()
1361 tuples.append ( (message, original,) )
1362 for auth_hrn in options.delegate_auths:
1363 message="%s.auth"%auth_hrn
1364 original=self.authority_credential_string(auth_hrn)
1365 tuples.append ( (message, original, ) )
1366 # if nothing was specified at all at this point, let's assume -u
1367 if not tuples: options.delegate_user=True
1369 if options.delegate_user:
1370 message="%s.user"%self.user
1371 original = self.my_credential_string
1372 tuples.append ( (message, original, ) )
1374 # default type for beneficial is user unless -A
1375 if options.delegate_to_authority: to_type='authority'
1376 else: to_type='user'
1378 # let's now handle all this
1379 # it's all in the filenaming scheme
1380 for (message,original) in tuples:
1381 delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1382 delegated_credential = Credential (string=delegated_string)
1383 filename = os.path.join ( self.options.sfi_dir,
1384 "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1385 delegated_credential.save_to_file(filename, save_parents=True)
1386 self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1388 def trusted(self, options, args):
1390 return uhe trusted certs at this interface (get_trusted_certs)
1392 trusted_certs = self.registry().get_trusted_certs()
1393 for trusted_cert in trusted_certs:
1394 gid = GID(string=trusted_cert)
1396 cert = Certificate(string=trusted_cert)
1397 self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1400 def config (self, options, args):
1401 "Display contents of current config"