2 # sfi.py - basic SFA command-line client
3 # the actual binary in sfa/clientbin essentially runs main()
4 # this module is used in sfascan
17 from lxml import etree
18 from StringIO import StringIO
19 from optparse import OptionParser
20 from pprint import PrettyPrinter
22 from sfa.trust.certificate import Keypair, Certificate
23 from sfa.trust.gid import GID
24 from sfa.trust.credential import Credential
25 from sfa.trust.sfaticket import SfaTicket
27 from sfa.util.faults import SfaInvalidArgument
28 from sfa.util.sfalogging import sfi_logger
29 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
30 from sfa.util.config import Config
31 from sfa.util.version import version_core
32 from sfa.util.cache import Cache
34 from sfa.storage.record import Record
36 from sfa.rspecs.rspec import RSpec
37 from sfa.rspecs.rspec_converter import RSpecConverter
38 from sfa.rspecs.version_manager import VersionManager
40 from sfa.client.sfaclientlib import SfaClientBootstrap
41 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
42 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
43 from sfa.client.return_value import ReturnValue
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
117 def save_raw_to_file(var, filename, format="text", banner=None):
119 # if filename is "-", send it to stdout
122 f = open(filename, "w")
127 elif format == "pickled":
128 f.write(pickle.dumps(var))
129 elif format == "json":
130 if hasattr(json, "dumps"):
131 f.write(json.dumps(var)) # python 2.6
133 f.write(json.write(var)) # python 2.5
135 # this should never happen
136 print "unknown output format", format
138 f.write('\n'+banner+"\n")
140 def save_rspec_to_file(rspec, filename):
141 if not filename.endswith(".rspec"):
142 filename = filename + ".rspec"
143 f = open(filename, 'w')
148 def save_records_to_file(filename, record_dicts, format="xml"):
151 for record_dict in record_dicts:
153 save_record_to_file(filename + "." + str(index), record_dict)
155 save_record_to_file(filename, record_dict)
157 elif format == "xmllist":
158 f = open(filename, "w")
159 f.write("<recordlist>\n")
160 for record_dict in record_dicts:
161 record_obj=Record(dict=record_dict)
162 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
163 f.write("</recordlist>\n")
165 elif format == "hrnlist":
166 f = open(filename, "w")
167 for record_dict in record_dicts:
168 record_obj=Record(dict=record_dict)
169 f.write(record_obj.hrn + "\n")
172 # this should never happen
173 print "unknown output format", format
175 def save_record_to_file(filename, record_dict):
176 record = Record(dict=record_dict)
177 xml = record.save_as_xml()
178 f=codecs.open(filename, encoding='utf-8',mode="w")
183 # minimally check a key argument
184 def check_ssh_key (key):
185 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
186 return re.match(good_ssh_key, key, re.IGNORECASE)
189 def load_record_from_opts(options):
191 if hasattr(options, 'xrn') and options.xrn:
192 if hasattr(options, 'type') and options.type:
193 xrn = Xrn(options.xrn, options.type)
195 xrn = Xrn(options.xrn)
196 record_dict['urn'] = xrn.get_urn()
197 record_dict['hrn'] = xrn.get_hrn()
198 record_dict['type'] = xrn.get_type()
199 if hasattr(options, 'key') and options.key:
201 pubkey = open(options.key, 'r').read()
204 if not check_ssh_key (pubkey):
205 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
206 record_dict['keys'] = [pubkey]
207 if hasattr(options, 'slices') and options.slices:
208 record_dict['slices'] = options.slices
209 if hasattr(options, 'researchers') and options.researchers:
210 record_dict['researcher'] = options.researchers
211 if hasattr(options, 'email') and options.email:
212 record_dict['email'] = options.email
213 if hasattr(options, 'pis') and options.pis:
214 record_dict['pi'] = options.pis
216 # handle extra settings
217 record_dict.update(options.extras)
219 return Record(dict=record_dict)
221 def load_record_from_file(filename):
222 f=codecs.open(filename, encoding="utf-8", mode="r")
223 xml_string = f.read()
225 return Record(xml=xml_string)
229 def unique_call_id(): return uuid.uuid4().urn
233 # dirty hack to make this class usable from the outside
234 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
237 def default_sfi_dir ():
238 if os.path.isfile("./sfi_config"):
241 return os.path.expanduser("~/.sfi/")
243 # dummy to meet Sfi's expectations for its 'options' field
244 # i.e. s/t we can do setattr on
248 def __init__ (self,options=None):
249 if options is None: options=Sfi.DummyOptions()
250 for opt in Sfi.required_options:
251 if not hasattr(options,opt): setattr(options,opt,None)
252 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
253 self.options = options
255 self.authority = None
256 self.logger = sfi_logger
257 self.logger.enable_console()
258 self.available_names = [ tuple[0] for tuple in Sfi.available ]
259 self.available_dict = dict (Sfi.available)
261 # tuples command-name expected-args in the order in which they should appear in the help
264 ("list", "authority"),
267 ("update", "record"),
270 ("resources", "[slice_hrn]"),
271 ("create", "slice_hrn rspec"),
272 ("delete", "slice_hrn"),
273 ("status", "slice_hrn"),
274 ("start", "slice_hrn"),
275 ("stop", "slice_hrn"),
276 ("reset", "slice_hrn"),
277 ("renew", "slice_hrn time"),
278 ("shutdown", "slice_hrn"),
279 ("get_ticket", "slice_hrn rspec"),
280 ("redeem_ticket", "ticket"),
281 ("delegate", "name"),
282 ("create_gid", "[name]"),
283 ("get_trusted_certs", "cred"),
287 def print_command_help (self, options):
288 verbose=getattr(options,'verbose')
289 format3="%18s %-15s %s"
292 print format3%("command","cmd_args","description")
296 self.create_parser().print_help()
297 for command in self.available_names:
298 args=self.available_dict[command]
299 method=getattr(self,command,None)
301 if method: doc=getattr(method,'__doc__',"")
302 if not doc: doc="*** no doc found ***"
303 doc=doc.strip(" \t\n")
304 doc=doc.replace("\n","\n"+35*' ')
307 print format3%(command,args,doc)
309 self.create_command_parser(command).print_help()
311 def create_command_parser(self, command):
312 if command not in self.available_dict:
313 msg="Invalid command\n"
315 msg += ','.join(self.available_names)
316 self.logger.critical(msg)
319 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
320 % (command, self.available_dict[command]))
322 if command in ("add", "update"):
323 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
324 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
325 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
326 # use --extra instead
327 # parser.add_option('-u', '--url', dest='url', metavar='<url>', default=None, help="URL, useful for slices")
328 # parser.add_option('-d', '--description', dest='description', metavar='<description>',
329 # help='Description, useful for slices', default=None)
330 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
332 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='slice xrns',
333 default='', type="str", action='callback', callback=optparse_listvalue_callback)
334 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
335 help='slice researchers', default='', type="str", action='callback',
336 callback=optparse_listvalue_callback)
337 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Principal Investigators/Project Managers',
338 default='', type="str", action='callback', callback=optparse_listvalue_callback)
339 # use --extra instead
340 # parser.add_option('-f', '--firstname', dest='firstname', metavar='<firstname>', help='user first name')
341 # parser.add_option('-l', '--lastname', dest='lastname', metavar='<lastname>', help='user last name')
342 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
343 action="callback", callback=optparse_dictvalue_callback, nargs=1,
344 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
346 # user specifies remote aggregate/sm/component
347 if command in ("resources", "slices", "create", "delete", "start", "stop",
348 "restart", "shutdown", "get_ticket", "renew", "status"):
349 parser.add_option("-d", "--delegate", dest="delegate", default=None,
351 help="Include a credential delegated to the user's root"+\
352 "authority in set of credentials for this call")
354 # registy filter option
355 if command in ("list", "show", "remove"):
356 parser.add_option("-t", "--type", dest="type", type="choice",
357 help="type filter ([all]|user|slice|authority|node|aggregate)",
358 choices=("all", "user", "slice", "authority", "node", "aggregate"),
360 if command in ("resources"):
362 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
363 help="schema type and version of resulting RSpec")
364 # disable/enable cached rspecs
365 parser.add_option("-c", "--current", dest="current", default=False,
367 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
369 parser.add_option("-f", "--format", dest="format", type="choice",
370 help="display format ([xml]|dns|ip)", default="xml",
371 choices=("xml", "dns", "ip"))
372 #panos: a new option to define the type of information about resources a user is interested in
373 parser.add_option("-i", "--info", dest="info",
374 help="optional component information", default=None)
377 # 'create' does return the new rspec, makes sense to save that too
378 if command in ("resources", "show", "list", "create_gid", 'create'):
379 parser.add_option("-o", "--output", dest="file",
380 help="output XML to file", metavar="FILE", default=None)
382 if command in ("show", "list"):
383 parser.add_option("-f", "--format", dest="format", type="choice",
384 help="display format ([text]|xml)", default="text",
385 choices=("text", "xml"))
387 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
388 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
389 choices=("xml", "xmllist", "hrnlist"))
390 if command == 'list':
391 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
392 help="list all child records", default=False)
393 if command in ("delegate"):
394 parser.add_option("-u", "--user",
395 action="store_true", dest="delegate_user", default=False,
396 help="delegate user credential")
397 parser.add_option("-s", "--slice", dest="delegate_slice",
398 help="delegate slice credential", metavar="HRN", default=None)
400 if command in ("version"):
401 parser.add_option("-R","--registry-version",
402 action="store_true", dest="version_registry", default=False,
403 help="probe registry version instead of sliceapi")
404 parser.add_option("-l","--local",
405 action="store_true", dest="version_local", default=False,
406 help="display version of the local client")
411 def create_parser(self):
413 # Generate command line parser
414 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
415 description="Commands: %s"%(" ".join(self.available_names)))
416 parser.add_option("-r", "--registry", dest="registry",
417 help="root registry", metavar="URL", default=None)
418 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
419 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
420 parser.add_option("-R", "--raw", dest="raw", default=None,
421 help="Save raw, unparsed server response to a file")
422 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
423 help="raw file format ([text]|pickled|json)", default="text",
424 choices=("text","pickled","json"))
425 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
426 help="text string to write before and after raw output")
427 parser.add_option("-d", "--dir", dest="sfi_dir",
428 help="config & working directory - default is %default",
429 metavar="PATH", default=Sfi.default_sfi_dir())
430 parser.add_option("-u", "--user", dest="user",
431 help="user name", metavar="HRN", default=None)
432 parser.add_option("-a", "--auth", dest="auth",
433 help="authority name", metavar="HRN", default=None)
434 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
435 help="verbose mode - cumulative")
436 parser.add_option("-D", "--debug",
437 action="store_true", dest="debug", default=False,
438 help="Debug (xml-rpc) protocol messages")
439 # would it make sense to use ~/.ssh/id_rsa as a default here ?
440 parser.add_option("-k", "--private-key",
441 action="store", dest="user_private_key", default=None,
442 help="point to the private key file to use if not yet installed in sfi_dir")
443 parser.add_option("-t", "--timeout", dest="timeout", default=None,
444 help="Amout of time to wait before timing out the request")
445 parser.add_option("-?", "--commands",
446 action="store_true", dest="command_help", default=False,
447 help="one page summary on commands & exit")
448 parser.disable_interspersed_args()
453 def print_help (self):
454 self.sfi_parser.print_help()
455 self.command_parser.print_help()
458 # Main: parse arguments and dispatch to command
460 def dispatch(self, command, command_options, command_args):
461 return getattr(self, command)(command_options, command_args)
464 self.sfi_parser = self.create_parser()
465 (options, args) = self.sfi_parser.parse_args()
466 if options.command_help:
467 self.print_command_help(options)
469 self.options = options
471 self.logger.setLevelFromOptVerbose(self.options.verbose)
474 self.logger.critical("No command given. Use -h for help.")
475 self.print_command_help(options)
479 self.command_parser = self.create_command_parser(command)
480 (command_options, command_args) = self.command_parser.parse_args(args[1:])
481 self.command_options = command_options
485 self.logger.info("Command=%s" % command)
488 self.dispatch(command, command_options, command_args)
490 self.logger.critical ("Unknown command %s"%command)
497 def read_config(self):
498 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
500 config = Config (config_file)
502 self.logger.critical("Failed to read configuration file %s"%config_file)
503 self.logger.info("Make sure to remove the export clauses and to add quotes")
504 if self.options.verbose==0:
505 self.logger.info("Re-run with -v for more details")
507 self.logger.log_exc("Could not read config file %s"%config_file)
512 if (self.options.sm is not None):
513 self.sm_url = self.options.sm
514 elif hasattr(config, "SFI_SM"):
515 self.sm_url = config.SFI_SM
517 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
521 if (self.options.registry is not None):
522 self.reg_url = self.options.registry
523 elif hasattr(config, "SFI_REGISTRY"):
524 self.reg_url = config.SFI_REGISTRY
526 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
530 if (self.options.user is not None):
531 self.user = self.options.user
532 elif hasattr(config, "SFI_USER"):
533 self.user = config.SFI_USER
535 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
539 if (self.options.auth is not None):
540 self.authority = self.options.auth
541 elif hasattr(config, "SFI_AUTH"):
542 self.authority = config.SFI_AUTH
544 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
547 self.config_file=config_file
551 def show_config (self):
552 print "From configuration file %s"%self.config_file
555 ('SFI_AUTH','authority'),
557 ('SFI_REGISTRY','reg_url'),
559 for (external_name, internal_name) in flags:
560 print "%s='%s'"%(external_name,getattr(self,internal_name))
563 # Get various credential and spec files
565 # Establishes limiting conventions
566 # - conflates MAs and SAs
567 # - assumes last token in slice name is unique
569 # Bootstraps credentials
570 # - bootstrap user credential from self-signed certificate
571 # - bootstrap authority credential from user credential
572 # - bootstrap slice credential from user credential
575 # init self-signed cert, user credentials and gid
576 def bootstrap (self):
577 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
578 # if -k is provided, use this to initialize private key
579 if self.options.user_private_key:
580 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
582 # trigger legacy compat code if needed
583 # the name has changed from just <leaf>.pkey to <hrn>.pkey
584 if not os.path.isfile(client_bootstrap.private_key_filename()):
585 self.logger.info ("private key not found, trying legacy name")
587 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
588 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
589 client_bootstrap.init_private_key_if_missing (legacy_private_key)
590 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
592 self.logger.log_exc("Can't find private key ")
596 client_bootstrap.bootstrap_my_gid()
597 # extract what's needed
598 self.private_key = client_bootstrap.private_key()
599 self.my_credential_string = client_bootstrap.my_credential_string ()
600 self.my_gid = client_bootstrap.my_gid ()
601 self.client_bootstrap = client_bootstrap
604 def my_authority_credential_string(self):
605 if not self.authority:
606 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
608 return self.client_bootstrap.authority_credential_string (self.authority)
610 def slice_credential_string(self, name):
611 return self.client_bootstrap.slice_credential_string (name)
613 # xxx should be supported by sfaclientbootstrap as well
614 def delegate_cred(self, object_cred, hrn, type='authority'):
615 # the gid and hrn of the object we are delegating
616 if isinstance(object_cred, str):
617 object_cred = Credential(string=object_cred)
618 object_gid = object_cred.get_gid_object()
619 object_hrn = object_gid.get_hrn()
621 if not object_cred.get_privileges().get_all_delegate():
622 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
625 # the delegating user's gid
626 caller_gidfile = self.my_gid()
628 # the gid of the user who will be delegated to
629 delegee_gid = self.client_bootstrap.gid(hrn,type)
630 delegee_hrn = delegee_gid.get_hrn()
631 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
632 return dcred.save_to_string(save_parents=True)
635 # Management of the servers
640 if not hasattr (self, 'registry_proxy'):
641 self.logger.info("Contacting Registry at: %s"%self.reg_url)
642 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
643 timeout=self.options.timeout, verbose=self.options.debug)
644 return self.registry_proxy
648 if not hasattr (self, 'sliceapi_proxy'):
649 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
650 if hasattr(self.command_options,'component') and self.command_options.component:
651 # resolve the hrn at the registry
652 node_hrn = self.command_options.component
653 records = self.registry().Resolve(node_hrn, self.my_credential_string)
654 records = filter_records('node', records)
656 self.logger.warning("No such component:%r"% opts.component)
658 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
659 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
661 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
662 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
663 self.sm_url = 'http://' + self.sm_url
664 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
665 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
666 timeout=self.options.timeout, verbose=self.options.debug)
667 return self.sliceapi_proxy
669 def get_cached_server_version(self, server):
670 # check local cache first
673 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
674 cache_key = server.url + "-version"
676 cache = Cache(cache_file)
679 self.logger.info("Local cache not found at: %s" % cache_file)
682 version = cache.get(cache_key)
685 result = server.GetVersion()
686 version= ReturnValue.get_value(result)
687 # cache version for 20 minutes
688 cache.add(cache_key, version, ttl= 60*20)
689 self.logger.info("Updating cache file %s" % cache_file)
690 cache.save_to_file(cache_file)
694 ### resurrect this temporarily so we can support V1 aggregates for a while
695 def server_supports_options_arg(self, server):
697 Returns true if server support the optional call_id arg, false otherwise.
699 server_version = self.get_cached_server_version(server)
701 # xxx need to rewrite this
702 if int(server_version.get('geni_api')) >= 2:
706 def server_supports_call_id_arg(self, server):
707 server_version = self.get_cached_server_version(server)
709 if 'sfa' in server_version and 'code_tag' in server_version:
710 code_tag = server_version['code_tag']
711 code_tag_parts = code_tag.split("-")
712 version_parts = code_tag_parts[0].split(".")
713 major, minor = version_parts[0], version_parts[1]
714 rev = code_tag_parts[1]
715 if int(major) == 1 and minor == 0 and build >= 22:
719 ### ois = options if supported
720 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
721 def ois (self, server, option_dict):
722 if self.server_supports_options_arg (server):
724 elif self.server_supports_call_id_arg (server):
725 return [ unique_call_id () ]
729 ### cis = call_id if supported - like ois
730 def cis (self, server):
731 if self.server_supports_call_id_arg (server):
732 return [ unique_call_id ]
736 ######################################## miscell utilities
737 def get_rspec_file(self, rspec):
738 if (os.path.isabs(rspec)):
741 file = os.path.join(self.options.sfi_dir, rspec)
742 if (os.path.isfile(file)):
745 self.logger.critical("No such rspec file %s"%rspec)
748 def get_record_file(self, record):
749 if (os.path.isabs(record)):
752 file = os.path.join(self.options.sfi_dir, record)
753 if (os.path.isfile(file)):
756 self.logger.critical("No such registry record file %s"%record)
760 #==========================================================================
761 # Following functions implement the commands
763 # Registry-related commands
764 #==========================================================================
766 def version(self, options, args):
768 display an SFA server version (GetVersion)
769 or version information about sfi itself
771 if options.version_local:
772 version=version_core()
774 if options.version_registry:
775 server=self.registry()
777 server = self.sliceapi()
778 result = server.GetVersion()
779 version = ReturnValue.get_value(result)
781 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
783 pprinter = PrettyPrinter(indent=4)
784 pprinter.pprint(version)
786 def list(self, options, args):
788 list entries in named authority registry (List)
795 if options.recursive:
796 opts['recursive'] = options.recursive
799 list = self.registry().List(hrn, self.my_credential_string, options)
801 raise Exception, "Not enough parameters for the 'list' command"
803 # filter on person, slice, site, node, etc.
804 # THis really should be in the self.filter_records funct def comment...
805 list = filter_records(options.type, list)
807 print "%s (%s)" % (record['hrn'], record['type'])
809 save_records_to_file(options.file, list, options.fileformat)
812 def show(self, options, args):
814 show details about named registry record (Resolve)
820 record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
821 record_dicts = filter_records(options.type, record_dicts)
823 self.logger.error("No record of type %s"% options.type)
824 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
825 for record in records:
826 if (options.format == "text"): record.dump(sort=True)
827 else: print record.save_as_xml()
829 save_records_to_file(options.file, record_dicts, options.fileformat)
832 def add(self, options, args):
833 "add record into registry from xml file (Register)"
834 auth_cred = self.my_authority_credential_string()
837 record_filepath = args[0]
838 rec_file = self.get_record_file(record_filepath)
839 record_dict.update(load_record_from_file(rec_file).todict())
841 record_dict.update(load_record_from_opts(options).todict())
842 # we should have a type by now
843 if 'type' not in record_dict :
846 # this is still planetlab dependent.. as plc will whine without that
847 # also, it's only for adding
848 if record_dict['type'] == 'user':
849 if not 'first_name' in record_dict:
850 record_dict['first_name'] = record_dict['hrn']
851 if 'last_name' not in record_dict:
852 record_dict['last_name'] = record_dict['hrn']
853 return self.registry().Register(record_dict, auth_cred)
855 def update(self, options, args):
856 "update record into registry from xml file (Update)"
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 record_dict.update(load_record_from_opts(options).todict())
868 record = Record(dict=record_dict)
869 if record.type == "user":
870 if record.hrn == self.user:
871 cred = self.my_credential_string
873 cred = self.my_authority_credential_string()
874 elif record.type in ["slice"]:
876 cred = self.slice_credential_string(record.hrn)
877 except ServerException, e:
878 # XXX smbaker -- once we have better error return codes, update this
879 # to do something better than a string compare
880 if "Permission error" in e.args[0]:
881 cred = self.my_authority_credential_string()
884 elif record.type in ["authority"]:
885 cred = self.my_authority_credential_string()
886 elif record.type == 'node':
887 cred = self.my_authority_credential_string()
889 raise "unknown record type" + record.type
890 record_dict = record.todict()
891 return self.registry().Update(record_dict, cred)
893 def remove(self, options, args):
894 "remove registry record by name (Remove)"
895 auth_cred = self.my_authority_credential_string()
903 return self.registry().Remove(hrn, auth_cred, type)
905 # ==================================================================
906 # Slice-related commands
907 # ==================================================================
909 def slices(self, options, args):
910 "list instantiated slices (ListSlices) - returns urn's"
911 server = self.sliceapi()
913 creds = [self.my_credential_string]
915 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
916 creds.append(delegated_cred)
917 # options and call_id when supported
919 api_options['call_id']=unique_call_id()
920 result = server.ListSlices(creds, *self.ois(server,api_options))
921 value = ReturnValue.get_value(result)
923 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
928 # show rspec for named slice
929 def resources(self, options, args):
931 with no arg, discover available resources, (ListResources)
932 or with an slice hrn, shows currently provisioned resources
934 server = self.sliceapi()
939 creds.append(self.slice_credential_string(args[0]))
941 creds.append(self.my_credential_string)
943 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
945 # no need to check if server accepts the options argument since the options has
946 # been a required argument since v1 API
948 # always send call_id to v2 servers
949 api_options ['call_id'] = unique_call_id()
950 # ask for cached value if available
951 api_options ['cached'] = True
954 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
956 api_options['info'] = options.info
958 if options.current == True:
959 api_options['cached'] = False
961 api_options['cached'] = True
962 if options.rspec_version:
963 version_manager = VersionManager()
964 server_version = self.get_cached_server_version(server)
965 if 'sfa' in server_version:
966 # just request the version the client wants
967 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
969 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
971 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
972 result = server.ListResources (creds, api_options)
973 value = ReturnValue.get_value(result)
975 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
976 if options.file is not None:
977 save_rspec_to_file(value, options.file)
978 if (self.options.raw is None) and (options.file is None):
979 display_rspec(value, options.format)
983 def create(self, options, args):
985 create or update named slice with given rspec
987 server = self.sliceapi()
989 # xxx do we need to check usage (len(args)) ?
992 slice_urn = hrn_to_urn(slice_hrn, 'slice')
995 creds = [self.slice_credential_string(slice_hrn)]
996 delegated_cred = None
997 server_version = self.get_cached_server_version(server)
998 if server_version.get('interface') == 'slicemgr':
999 # delegate our cred to the slice manager
1000 # do not delegate cred to slicemgr...not working at the moment
1002 #if server_version.get('hrn'):
1003 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1004 #elif server_version.get('urn'):
1005 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1008 rspec_file = self.get_rspec_file(args[1])
1009 rspec = open(rspec_file).read()
1012 # need to pass along user keys to the aggregate.
1014 # { urn: urn:publicid:IDN+emulab.net+user+alice
1015 # keys: [<ssh key A>, <ssh key B>]
1018 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1019 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1020 slice_record = slice_records[0]
1021 user_hrns = slice_record['researcher']
1022 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1023 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1025 if 'sfa' not in server_version:
1026 users = pg_users_arg(user_records)
1027 rspec = RSpec(rspec)
1028 rspec.filter({'component_manager_id': server_version['urn']})
1029 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1031 users = sfa_users_arg(user_records, slice_record)
1033 # do not append users, keys, or slice tags. Anything
1034 # not contained in this request will be removed from the slice
1036 # CreateSliver has supported the options argument for a while now so it should
1037 # be safe to assume this server support it
1039 api_options ['append'] = False
1040 api_options ['call_id'] = unique_call_id()
1041 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1042 value = ReturnValue.get_value(result)
1043 if self.options.raw:
1044 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1045 if options.file is not None:
1046 save_rspec_to_file (value, options.file)
1047 if (self.options.raw is None) and (options.file is None):
1052 def delete(self, options, args):
1054 delete named slice (DeleteSliver)
1056 server = self.sliceapi()
1060 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1063 slice_cred = self.slice_credential_string(slice_hrn)
1064 creds = [slice_cred]
1065 if options.delegate:
1066 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1067 creds.append(delegated_cred)
1069 # options and call_id when supported
1071 api_options ['call_id'] = unique_call_id()
1072 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1073 value = ReturnValue.get_value(result)
1074 if self.options.raw:
1075 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1080 def status(self, options, args):
1082 retrieve slice status (SliverStatus)
1084 server = self.sliceapi()
1088 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1091 slice_cred = self.slice_credential_string(slice_hrn)
1092 creds = [slice_cred]
1093 if options.delegate:
1094 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1095 creds.append(delegated_cred)
1097 # options and call_id when supported
1099 api_options['call_id']=unique_call_id()
1100 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1101 value = ReturnValue.get_value(result)
1102 if self.options.raw:
1103 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1107 def start(self, options, args):
1109 start named slice (Start)
1111 server = self.sliceapi()
1115 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1118 slice_cred = self.slice_credential_string(args[0])
1119 creds = [slice_cred]
1120 if options.delegate:
1121 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1122 creds.append(delegated_cred)
1123 # xxx Thierry - does this not need an api_options as well ?
1124 result = server.Start(slice_urn, creds)
1125 value = ReturnValue.get_value(result)
1126 if self.options.raw:
1127 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1132 def stop(self, options, args):
1134 stop named slice (Stop)
1136 server = self.sliceapi()
1139 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1141 slice_cred = self.slice_credential_string(args[0])
1142 creds = [slice_cred]
1143 if options.delegate:
1144 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1145 creds.append(delegated_cred)
1146 result = server.Stop(slice_urn, creds)
1147 value = ReturnValue.get_value(result)
1148 if self.options.raw:
1149 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1155 def reset(self, options, args):
1157 reset named slice (reset_slice)
1159 server = self.sliceapi()
1162 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1164 slice_cred = self.slice_credential_string(args[0])
1165 creds = [slice_cred]
1166 if options.delegate:
1167 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1168 creds.append(delegated_cred)
1169 result = server.reset_slice(creds, slice_urn)
1170 value = ReturnValue.get_value(result)
1171 if self.options.raw:
1172 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1177 def renew(self, options, args):
1179 renew slice (RenewSliver)
1181 server = self.sliceapi()
1184 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)
1193 # options and call_id when supported
1195 api_options['call_id']=unique_call_id()
1196 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1197 value = ReturnValue.get_value(result)
1198 if self.options.raw:
1199 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1205 def shutdown(self, options, args):
1207 shutdown named slice (Shutdown)
1209 server = self.sliceapi()
1212 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1214 slice_cred = self.slice_credential_string(slice_hrn)
1215 creds = [slice_cred]
1216 if options.delegate:
1217 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1218 creds.append(delegated_cred)
1219 result = server.Shutdown(slice_urn, creds)
1220 value = ReturnValue.get_value(result)
1221 if self.options.raw:
1222 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1228 def get_ticket(self, options, args):
1230 get a ticket for the specified slice
1232 server = self.sliceapi()
1234 slice_hrn, rspec_path = args[0], args[1]
1235 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1237 slice_cred = self.slice_credential_string(slice_hrn)
1238 creds = [slice_cred]
1239 if options.delegate:
1240 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1241 creds.append(delegated_cred)
1243 rspec_file = self.get_rspec_file(rspec_path)
1244 rspec = open(rspec_file).read()
1245 # options and call_id when supported
1247 api_options['call_id']=unique_call_id()
1248 # get ticket at the server
1249 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1251 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1252 self.logger.info("writing ticket to %s"%file)
1253 ticket = SfaTicket(string=ticket_string)
1254 ticket.save_to_file(filename=file, save_parents=True)
1256 def redeem_ticket(self, options, args):
1258 Connects to nodes in a slice and redeems a ticket
1259 (slice hrn is retrieved from the ticket)
1261 ticket_file = args[0]
1263 # get slice hrn from the ticket
1264 # use this to get the right slice credential
1265 ticket = SfaTicket(filename=ticket_file)
1267 ticket_string = ticket.save_to_string(save_parents=True)
1269 slice_hrn = ticket.gidObject.get_hrn()
1270 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1271 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1272 slice_cred = self.slice_credential_string(slice_hrn)
1274 # get a list of node hostnames from the RSpec
1275 tree = etree.parse(StringIO(ticket.rspec))
1276 root = tree.getroot()
1277 hostnames = root.xpath("./network/site/node/hostname/text()")
1279 # create an xmlrpc connection to the component manager at each of these
1280 # components and gall redeem_ticket
1282 for hostname in hostnames:
1284 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1285 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1286 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1287 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1288 timeout=self.options.timeout, verbose=self.options.debug)
1289 server.RedeemTicket(ticket_string, slice_cred)
1290 self.logger.info("Success")
1291 except socket.gaierror:
1292 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1293 except Exception, e:
1294 self.logger.log_exc(e.message)
1297 def create_gid(self, options, args):
1299 Create a GID (CreateGid)
1304 target_hrn = args[0]
1305 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1307 filename = options.file
1309 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1310 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1311 GID(string=gid).save_to_file(filename)
1314 def delegate(self, options, args):
1316 (locally) create delegate credential for use by given hrn
1318 delegee_hrn = args[0]
1319 if options.delegate_user:
1320 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1321 elif options.delegate_slice:
1322 slice_cred = self.slice_credential_string(options.delegate_slice)
1323 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1325 self.logger.warning("Must specify either --user or --slice <hrn>")
1327 delegated_cred = Credential(string=cred)
1328 object_hrn = delegated_cred.get_gid_object().get_hrn()
1329 if options.delegate_user:
1330 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1331 + get_leaf(object_hrn) + ".cred")
1332 elif options.delegate_slice:
1333 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1334 + get_leaf(object_hrn) + ".cred")
1336 delegated_cred.save_to_file(dest_fn, save_parents=True)
1338 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1340 def get_trusted_certs(self, options, args):
1342 return uhe trusted certs at this interface (get_trusted_certs)
1344 trusted_certs = self.registry().get_trusted_certs()
1345 for trusted_cert in trusted_certs:
1346 gid = GID(string=trusted_cert)
1348 cert = Certificate(string=trusted_cert)
1349 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1352 def config (self, options, args):
1353 "Display contents of current config"