2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
16 from lxml import etree
17 from StringIO import StringIO
18 from optparse import OptionParser
19 from pprint import PrettyPrinter
21 from sfa.trust.certificate import Keypair, Certificate
22 from sfa.trust.gid import GID
23 from sfa.trust.credential import Credential
24 from sfa.trust.sfaticket import SfaTicket
26 from sfa.util.faults import SfaInvalidArgument
27 from sfa.util.sfalogging import sfi_logger
28 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
29 from sfa.util.config import Config
30 from sfa.util.version import version_core
31 from sfa.util.cache import Cache
33 from sfa.storage.record import Record
35 from sfa.rspecs.rspec import RSpec
36 from sfa.rspecs.rspec_converter import RSpecConverter
37 from sfa.rspecs.version_manager import VersionManager
39 from sfa.client.sfaclientlib import SfaClientBootstrap
40 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
41 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
42 from sfa.client.return_value import ReturnValue
46 # utility methods here
47 def optparse_listvalue_callback(option, option_string, value, parser):
48 setattr(parser.values, option.dest, value.split(','))
50 # a code fragment that could be helpful for argparse which unfortunately is
51 # available with 2.7 only, so this feels like too strong a requirement for the client side
52 #class ExtraArgAction (argparse.Action):
53 # def __call__ (self, parser, namespace, values, option_string=None):
54 # would need a try/except of course
55 # (k,v)=values.split('=')
56 # d=getattr(namespace,self.dest)
59 #parser.add_argument ("-X","--extra",dest='extras', default={}, action=ExtraArgAction,
60 # help="set extra flags, testbed dependent, e.g. --extra enabled=true")
62 def optparse_dictvalue_callback (option, option_string, value, parser):
64 (k,v)=value.split('=',1)
65 d=getattr(parser.values, option.dest)
72 def display_rspec(rspec, format='rspec'):
74 tree = etree.parse(StringIO(rspec))
76 result = root.xpath("./network/site/node/hostname/text()")
77 elif format in ['ip']:
78 # The IP address is not yet part of the new RSpec
79 # so this doesn't do anything yet.
80 tree = etree.parse(StringIO(rspec))
82 result = root.xpath("./network/site/node/ipv4/text()")
89 def display_list(results):
90 for result in results:
93 def display_records(recordList, dump=False):
94 ''' Print all fields in the record'''
95 for record in recordList:
96 display_record(record, dump)
98 def display_record(record, dump=False):
100 record.dump(sort=True)
102 info = record.getdict()
103 print "%s (%s)" % (info['hrn'], info['type'])
107 def filter_records(type, records):
108 filtered_records = []
109 for record in records:
110 if (record['type'] == type) or (type == "all"):
111 filtered_records.append(record)
112 return filtered_records
116 def save_raw_to_file(var, filename, format="text", banner=None):
118 # if filename is "-", send it to stdout
121 f = open(filename, "w")
126 elif format == "pickled":
127 f.write(pickle.dumps(var))
128 elif format == "json":
129 if hasattr(json, "dumps"):
130 f.write(json.dumps(var)) # python 2.6
132 f.write(json.write(var)) # python 2.5
134 # this should never happen
135 print "unknown output format", format
137 f.write('\n'+banner+"\n")
139 def save_rspec_to_file(rspec, filename):
140 if not filename.endswith(".rspec"):
141 filename = filename + ".rspec"
142 f = open(filename, 'w')
147 def save_records_to_file(filename, record_dicts, format="xml"):
150 for record_dict in record_dicts:
152 save_record_to_file(filename + "." + str(index), record_dict)
154 save_record_to_file(filename, record_dict)
156 elif format == "xmllist":
157 f = open(filename, "w")
158 f.write("<recordlist>\n")
159 for record_dict in record_dicts:
160 record_obj=Record(dict=record_dict)
161 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
162 f.write("</recordlist>\n")
164 elif format == "hrnlist":
165 f = open(filename, "w")
166 for record_dict in record_dicts:
167 record_obj=Record(dict=record_dict)
168 f.write(record_obj.hrn + "\n")
171 # this should never happen
172 print "unknown output format", format
174 def save_record_to_file(filename, record_dict):
175 record = Record(dict=record_dict)
176 xml = record.save_as_xml()
177 f=codecs.open(filename, encoding='utf-8',mode="w")
182 # minimally check a key argument
183 def check_ssh_key (key):
184 good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
185 return re.match(good_ssh_key, key, re.IGNORECASE)
188 def load_record_from_opts(options):
190 if hasattr(options, 'xrn') and options.xrn:
191 if hasattr(options, 'type') and options.type:
192 xrn = Xrn(options.xrn, options.type)
194 xrn = Xrn(options.xrn)
195 record_dict['urn'] = xrn.get_urn()
196 record_dict['hrn'] = xrn.get_hrn()
197 record_dict['type'] = xrn.get_type()
198 if hasattr(options, 'key') and options.key:
200 pubkey = open(options.key, 'r').read()
203 if not check_ssh_key (pubkey):
204 raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
205 record_dict['keys'] = [pubkey]
206 if hasattr(options, 'slices') and options.slices:
207 record_dict['slices'] = options.slices
208 if hasattr(options, 'researchers') and options.researchers:
209 record_dict['researcher'] = options.researchers
210 if hasattr(options, 'email') and options.email:
211 record_dict['email'] = options.email
212 if hasattr(options, 'pis') and options.pis:
213 record_dict['pi'] = options.pis
215 # handle extra settings
216 record_dict.update(options.extras)
218 return Record(dict=record_dict)
220 def load_record_from_file(filename):
221 f=codecs.open(filename, encoding="utf-8", mode="r")
222 xml_string = f.read()
224 return Record(xml=xml_string)
228 def unique_call_id(): return uuid.uuid4().urn
232 # dirty hack to make this class usable from the outside
233 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
236 def default_sfi_dir ():
237 if os.path.isfile("./sfi_config"):
240 return os.path.expanduser("~/.sfi/")
242 # dummy to meet Sfi's expectations for its 'options' field
243 # i.e. s/t we can do setattr on
247 def __init__ (self,options=None):
248 if options is None: options=Sfi.DummyOptions()
249 for opt in Sfi.required_options:
250 if not hasattr(options,opt): setattr(options,opt,None)
251 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
252 self.options = options
254 self.authority = None
255 self.logger = sfi_logger
256 self.logger.enable_console()
257 self.available_names = [ tuple[0] for tuple in Sfi.available ]
258 self.available_dict = dict (Sfi.available)
260 # tuples command-name expected-args in the order in which they should appear in the help
263 ("list", "authority"),
266 ("update", "record"),
269 ("resources", "[slice_hrn]"),
270 ("create", "slice_hrn rspec"),
271 ("delete", "slice_hrn"),
272 ("status", "slice_hrn"),
273 ("start", "slice_hrn"),
274 ("stop", "slice_hrn"),
275 ("reset", "slice_hrn"),
276 ("renew", "slice_hrn time"),
277 ("shutdown", "slice_hrn"),
278 ("get_ticket", "slice_hrn rspec"),
279 ("redeem_ticket", "ticket"),
280 ("delegate", "name"),
281 ("create_gid", "[name]"),
282 ("get_trusted_certs", "cred"),
286 def print_command_help (self, options):
287 verbose=getattr(options,'verbose')
288 format3="%18s %-15s %s"
291 print format3%("command","cmd_args","description")
295 self.create_parser().print_help()
296 for command in self.available_names:
297 args=self.available_dict[command]
298 method=getattr(self,command,None)
300 if method: doc=getattr(method,'__doc__',"")
301 if not doc: doc="*** no doc found ***"
302 doc=doc.strip(" \t\n")
303 doc=doc.replace("\n","\n"+35*' ')
306 print format3%(command,args,doc)
308 self.create_command_parser(command).print_help()
310 def create_command_parser(self, command):
311 if command not in self.available_dict:
312 msg="Invalid command\n"
314 msg += ','.join(self.available_names)
315 self.logger.critical(msg)
318 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
319 % (command, self.available_dict[command]))
321 if command in ("add", "update"):
322 parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
323 parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
324 parser.add_option('-e', '--email', dest='email', default="", help="email (mandatory for users)")
325 # use --extra instead
326 # parser.add_option('-u', '--url', dest='url', metavar='<url>', default=None, help="URL, useful for slices")
327 # parser.add_option('-d', '--description', dest='description', metavar='<description>',
328 # help='Description, useful for slices', default=None)
329 parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file',
331 parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='slice xrns',
332 default='', type="str", action='callback', callback=optparse_listvalue_callback)
333 parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>',
334 help='slice researchers', default='', type="str", action='callback',
335 callback=optparse_listvalue_callback)
336 parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Principal Investigators/Project Managers',
337 default='', type="str", action='callback', callback=optparse_listvalue_callback)
338 # use --extra instead
339 # parser.add_option('-f', '--firstname', dest='firstname', metavar='<firstname>', help='user first name')
340 # parser.add_option('-l', '--lastname', dest='lastname', metavar='<lastname>', help='user last name')
341 parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
342 action="callback", callback=optparse_dictvalue_callback, nargs=1,
343 help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
345 # user specifies remote aggregate/sm/component
346 if command in ("resources", "slices", "create", "delete", "start", "stop",
347 "restart", "shutdown", "get_ticket", "renew", "status"):
348 parser.add_option("-d", "--delegate", dest="delegate", default=None,
350 help="Include a credential delegated to the user's root"+\
351 "authority in set of credentials for this call")
353 # registy filter option
354 if command in ("list", "show", "remove"):
355 parser.add_option("-t", "--type", dest="type", type="choice",
356 help="type filter ([all]|user|slice|authority|node|aggregate)",
357 choices=("all", "user", "slice", "authority", "node", "aggregate"),
359 if command in ("resources"):
361 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
362 help="schema type and version of resulting RSpec")
363 # disable/enable cached rspecs
364 parser.add_option("-c", "--current", dest="current", default=False,
366 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
368 parser.add_option("-f", "--format", dest="format", type="choice",
369 help="display format ([xml]|dns|ip)", default="xml",
370 choices=("xml", "dns", "ip"))
371 #panos: a new option to define the type of information about resources a user is interested in
372 parser.add_option("-i", "--info", dest="info",
373 help="optional component information", default=None)
374 # a new option to retreive or not reservation-oriented RSpecs (leases)
375 parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
376 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
377 choices=("all", "resources", "leases"), default="resources")
380 # 'create' does return the new rspec, makes sense to save that too
381 if command in ("resources", "show", "list", "create_gid", 'create'):
382 parser.add_option("-o", "--output", dest="file",
383 help="output XML to file", metavar="FILE", default=None)
385 if command in ("show", "list"):
386 parser.add_option("-f", "--format", dest="format", type="choice",
387 help="display format ([text]|xml)", default="text",
388 choices=("text", "xml"))
390 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
391 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
392 choices=("xml", "xmllist", "hrnlist"))
393 if command == 'list':
394 parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
395 help="list all child records", default=False)
396 if command in ("delegate"):
397 parser.add_option("-u", "--user",
398 action="store_true", dest="delegate_user", default=False,
399 help="delegate user credential")
400 parser.add_option("-s", "--slice", dest="delegate_slice",
401 help="delegate slice credential", metavar="HRN", default=None)
403 if command in ("version"):
404 parser.add_option("-R","--registry-version",
405 action="store_true", dest="version_registry", default=False,
406 help="probe registry version instead of sliceapi")
407 parser.add_option("-l","--local",
408 action="store_true", dest="version_local", default=False,
409 help="display version of the local client")
414 def create_parser(self):
416 # Generate command line parser
417 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
418 description="Commands: %s"%(" ".join(self.available_names)))
419 parser.add_option("-r", "--registry", dest="registry",
420 help="root registry", metavar="URL", default=None)
421 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
422 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
423 parser.add_option("-R", "--raw", dest="raw", default=None,
424 help="Save raw, unparsed server response to a file")
425 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
426 help="raw file format ([text]|pickled|json)", default="text",
427 choices=("text","pickled","json"))
428 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
429 help="text string to write before and after raw output")
430 parser.add_option("-d", "--dir", dest="sfi_dir",
431 help="config & working directory - default is %default",
432 metavar="PATH", default=Sfi.default_sfi_dir())
433 parser.add_option("-u", "--user", dest="user",
434 help="user name", metavar="HRN", default=None)
435 parser.add_option("-a", "--auth", dest="auth",
436 help="authority name", metavar="HRN", default=None)
437 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
438 help="verbose mode - cumulative")
439 parser.add_option("-D", "--debug",
440 action="store_true", dest="debug", default=False,
441 help="Debug (xml-rpc) protocol messages")
442 # would it make sense to use ~/.ssh/id_rsa as a default here ?
443 parser.add_option("-k", "--private-key",
444 action="store", dest="user_private_key", default=None,
445 help="point to the private key file to use if not yet installed in sfi_dir")
446 parser.add_option("-t", "--timeout", dest="timeout", default=None,
447 help="Amout of time to wait before timing out the request")
448 parser.add_option("-?", "--commands",
449 action="store_true", dest="command_help", default=False,
450 help="one page summary on commands & exit")
451 parser.disable_interspersed_args()
456 def print_help (self):
457 print "==================== Generic sfi usage"
458 self.sfi_parser.print_help()
459 print "==================== Specific command usage"
460 self.command_parser.print_help()
463 # Main: parse arguments and dispatch to command
465 def dispatch(self, command, command_options, command_args):
466 return getattr(self, command)(command_options, command_args)
469 self.sfi_parser = self.create_parser()
470 (options, args) = self.sfi_parser.parse_args()
471 if options.command_help:
472 self.print_command_help(options)
474 self.options = options
476 self.logger.setLevelFromOptVerbose(self.options.verbose)
479 self.logger.critical("No command given. Use -h for help.")
480 self.print_command_help(options)
484 self.command_parser = self.create_command_parser(command)
485 (command_options, command_args) = self.command_parser.parse_args(args[1:])
486 self.command_options = command_options
490 self.logger.info("Command=%s" % command)
493 self.dispatch(command, command_options, command_args)
495 self.logger.critical ("Unknown command %s"%command)
502 def read_config(self):
503 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
505 config = Config (config_file)
507 self.logger.critical("Failed to read configuration file %s"%config_file)
508 self.logger.info("Make sure to remove the export clauses and to add quotes")
509 if self.options.verbose==0:
510 self.logger.info("Re-run with -v for more details")
512 self.logger.log_exc("Could not read config file %s"%config_file)
517 if (self.options.sm is not None):
518 self.sm_url = self.options.sm
519 elif hasattr(config, "SFI_SM"):
520 self.sm_url = config.SFI_SM
522 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
526 if (self.options.registry is not None):
527 self.reg_url = self.options.registry
528 elif hasattr(config, "SFI_REGISTRY"):
529 self.reg_url = config.SFI_REGISTRY
531 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
535 if (self.options.user is not None):
536 self.user = self.options.user
537 elif hasattr(config, "SFI_USER"):
538 self.user = config.SFI_USER
540 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
544 if (self.options.auth is not None):
545 self.authority = self.options.auth
546 elif hasattr(config, "SFI_AUTH"):
547 self.authority = config.SFI_AUTH
549 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
552 self.config_file=config_file
556 def show_config (self):
557 print "From configuration file %s"%self.config_file
560 ('SFI_AUTH','authority'),
562 ('SFI_REGISTRY','reg_url'),
564 for (external_name, internal_name) in flags:
565 print "%s='%s'"%(external_name,getattr(self,internal_name))
568 # Get various credential and spec files
570 # Establishes limiting conventions
571 # - conflates MAs and SAs
572 # - assumes last token in slice name is unique
574 # Bootstraps credentials
575 # - bootstrap user credential from self-signed certificate
576 # - bootstrap authority credential from user credential
577 # - bootstrap slice credential from user credential
580 # init self-signed cert, user credentials and gid
581 def bootstrap (self):
582 client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
583 # if -k is provided, use this to initialize private key
584 if self.options.user_private_key:
585 client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
587 # trigger legacy compat code if needed
588 # the name has changed from just <leaf>.pkey to <hrn>.pkey
589 if not os.path.isfile(client_bootstrap.private_key_filename()):
590 self.logger.info ("private key not found, trying legacy name")
592 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
593 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
594 client_bootstrap.init_private_key_if_missing (legacy_private_key)
595 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
597 self.logger.log_exc("Can't find private key ")
601 client_bootstrap.bootstrap_my_gid()
602 # extract what's needed
603 self.private_key = client_bootstrap.private_key()
604 self.my_credential_string = client_bootstrap.my_credential_string ()
605 self.my_gid = client_bootstrap.my_gid ()
606 self.client_bootstrap = client_bootstrap
609 def my_authority_credential_string(self):
610 if not self.authority:
611 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
613 return self.client_bootstrap.authority_credential_string (self.authority)
615 def slice_credential_string(self, name):
616 return self.client_bootstrap.slice_credential_string (name)
618 # xxx should be supported by sfaclientbootstrap as well
619 def delegate_cred(self, object_cred, hrn, type='authority'):
620 # the gid and hrn of the object we are delegating
621 if isinstance(object_cred, str):
622 object_cred = Credential(string=object_cred)
623 object_gid = object_cred.get_gid_object()
624 object_hrn = object_gid.get_hrn()
626 if not object_cred.get_privileges().get_all_delegate():
627 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
630 # the delegating user's gid
631 caller_gidfile = self.my_gid()
633 # the gid of the user who will be delegated to
634 delegee_gid = self.client_bootstrap.gid(hrn,type)
635 delegee_hrn = delegee_gid.get_hrn()
636 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
637 return dcred.save_to_string(save_parents=True)
640 # Management of the servers
645 if not hasattr (self, 'registry_proxy'):
646 self.logger.info("Contacting Registry at: %s"%self.reg_url)
647 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
648 timeout=self.options.timeout, verbose=self.options.debug)
649 return self.registry_proxy
653 if not hasattr (self, 'sliceapi_proxy'):
654 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
655 if hasattr(self.command_options,'component') and self.command_options.component:
656 # resolve the hrn at the registry
657 node_hrn = self.command_options.component
658 records = self.registry().Resolve(node_hrn, self.my_credential_string)
659 records = filter_records('node', records)
661 self.logger.warning("No such component:%r"% opts.component)
663 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
664 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
666 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
667 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
668 self.sm_url = 'http://' + self.sm_url
669 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
670 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
671 timeout=self.options.timeout, verbose=self.options.debug)
672 return self.sliceapi_proxy
674 def get_cached_server_version(self, server):
675 # check local cache first
678 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
679 cache_key = server.url + "-version"
681 cache = Cache(cache_file)
684 self.logger.info("Local cache not found at: %s" % cache_file)
687 version = cache.get(cache_key)
690 result = server.GetVersion()
691 version= ReturnValue.get_value(result)
692 # cache version for 20 minutes
693 cache.add(cache_key, version, ttl= 60*20)
694 self.logger.info("Updating cache file %s" % cache_file)
695 cache.save_to_file(cache_file)
699 ### resurrect this temporarily so we can support V1 aggregates for a while
700 def server_supports_options_arg(self, server):
702 Returns true if server support the optional call_id arg, false otherwise.
704 server_version = self.get_cached_server_version(server)
706 # xxx need to rewrite this
707 if int(server_version.get('geni_api')) >= 2:
711 def server_supports_call_id_arg(self, server):
712 server_version = self.get_cached_server_version(server)
714 if 'sfa' in server_version and 'code_tag' in server_version:
715 code_tag = server_version['code_tag']
716 code_tag_parts = code_tag.split("-")
717 version_parts = code_tag_parts[0].split(".")
718 major, minor = version_parts[0], version_parts[1]
719 rev = code_tag_parts[1]
720 if int(major) == 1 and minor == 0 and build >= 22:
724 ### ois = options if supported
725 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
726 def ois (self, server, option_dict):
727 if self.server_supports_options_arg (server):
729 elif self.server_supports_call_id_arg (server):
730 return [ unique_call_id () ]
734 ### cis = call_id if supported - like ois
735 def cis (self, server):
736 if self.server_supports_call_id_arg (server):
737 return [ unique_call_id ]
741 ######################################## miscell utilities
742 def get_rspec_file(self, rspec):
743 if (os.path.isabs(rspec)):
746 file = os.path.join(self.options.sfi_dir, rspec)
747 if (os.path.isfile(file)):
750 self.logger.critical("No such rspec file %s"%rspec)
753 def get_record_file(self, record):
754 if (os.path.isabs(record)):
757 file = os.path.join(self.options.sfi_dir, record)
758 if (os.path.isfile(file)):
761 self.logger.critical("No such registry record file %s"%record)
765 #==========================================================================
766 # Following functions implement the commands
768 # Registry-related commands
769 #==========================================================================
771 def version(self, options, args):
773 display an SFA server version (GetVersion)
774 or version information about sfi itself
776 if options.version_local:
777 version=version_core()
779 if options.version_registry:
780 server=self.registry()
782 server = self.sliceapi()
783 result = server.GetVersion()
784 version = ReturnValue.get_value(result)
786 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
788 pprinter = PrettyPrinter(indent=4)
789 pprinter.pprint(version)
791 def list(self, options, args):
793 list entries in named authority registry (List)
800 if options.recursive:
801 opts['recursive'] = options.recursive
804 list = self.registry().List(hrn, self.my_credential_string, options)
806 raise Exception, "Not enough parameters for the 'list' command"
808 # filter on person, slice, site, node, etc.
809 # THis really should be in the self.filter_records funct def comment...
810 list = filter_records(options.type, list)
812 print "%s (%s)" % (record['hrn'], record['type'])
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 record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
826 record_dicts = filter_records(options.type, record_dicts)
828 self.logger.error("No record of type %s"% options.type)
829 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
830 for record in records:
831 if (options.format == "text"): record.dump(sort=True)
832 else: print record.save_as_xml()
834 save_records_to_file(options.file, record_dicts, options.fileformat)
837 def add(self, options, args):
838 "add record into registry from xml file (Register)"
839 auth_cred = self.my_authority_credential_string()
842 record_filepath = args[0]
843 rec_file = self.get_record_file(record_filepath)
844 record_dict.update(load_record_from_file(rec_file).todict())
846 record_dict.update(load_record_from_opts(options).todict())
847 # we should have a type by now
848 if 'type' not in record_dict :
851 # this is still planetlab dependent.. as plc will whine without that
852 # also, it's only for adding
853 if record_dict['type'] == 'user':
854 if not 'first_name' in record_dict:
855 record_dict['first_name'] = record_dict['hrn']
856 if 'last_name' not in record_dict:
857 record_dict['last_name'] = record_dict['hrn']
858 return self.registry().Register(record_dict, auth_cred)
860 def update(self, options, args):
861 "update record into registry from xml file (Update)"
864 record_filepath = args[0]
865 rec_file = self.get_record_file(record_filepath)
866 record_dict.update(load_record_from_file(rec_file).todict())
868 record_dict.update(load_record_from_opts(options).todict())
869 # at the very least we need 'type' here
870 if 'type' not in record_dict:
874 # don't translate into an object, as this would possibly distort
875 # user-provided data; e.g. add an 'email' field to Users
876 if record_dict['type'] == "user":
877 if record_dict['hrn'] == self.user:
878 cred = self.my_credential_string
880 cred = self.my_authority_credential_string()
881 elif record_dict['type'] in ["slice"]:
883 cred = self.slice_credential_string(record_dict['hrn'])
884 except ServerException, e:
885 # XXX smbaker -- once we have better error return codes, update this
886 # to do something better than a string compare
887 if "Permission error" in e.args[0]:
888 cred = self.my_authority_credential_string()
891 elif record_dict['type'] in ["authority"]:
892 cred = self.my_authority_credential_string()
893 elif record_dict['type'] == 'node':
894 cred = self.my_authority_credential_string()
896 raise "unknown record type" + record_dict['type']
897 return self.registry().Update(record_dict, cred)
899 def remove(self, options, args):
900 "remove registry record by name (Remove)"
901 auth_cred = self.my_authority_credential_string()
909 return self.registry().Remove(hrn, auth_cred, type)
911 # ==================================================================
912 # Slice-related commands
913 # ==================================================================
915 def slices(self, options, args):
916 "list instantiated slices (ListSlices) - returns urn's"
917 server = self.sliceapi()
919 creds = [self.my_credential_string]
921 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
922 creds.append(delegated_cred)
923 # options and call_id when supported
925 api_options['call_id']=unique_call_id()
926 result = server.ListSlices(creds, *self.ois(server,api_options))
927 value = ReturnValue.get_value(result)
929 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
934 # show rspec for named slice
935 def resources(self, options, args):
937 with no arg, discover available resources, (ListResources)
938 or with an slice hrn, shows currently provisioned resources
940 server = self.sliceapi()
945 creds.append(self.slice_credential_string(args[0]))
947 creds.append(self.my_credential_string)
949 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
951 # no need to check if server accepts the options argument since the options has
952 # been a required argument since v1 API
954 # always send call_id to v2 servers
955 api_options ['call_id'] = unique_call_id()
956 # ask for cached value if available
957 api_options ['cached'] = True
960 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
962 api_options['info'] = options.info
963 if options.list_leases:
964 api_options['list_leases'] = options.list_leases
966 if options.current == True:
967 api_options['cached'] = False
969 api_options['cached'] = True
970 if options.rspec_version:
971 version_manager = VersionManager()
972 server_version = self.get_cached_server_version(server)
973 if 'sfa' in server_version:
974 # just request the version the client wants
975 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
977 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
979 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
980 result = server.ListResources (creds, api_options)
981 value = ReturnValue.get_value(result)
983 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
984 if options.file is not None:
985 save_rspec_to_file(value, options.file)
986 if (self.options.raw is None) and (options.file is None):
987 display_rspec(value, options.format)
991 def create(self, options, args):
993 create or update named slice with given rspec
995 server = self.sliceapi()
997 # xxx do we need to check usage (len(args)) ?
1000 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1003 creds = [self.slice_credential_string(slice_hrn)]
1004 delegated_cred = None
1005 server_version = self.get_cached_server_version(server)
1006 if server_version.get('interface') == 'slicemgr':
1007 # delegate our cred to the slice manager
1008 # do not delegate cred to slicemgr...not working at the moment
1010 #if server_version.get('hrn'):
1011 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1012 #elif server_version.get('urn'):
1013 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1016 rspec_file = self.get_rspec_file(args[1])
1017 rspec = open(rspec_file).read()
1020 # need to pass along user keys to the aggregate.
1022 # { urn: urn:publicid:IDN+emulab.net+user+alice
1023 # keys: [<ssh key A>, <ssh key B>]
1026 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1027 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
1028 slice_record = slice_records[0]
1029 user_hrns = slice_record['researcher']
1030 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1031 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1033 if 'sfa' not in server_version:
1034 users = pg_users_arg(user_records)
1035 rspec = RSpec(rspec)
1036 rspec.filter({'component_manager_id': server_version['urn']})
1037 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1039 users = sfa_users_arg(user_records, slice_record)
1041 # do not append users, keys, or slice tags. Anything
1042 # not contained in this request will be removed from the slice
1044 # CreateSliver has supported the options argument for a while now so it should
1045 # be safe to assume this server support it
1047 api_options ['append'] = False
1048 api_options ['call_id'] = unique_call_id()
1049 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1050 value = ReturnValue.get_value(result)
1051 if self.options.raw:
1052 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1053 if options.file is not None:
1054 save_rspec_to_file (value, options.file)
1055 if (self.options.raw is None) and (options.file is None):
1060 def delete(self, options, args):
1062 delete named slice (DeleteSliver)
1064 server = self.sliceapi()
1068 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1071 slice_cred = self.slice_credential_string(slice_hrn)
1072 creds = [slice_cred]
1073 if options.delegate:
1074 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1075 creds.append(delegated_cred)
1077 # options and call_id when supported
1079 api_options ['call_id'] = unique_call_id()
1080 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1081 value = ReturnValue.get_value(result)
1082 if self.options.raw:
1083 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1088 def status(self, options, args):
1090 retrieve slice status (SliverStatus)
1092 server = self.sliceapi()
1096 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1099 slice_cred = self.slice_credential_string(slice_hrn)
1100 creds = [slice_cred]
1101 if options.delegate:
1102 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1103 creds.append(delegated_cred)
1105 # options and call_id when supported
1107 api_options['call_id']=unique_call_id()
1108 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1109 value = ReturnValue.get_value(result)
1110 if self.options.raw:
1111 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1115 def start(self, options, args):
1117 start named slice (Start)
1119 server = self.sliceapi()
1123 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1126 slice_cred = self.slice_credential_string(args[0])
1127 creds = [slice_cred]
1128 if options.delegate:
1129 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1130 creds.append(delegated_cred)
1131 # xxx Thierry - does this not need an api_options as well ?
1132 result = server.Start(slice_urn, creds)
1133 value = ReturnValue.get_value(result)
1134 if self.options.raw:
1135 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1140 def stop(self, options, args):
1142 stop named slice (Stop)
1144 server = self.sliceapi()
1147 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1149 slice_cred = self.slice_credential_string(args[0])
1150 creds = [slice_cred]
1151 if options.delegate:
1152 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1153 creds.append(delegated_cred)
1154 result = server.Stop(slice_urn, creds)
1155 value = ReturnValue.get_value(result)
1156 if self.options.raw:
1157 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1163 def reset(self, options, args):
1165 reset named slice (reset_slice)
1167 server = self.sliceapi()
1170 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1172 slice_cred = self.slice_credential_string(args[0])
1173 creds = [slice_cred]
1174 if options.delegate:
1175 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1176 creds.append(delegated_cred)
1177 result = server.reset_slice(creds, slice_urn)
1178 value = ReturnValue.get_value(result)
1179 if self.options.raw:
1180 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1185 def renew(self, options, args):
1187 renew slice (RenewSliver)
1189 server = self.sliceapi()
1192 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1194 slice_cred = self.slice_credential_string(args[0])
1195 creds = [slice_cred]
1196 if options.delegate:
1197 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1198 creds.append(delegated_cred)
1201 # options and call_id when supported
1203 api_options['call_id']=unique_call_id()
1204 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1205 value = ReturnValue.get_value(result)
1206 if self.options.raw:
1207 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1213 def shutdown(self, options, args):
1215 shutdown named slice (Shutdown)
1217 server = self.sliceapi()
1220 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1222 slice_cred = self.slice_credential_string(slice_hrn)
1223 creds = [slice_cred]
1224 if options.delegate:
1225 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1226 creds.append(delegated_cred)
1227 result = server.Shutdown(slice_urn, creds)
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 get_ticket(self, options, args):
1238 get a ticket for the specified slice
1240 server = self.sliceapi()
1242 slice_hrn, rspec_path = args[0], args[1]
1243 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1245 slice_cred = self.slice_credential_string(slice_hrn)
1246 creds = [slice_cred]
1247 if options.delegate:
1248 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1249 creds.append(delegated_cred)
1251 rspec_file = self.get_rspec_file(rspec_path)
1252 rspec = open(rspec_file).read()
1253 # options and call_id when supported
1255 api_options['call_id']=unique_call_id()
1256 # get ticket at the server
1257 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1259 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1260 self.logger.info("writing ticket to %s"%file)
1261 ticket = SfaTicket(string=ticket_string)
1262 ticket.save_to_file(filename=file, save_parents=True)
1264 def redeem_ticket(self, options, args):
1266 Connects to nodes in a slice and redeems a ticket
1267 (slice hrn is retrieved from the ticket)
1269 ticket_file = args[0]
1271 # get slice hrn from the ticket
1272 # use this to get the right slice credential
1273 ticket = SfaTicket(filename=ticket_file)
1275 ticket_string = ticket.save_to_string(save_parents=True)
1277 slice_hrn = ticket.gidObject.get_hrn()
1278 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1279 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1280 slice_cred = self.slice_credential_string(slice_hrn)
1282 # get a list of node hostnames from the RSpec
1283 tree = etree.parse(StringIO(ticket.rspec))
1284 root = tree.getroot()
1285 hostnames = root.xpath("./network/site/node/hostname/text()")
1287 # create an xmlrpc connection to the component manager at each of these
1288 # components and gall redeem_ticket
1290 for hostname in hostnames:
1292 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1293 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1294 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1295 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1296 timeout=self.options.timeout, verbose=self.options.debug)
1297 server.RedeemTicket(ticket_string, slice_cred)
1298 self.logger.info("Success")
1299 except socket.gaierror:
1300 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1301 except Exception, e:
1302 self.logger.log_exc(e.message)
1305 def create_gid(self, options, args):
1307 Create a GID (CreateGid)
1312 target_hrn = args[0]
1313 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.client_bootstrap.my_gid_string())
1315 filename = options.file
1317 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1318 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1319 GID(string=gid).save_to_file(filename)
1322 def delegate(self, options, args):
1324 (locally) create delegate credential for use by given hrn
1326 delegee_hrn = args[0]
1327 if options.delegate_user:
1328 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1329 elif options.delegate_slice:
1330 slice_cred = self.slice_credential_string(options.delegate_slice)
1331 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1333 self.logger.warning("Must specify either --user or --slice <hrn>")
1335 delegated_cred = Credential(string=cred)
1336 object_hrn = delegated_cred.get_gid_object().get_hrn()
1337 if options.delegate_user:
1338 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1339 + get_leaf(object_hrn) + ".cred")
1340 elif options.delegate_slice:
1341 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1342 + get_leaf(object_hrn) + ".cred")
1344 delegated_cred.save_to_file(dest_fn, save_parents=True)
1346 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1348 def get_trusted_certs(self, options, args):
1350 return uhe trusted certs at this interface (get_trusted_certs)
1352 trusted_certs = self.registry().get_trusted_certs()
1353 for trusted_cert in trusted_certs:
1354 gid = GID(string=trusted_cert)
1356 cert = Certificate(string=trusted_cert)
1357 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1360 def config (self, options, args):
1361 "Display contents of current config"