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
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.sfalogging import sfi_logger
27 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn
28 from sfa.util.config import Config
29 from sfa.util.version import version_core
30 from sfa.util.cache import Cache
32 from sfa.storage.record import Record
34 from sfa.rspecs.rspec import RSpec
35 from sfa.rspecs.rspec_converter import RSpecConverter
36 from sfa.rspecs.version_manager import VersionManager
38 from sfa.client.sfaclientlib import SfaClientBootstrap
39 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
40 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
41 from sfa.client.return_value import ReturnValue
45 # utility methods here
47 def display_rspec(rspec, format='rspec'):
49 tree = etree.parse(StringIO(rspec))
51 result = root.xpath("./network/site/node/hostname/text()")
52 elif format in ['ip']:
53 # The IP address is not yet part of the new RSpec
54 # so this doesn't do anything yet.
55 tree = etree.parse(StringIO(rspec))
57 result = root.xpath("./network/site/node/ipv4/text()")
64 def display_list(results):
65 for result in results:
68 def display_records(recordList, dump=False):
69 ''' Print all fields in the record'''
70 for record in recordList:
71 display_record(record, dump)
73 def display_record(record, dump=False):
77 info = record.getdict()
78 print "%s (%s)" % (info['hrn'], info['type'])
82 def filter_records(type, records):
84 for record in records:
85 if (record['type'] == type) or (type == "all"):
86 filtered_records.append(record)
87 return filtered_records
91 def save_raw_to_file(var, filename, format="text", banner=None):
93 # if filename is "-", send it to stdout
96 f = open(filename, "w")
101 elif format == "pickled":
102 f.write(pickle.dumps(var))
103 elif format == "json":
104 if hasattr(json, "dumps"):
105 f.write(json.dumps(var)) # python 2.6
107 f.write(json.write(var)) # python 2.5
109 # this should never happen
110 print "unknown output format", format
112 f.write('\n'+banner+"\n")
114 def save_rspec_to_file(rspec, filename):
115 if not filename.endswith(".rspec"):
116 filename = filename + ".rspec"
117 f = open(filename, 'w')
122 def save_records_to_file(filename, record_dicts, format="xml"):
125 for record_dict in record_dicts:
127 save_record_to_file(filename + "." + str(index), record_dict)
129 save_record_to_file(filename, record_dict)
131 elif format == "xmllist":
132 f = open(filename, "w")
133 f.write("<recordlist>\n")
134 for record_dict in record_dicts:
135 record_obj=Record(dict=record_dict)
136 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
137 f.write("</recordlist>\n")
139 elif format == "hrnlist":
140 f = open(filename, "w")
141 for record_dict in record_dicts:
142 record_obj=Record(dict=record_dict)
143 f.write(record_obj.hrn + "\n")
146 # this should never happen
147 print "unknown output format", format
149 def save_record_to_file(filename, record_dict):
150 record = Record(dict=record_dict)
151 xml = record.save_as_xml()
152 f=codecs.open(filename, encoding='utf-8',mode="w")
159 def load_record_from_file(filename):
160 f=codecs.open(filename, encoding="utf-8", mode="r")
161 xml_string = f.read()
163 return Record(xml=xml_string)
167 def unique_call_id(): return uuid.uuid4().urn
171 # dirty hack to make this class usable from the outside
172 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
175 def default_sfi_dir ():
176 if os.path.isfile("./sfi_config"):
179 return os.path.expanduser("~/.sfi/")
181 # dummy to meet Sfi's expectations for its 'options' field
182 # i.e. s/t we can do setattr on
186 def __init__ (self,options=None):
187 if options is None: options=Sfi.DummyOptions()
188 for opt in Sfi.required_options:
189 if not hasattr(options,opt): setattr(options,opt,None)
190 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
191 self.options = options
193 self.authority = None
194 self.logger = sfi_logger
195 self.logger.enable_console()
196 self.available_names = [ tuple[0] for tuple in Sfi.available ]
197 self.available_dict = dict (Sfi.available)
199 # tuples command-name expected-args in the order in which they should appear in the help
202 ("list", "authority"),
205 ("update", "record"),
208 ("resources", "[slice_hrn]"),
209 ("create", "slice_hrn rspec"),
210 ("delete", "slice_hrn"),
211 ("status", "slice_hrn"),
212 ("start", "slice_hrn"),
213 ("stop", "slice_hrn"),
214 ("reset", "slice_hrn"),
215 ("renew", "slice_hrn time"),
216 ("shutdown", "slice_hrn"),
217 ("get_ticket", "slice_hrn rspec"),
218 ("redeem_ticket", "ticket"),
219 ("delegate", "name"),
220 ("create_gid", "[name]"),
221 ("get_trusted_certs", "cred"),
225 def print_command_help (self, options):
226 verbose=getattr(options,'verbose')
227 format3="%18s %-15s %s"
230 print format3%("command","cmd_args","description")
234 self.create_parser().print_help()
235 for command in self.available_names:
236 args=self.available_dict[command]
237 method=getattr(self,command,None)
239 if method: doc=getattr(method,'__doc__',"")
240 if not doc: doc="*** no doc found ***"
241 doc=doc.strip(" \t\n")
242 doc=doc.replace("\n","\n"+35*' ')
245 print format3%(command,args,doc)
247 self.create_command_parser(command).print_help()
249 def create_command_parser(self, command):
250 if command not in self.available_dict:
251 msg="Invalid command\n"
253 msg += ','.join(self.available_names)
254 self.logger.critical(msg)
257 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
258 % (command, self.available_dict[command]))
260 # user specifies remote aggregate/sm/component
261 if command in ("resources", "slices", "create", "delete", "start", "stop",
262 "restart", "shutdown", "get_ticket", "renew", "status"):
263 parser.add_option("-d", "--delegate", dest="delegate", default=None,
265 help="Include a credential delegated to the user's root"+\
266 "authority in set of credentials for this call")
268 # registy filter option
269 if command in ("list", "show", "remove"):
270 parser.add_option("-t", "--type", dest="type", type="choice",
271 help="type filter ([all]|user|slice|authority|node|aggregate)",
272 choices=("all", "user", "slice", "authority", "node", "aggregate"),
274 if command in ("resources"):
276 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
277 help="schema type and version of resulting RSpec")
278 # disable/enable cached rspecs
279 parser.add_option("-c", "--current", dest="current", default=False,
281 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
283 parser.add_option("-f", "--format", dest="format", type="choice",
284 help="display format ([xml]|dns|ip)", default="xml",
285 choices=("xml", "dns", "ip"))
286 #panos: a new option to define the type of information about resources a user is interested in
287 parser.add_option("-i", "--info", dest="info",
288 help="optional component information", default=None)
291 # 'create' does return the new rspec, makes sense to save that too
292 if command in ("resources", "show", "list", "create_gid", 'create'):
293 parser.add_option("-o", "--output", dest="file",
294 help="output XML to file", metavar="FILE", default=None)
296 if command in ("show", "list"):
297 parser.add_option("-f", "--format", dest="format", type="choice",
298 help="display format ([text]|xml)", default="text",
299 choices=("text", "xml"))
301 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
302 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
303 choices=("xml", "xmllist", "hrnlist"))
305 if command in ("delegate"):
306 parser.add_option("-u", "--user",
307 action="store_true", dest="delegate_user", default=False,
308 help="delegate user credential")
309 parser.add_option("-s", "--slice", dest="delegate_slice",
310 help="delegate slice credential", metavar="HRN", default=None)
312 if command in ("version"):
313 parser.add_option("-R","--registry-version",
314 action="store_true", dest="version_registry", default=False,
315 help="probe registry version instead of sliceapi")
316 parser.add_option("-l","--local",
317 action="store_true", dest="version_local", default=False,
318 help="display version of the local client")
323 def create_parser(self):
325 # Generate command line parser
326 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
327 description="Commands: %s"%(" ".join(self.available_names)))
328 parser.add_option("-r", "--registry", dest="registry",
329 help="root registry", metavar="URL", default=None)
330 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
331 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
332 parser.add_option("-R", "--raw", dest="raw", default=None,
333 help="Save raw, unparsed server response to a file")
334 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
335 help="raw file format ([text]|pickled|json)", default="text",
336 choices=("text","pickled","json"))
337 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
338 help="text string to write before and after raw output")
339 parser.add_option("-d", "--dir", dest="sfi_dir",
340 help="config & working directory - default is %default",
341 metavar="PATH", default=Sfi.default_sfi_dir())
342 parser.add_option("-u", "--user", dest="user",
343 help="user name", metavar="HRN", default=None)
344 parser.add_option("-a", "--auth", dest="auth",
345 help="authority name", metavar="HRN", default=None)
346 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
347 help="verbose mode - cumulative")
348 parser.add_option("-D", "--debug",
349 action="store_true", dest="debug", default=False,
350 help="Debug (xml-rpc) protocol messages")
351 # would it make sense to use ~/.ssh/id_rsa as a default here ?
352 parser.add_option("-k", "--private-key",
353 action="store", dest="user_private_key", default=None,
354 help="point to the private key file to use if not yet installed in sfi_dir")
355 parser.add_option("-t", "--timeout", dest="timeout", default=None,
356 help="Amout of time to wait before timing out the request")
357 parser.add_option("-?", "--commands",
358 action="store_true", dest="command_help", default=False,
359 help="one page summary on commands & exit")
360 parser.disable_interspersed_args()
365 def print_help (self):
366 self.sfi_parser.print_help()
367 self.command_parser.print_help()
370 # Main: parse arguments and dispatch to command
372 def dispatch(self, command, command_options, command_args):
373 return getattr(self, command)(command_options, command_args)
376 self.sfi_parser = self.create_parser()
377 (options, args) = self.sfi_parser.parse_args()
378 if options.command_help:
379 self.print_command_help(options)
381 self.options = options
383 self.logger.setLevelFromOptVerbose(self.options.verbose)
386 self.logger.critical("No command given. Use -h for help.")
387 self.print_command_help(options)
391 self.command_parser = self.create_command_parser(command)
392 (command_options, command_args) = self.command_parser.parse_args(args[1:])
393 self.command_options = command_options
397 self.logger.info("Command=%s" % command)
400 self.dispatch(command, command_options, command_args)
402 self.logger.critical ("Unknown command %s"%command)
409 def read_config(self):
410 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
412 config = Config (config_file)
414 self.logger.critical("Failed to read configuration file %s"%config_file)
415 self.logger.info("Make sure to remove the export clauses and to add quotes")
416 if self.options.verbose==0:
417 self.logger.info("Re-run with -v for more details")
419 self.logger.log_exc("Could not read config file %s"%config_file)
424 if (self.options.sm is not None):
425 self.sm_url = self.options.sm
426 elif hasattr(config, "SFI_SM"):
427 self.sm_url = config.SFI_SM
429 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
433 if (self.options.registry is not None):
434 self.reg_url = self.options.registry
435 elif hasattr(config, "SFI_REGISTRY"):
436 self.reg_url = config.SFI_REGISTRY
438 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
442 if (self.options.user is not None):
443 self.user = self.options.user
444 elif hasattr(config, "SFI_USER"):
445 self.user = config.SFI_USER
447 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
451 if (self.options.auth is not None):
452 self.authority = self.options.auth
453 elif hasattr(config, "SFI_AUTH"):
454 self.authority = config.SFI_AUTH
456 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
459 self.config_file=config_file
463 def show_config (self):
464 print "From configuration file %s"%self.config_file
467 ('SFI_AUTH','authority'),
469 ('SFI_REGISTRY','reg_url'),
471 for (external_name, internal_name) in flags:
472 print "%s='%s'"%(external_name,getattr(self,internal_name))
475 # Get various credential and spec files
477 # Establishes limiting conventions
478 # - conflates MAs and SAs
479 # - assumes last token in slice name is unique
481 # Bootstraps credentials
482 # - bootstrap user credential from self-signed certificate
483 # - bootstrap authority credential from user credential
484 # - bootstrap slice credential from user credential
487 # init self-signed cert, user credentials and gid
488 def bootstrap (self):
489 bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
490 # if -k is provided, use this to initialize private key
491 if self.options.user_private_key:
492 bootstrap.init_private_key_if_missing (self.options.user_private_key)
494 # trigger legacy compat code if needed
495 # the name has changed from just <leaf>.pkey to <hrn>.pkey
496 if not os.path.isfile(bootstrap.private_key_filename()):
497 self.logger.info ("private key not found, trying legacy name")
499 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
500 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
501 bootstrap.init_private_key_if_missing (legacy_private_key)
502 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
504 self.logger.log_exc("Can't find private key ")
508 bootstrap.bootstrap_my_gid()
509 # extract what's needed
510 self.private_key = bootstrap.private_key()
511 self.my_credential_string = bootstrap.my_credential_string ()
512 self.my_gid = bootstrap.my_gid ()
513 self.bootstrap = bootstrap
516 def my_authority_credential_string(self):
517 if not self.authority:
518 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
520 return self.bootstrap.authority_credential_string (self.authority)
522 def slice_credential_string(self, name):
523 return self.bootstrap.slice_credential_string (name)
525 # xxx should be supported by sfaclientbootstrap as well
526 def delegate_cred(self, object_cred, hrn, type='authority'):
527 # the gid and hrn of the object we are delegating
528 if isinstance(object_cred, str):
529 object_cred = Credential(string=object_cred)
530 object_gid = object_cred.get_gid_object()
531 object_hrn = object_gid.get_hrn()
533 if not object_cred.get_privileges().get_all_delegate():
534 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
537 # the delegating user's gid
538 caller_gidfile = self.my_gid()
540 # the gid of the user who will be delegated to
541 delegee_gid = self.bootstrap.gid(hrn,type)
542 delegee_hrn = delegee_gid.get_hrn()
543 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
544 return dcred.save_to_string(save_parents=True)
547 # Management of the servers
552 if not hasattr (self, 'registry_proxy'):
553 self.logger.info("Contacting Registry at: %s"%self.reg_url)
554 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
555 timeout=self.options.timeout, verbose=self.options.debug)
556 return self.registry_proxy
560 if not hasattr (self, 'sliceapi_proxy'):
561 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
562 if hasattr(self.command_options,'component') and self.command_options.component:
563 # resolve the hrn at the registry
564 node_hrn = self.command_options.component
565 records = self.registry().Resolve(node_hrn, self.my_credential_string)
566 records = filter_records('node', records)
568 self.logger.warning("No such component:%r"% opts.component)
570 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
571 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
573 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
574 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
575 self.sm_url = 'http://' + self.sm_url
576 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
577 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
578 timeout=self.options.timeout, verbose=self.options.debug)
579 return self.sliceapi_proxy
581 def get_cached_server_version(self, server):
582 # check local cache first
585 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
586 cache_key = server.url + "-version"
588 cache = Cache(cache_file)
591 self.logger.info("Local cache not found at: %s" % cache_file)
594 version = cache.get(cache_key)
597 result = server.GetVersion()
598 version= ReturnValue.get_value(result)
599 # cache version for 20 minutes
600 cache.add(cache_key, version, ttl= 60*20)
601 self.logger.info("Updating cache file %s" % cache_file)
602 cache.save_to_file(cache_file)
606 ### resurrect this temporarily so we can support V1 aggregates for a while
607 def server_supports_options_arg(self, server):
609 Returns true if server support the optional call_id arg, false otherwise.
611 server_version = self.get_cached_server_version(server)
613 # xxx need to rewrite this
614 if int(server_version.get('geni_api')) >= 2:
618 def server_supports_call_id_arg(self, server):
619 server_version = self.get_cached_server_version(server)
621 if 'sfa' in server_version and 'code_tag' in server_version:
622 code_tag = server_version['code_tag']
623 code_tag_parts = code_tag.split("-")
624 version_parts = code_tag_parts[0].split(".")
625 major, minor = version_parts[0], version_parts[1]
626 rev = code_tag_parts[1]
627 if int(major) == 1 and minor == 0 and build >= 22:
631 ### ois = options if supported
632 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
633 def ois (self, server, option_dict):
634 if self.server_supports_options_arg (server):
636 elif self.server_supports_call_id_arg (server):
637 return [ unique_call_id () ]
641 ### cis = call_id if supported - like ois
642 def cis (self, server):
643 if self.server_supports_call_id_arg (server):
644 return [ unique_call_id ]
648 ######################################## miscell utilities
649 def get_rspec_file(self, rspec):
650 if (os.path.isabs(rspec)):
653 file = os.path.join(self.options.sfi_dir, rspec)
654 if (os.path.isfile(file)):
657 self.logger.critical("No such rspec file %s"%rspec)
660 def get_record_file(self, record):
661 if (os.path.isabs(record)):
664 file = os.path.join(self.options.sfi_dir, record)
665 if (os.path.isfile(file)):
668 self.logger.critical("No such registry record file %s"%record)
672 #==========================================================================
673 # Following functions implement the commands
675 # Registry-related commands
676 #==========================================================================
678 def version(self, options, args):
680 display an SFA server version (GetVersion)
681 or version information about sfi itself
683 if options.version_local:
684 version=version_core()
686 if options.version_registry:
687 server=self.registry()
689 server = self.sliceapi()
690 result = server.GetVersion()
691 version = ReturnValue.get_value(result)
693 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
695 pprinter = PrettyPrinter(indent=4)
696 pprinter.pprint(version)
698 def list(self, options, args):
700 list entries in named authority registry (List)
707 list = self.registry().List(hrn, self.my_credential_string)
709 raise Exception, "Not enough parameters for the 'list' command"
711 # filter on person, slice, site, node, etc.
712 # THis really should be in the self.filter_records funct def comment...
713 list = filter_records(options.type, list)
715 print "%s (%s)" % (record['hrn'], record['type'])
717 save_records_to_file(options.file, list, options.fileformat)
720 def show(self, options, args):
722 show details about named registry record (Resolve)
728 record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
729 record_dicts = filter_records(options.type, record_dicts)
731 self.logger.error("No record of type %s"% options.type)
732 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
733 for record in records:
734 if (options.format == "text"): record.dump()
735 else: print record.save_as_xml()
737 save_records_to_file(options.file, record_dicts, options.fileformat)
740 def add(self, options, args):
741 "add record into registry from xml file (Register)"
742 auth_cred = self.my_authority_credential_string()
746 record_filepath = args[0]
747 rec_file = self.get_record_file(record_filepath)
748 record = load_record_from_file(rec_file).todict()
749 return self.registry().Register(record, auth_cred)
751 def update(self, options, args):
752 "update record into registry from xml file (Update)"
756 rec_file = self.get_record_file(args[0])
757 record = load_record_from_file(rec_file)
758 if record.type == "user":
759 if record.hrn == self.user:
760 cred = self.my_credential_string
762 cred = self.my_authority_credential_string()
763 elif record.type in ["slice"]:
765 cred = self.slice_credential_string(record.hrn)
766 except ServerException, e:
767 # XXX smbaker -- once we have better error return codes, update this
768 # to do something better than a string compare
769 if "Permission error" in e.args[0]:
770 cred = self.my_authority_credential_string()
773 elif record.type in ["authority"]:
774 cred = self.my_authority_credential_string()
775 elif record.type == 'node':
776 cred = self.my_authority_credential_string()
778 raise "unknown record type" + record.type
779 record_dict = record.todict()
780 return self.registry().Update(record_dict, cred)
782 def remove(self, options, args):
783 "remove registry record by name (Remove)"
784 auth_cred = self.my_authority_credential_string()
792 return self.registry().Remove(hrn, auth_cred, type)
794 # ==================================================================
795 # Slice-related commands
796 # ==================================================================
798 def slices(self, options, args):
799 "list instantiated slices (ListSlices) - returns urn's"
800 server = self.sliceapi()
802 creds = [self.my_credential_string]
804 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
805 creds.append(delegated_cred)
806 # options and call_id when supported
808 api_options['call_id']=unique_call_id()
809 result = server.ListSlices(creds, *self.ois(server,api_options))
810 value = ReturnValue.get_value(result)
812 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
817 # show rspec for named slice
818 def resources(self, options, args):
820 with no arg, discover available resources, (ListResources)
821 or with an slice hrn, shows currently provisioned resources
823 server = self.sliceapi()
828 creds.append(self.slice_credential_string(args[0]))
830 creds.append(self.my_credential_string)
832 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
834 # no need to check if server accepts the options argument since the options has
835 # been a required argument since v1 API
837 # always send call_id to v2 servers
838 api_options ['call_id'] = unique_call_id()
839 # ask for cached value if available
840 api_options ['cached'] = True
843 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
845 api_options['info'] = options.info
847 if options.current == True:
848 api_options['cached'] = False
850 api_options['cached'] = True
851 if options.rspec_version:
852 version_manager = VersionManager()
853 server_version = self.get_cached_server_version(server)
854 if 'sfa' in server_version:
855 # just request the version the client wants
856 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
858 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
860 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
861 result = server.ListResources (creds, api_options)
862 value = ReturnValue.get_value(result)
864 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
865 if options.file is not None:
866 save_rspec_to_file(value, options.file)
867 if (self.options.raw is None) and (options.file is None):
868 display_rspec(value, options.format)
872 def create(self, options, args):
874 create or update named slice with given rspec
876 server = self.sliceapi()
878 # xxx do we need to check usage (len(args)) ?
881 slice_urn = hrn_to_urn(slice_hrn, 'slice')
884 creds = [self.slice_credential_string(slice_hrn)]
885 delegated_cred = None
886 server_version = self.get_cached_server_version(server)
887 if server_version.get('interface') == 'slicemgr':
888 # delegate our cred to the slice manager
889 # do not delegate cred to slicemgr...not working at the moment
891 #if server_version.get('hrn'):
892 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
893 #elif server_version.get('urn'):
894 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
897 rspec_file = self.get_rspec_file(args[1])
898 rspec = open(rspec_file).read()
901 # need to pass along user keys to the aggregate.
903 # { urn: urn:publicid:IDN+emulab.net+user+alice
904 # keys: [<ssh key A>, <ssh key B>]
907 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
908 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
909 slice_record = slice_records[0]
910 user_hrns = slice_record['researcher']
911 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
912 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
914 if 'sfa' not in server_version:
915 users = pg_users_arg(user_records)
917 rspec.filter({'component_manager_id': server_version['urn']})
918 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
920 users = sfa_users_arg(user_records, slice_record)
922 # do not append users, keys, or slice tags. Anything
923 # not contained in this request will be removed from the slice
925 # CreateSliver has supported the options argument for a while now so it should
926 # be safe to assume this server support it
928 api_options ['append'] = False
929 api_options ['call_id'] = unique_call_id()
930 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
931 value = ReturnValue.get_value(result)
933 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
934 if options.file is not None:
935 save_rspec_to_file (value, options.file)
936 if (self.options.raw is None) and (options.file is None):
941 def delete(self, options, args):
943 delete named slice (DeleteSliver)
945 server = self.sliceapi()
949 slice_urn = hrn_to_urn(slice_hrn, 'slice')
952 slice_cred = self.slice_credential_string(slice_hrn)
955 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
956 creds.append(delegated_cred)
958 # options and call_id when supported
960 api_options ['call_id'] = unique_call_id()
961 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
962 value = ReturnValue.get_value(result)
964 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
969 def status(self, options, args):
971 retrieve slice status (SliverStatus)
973 server = self.sliceapi()
977 slice_urn = hrn_to_urn(slice_hrn, 'slice')
980 slice_cred = self.slice_credential_string(slice_hrn)
983 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
984 creds.append(delegated_cred)
986 # options and call_id when supported
988 api_options['call_id']=unique_call_id()
989 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
990 value = ReturnValue.get_value(result)
992 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
996 def start(self, options, args):
998 start named slice (Start)
1000 server = self.sliceapi()
1004 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1007 slice_cred = self.slice_credential_string(args[0])
1008 creds = [slice_cred]
1009 if options.delegate:
1010 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1011 creds.append(delegated_cred)
1012 # xxx Thierry - does this not need an api_options as well ?
1013 result = server.Start(slice_urn, creds)
1014 value = ReturnValue.get_value(result)
1015 if self.options.raw:
1016 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1021 def stop(self, options, args):
1023 stop named slice (Stop)
1025 server = self.sliceapi()
1028 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1030 slice_cred = self.slice_credential_string(args[0])
1031 creds = [slice_cred]
1032 if options.delegate:
1033 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1034 creds.append(delegated_cred)
1035 result = server.Stop(slice_urn, creds)
1036 value = ReturnValue.get_value(result)
1037 if self.options.raw:
1038 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1044 def reset(self, options, args):
1046 reset named slice (reset_slice)
1048 server = self.sliceapi()
1051 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1053 slice_cred = self.slice_credential_string(args[0])
1054 creds = [slice_cred]
1055 if options.delegate:
1056 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1057 creds.append(delegated_cred)
1058 result = server.reset_slice(creds, slice_urn)
1059 value = ReturnValue.get_value(result)
1060 if self.options.raw:
1061 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1066 def renew(self, options, args):
1068 renew slice (RenewSliver)
1070 server = self.sliceapi()
1073 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1075 slice_cred = self.slice_credential_string(args[0])
1076 creds = [slice_cred]
1077 if options.delegate:
1078 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1079 creds.append(delegated_cred)
1082 # options and call_id when supported
1084 api_options['call_id']=unique_call_id()
1085 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1086 value = ReturnValue.get_value(result)
1087 if self.options.raw:
1088 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1094 def shutdown(self, options, args):
1096 shutdown named slice (Shutdown)
1098 server = self.sliceapi()
1101 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1103 slice_cred = self.slice_credential_string(slice_hrn)
1104 creds = [slice_cred]
1105 if options.delegate:
1106 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1107 creds.append(delegated_cred)
1108 result = server.Shutdown(slice_urn, creds)
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)
1117 def get_ticket(self, options, args):
1119 get a ticket for the specified slice
1121 server = self.sliceapi()
1123 slice_hrn, rspec_path = args[0], args[1]
1124 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1126 slice_cred = self.slice_credential_string(slice_hrn)
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)
1132 rspec_file = self.get_rspec_file(rspec_path)
1133 rspec = open(rspec_file).read()
1134 # options and call_id when supported
1136 api_options['call_id']=unique_call_id()
1137 # get ticket at the server
1138 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1140 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1141 self.logger.info("writing ticket to %s"%file)
1142 ticket = SfaTicket(string=ticket_string)
1143 ticket.save_to_file(filename=file, save_parents=True)
1145 def redeem_ticket(self, options, args):
1147 Connects to nodes in a slice and redeems a ticket
1148 (slice hrn is retrieved from the ticket)
1150 ticket_file = args[0]
1152 # get slice hrn from the ticket
1153 # use this to get the right slice credential
1154 ticket = SfaTicket(filename=ticket_file)
1156 ticket_string = ticket.save_to_string(save_parents=True)
1158 slice_hrn = ticket.gidObject.get_hrn()
1159 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1160 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1161 slice_cred = self.slice_credential_string(slice_hrn)
1163 # get a list of node hostnames from the RSpec
1164 tree = etree.parse(StringIO(ticket.rspec))
1165 root = tree.getroot()
1166 hostnames = root.xpath("./network/site/node/hostname/text()")
1168 # create an xmlrpc connection to the component manager at each of these
1169 # components and gall redeem_ticket
1171 for hostname in hostnames:
1173 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1174 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1175 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1176 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1177 timeout=self.options.timeout, verbose=self.options.debug)
1178 server.RedeemTicket(ticket_string, slice_cred)
1179 self.logger.info("Success")
1180 except socket.gaierror:
1181 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1182 except Exception, e:
1183 self.logger.log_exc(e.message)
1186 def create_gid(self, options, args):
1188 Create a GID (CreateGid)
1193 target_hrn = args[0]
1194 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1196 filename = options.file
1198 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1199 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1200 GID(string=gid).save_to_file(filename)
1203 def delegate(self, options, args):
1205 (locally) create delegate credential for use by given hrn
1207 delegee_hrn = args[0]
1208 if options.delegate_user:
1209 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1210 elif options.delegate_slice:
1211 slice_cred = self.slice_credential_string(options.delegate_slice)
1212 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1214 self.logger.warning("Must specify either --user or --slice <hrn>")
1216 delegated_cred = Credential(string=cred)
1217 object_hrn = delegated_cred.get_gid_object().get_hrn()
1218 if options.delegate_user:
1219 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1220 + get_leaf(object_hrn) + ".cred")
1221 elif options.delegate_slice:
1222 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1223 + get_leaf(object_hrn) + ".cred")
1225 delegated_cred.save_to_file(dest_fn, save_parents=True)
1227 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1229 def get_trusted_certs(self, options, args):
1231 return uhe trusted certs at this interface (get_trusted_certs)
1233 trusted_certs = self.registry().get_trusted_certs()
1234 for trusted_cert in trusted_certs:
1235 gid = GID(string=trusted_cert)
1237 cert = Certificate(string=trusted_cert)
1238 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
1241 def config (self, options, args):
1242 "Display contents of current config"