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
15 from lxml import etree
16 from StringIO import StringIO
17 from optparse import OptionParser
18 from pprint import PrettyPrinter
20 from sfa.trust.certificate import Keypair, Certificate
21 from sfa.trust.gid import GID
22 from sfa.trust.credential import Credential
23 from sfa.trust.sfaticket import SfaTicket
25 from sfa.util.sfalogging import sfi_logger
26 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn
27 from sfa.util.config import Config
28 from sfa.util.version import version_core
29 from sfa.util.cache import Cache
31 from sfa.storage.record import SfaRecord, UserRecord, SliceRecord, NodeRecord, AuthorityRecord
33 from sfa.rspecs.rspec import RSpec
34 from sfa.rspecs.rspec_converter import RSpecConverter
35 from sfa.rspecs.version_manager import VersionManager
37 from sfa.client.sfaclientlib import SfaClientBootstrap
38 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
39 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
40 from sfa.client.return_value import ReturnValue
44 # utility methods here
46 def display_rspec(rspec, format='rspec'):
48 tree = etree.parse(StringIO(rspec))
50 result = root.xpath("./network/site/node/hostname/text()")
51 elif format in ['ip']:
52 # The IP address is not yet part of the new RSpec
53 # so this doesn't do anything yet.
54 tree = etree.parse(StringIO(rspec))
56 result = root.xpath("./network/site/node/ipv4/text()")
63 def display_list(results):
64 for result in results:
67 def display_records(recordList, dump=False):
68 ''' Print all fields in the record'''
69 for record in recordList:
70 display_record(record, dump)
72 def display_record(record, dump=False):
76 info = record.getdict()
77 print "%s (%s)" % (info['hrn'], info['type'])
81 def filter_records(type, records):
83 for record in records:
84 if (record['type'] == type) or (type == "all"):
85 filtered_records.append(record)
86 return filtered_records
90 def save_variable_to_file(var, filename, format="text"):
91 f = open(filename, "w")
94 elif format == "pickled":
95 f.write(pickle.dumps(var))
97 # this should never happen
98 print "unknown output format", format
101 def save_rspec_to_file(rspec, filename):
102 if not filename.endswith(".rspec"):
103 filename = filename + ".rspec"
104 f = open(filename, 'w')
109 def save_records_to_file(filename, recordList, format="xml"):
112 for record in recordList:
114 save_record_to_file(filename + "." + str(index), record)
116 save_record_to_file(filename, record)
118 elif format == "xmllist":
119 f = open(filename, "w")
120 f.write("<recordlist>\n")
121 for record in recordList:
122 record = SfaRecord(dict=record)
123 f.write('<record hrn="' + record.get_name() + '" type="' + record.get_type() + '" />\n')
124 f.write("</recordlist>\n")
126 elif format == "hrnlist":
127 f = open(filename, "w")
128 for record in recordList:
129 record = SfaRecord(dict=record)
130 f.write(record.get_name() + "\n")
133 # this should never happen
134 print "unknown output format", format
136 def save_record_to_file(filename, record):
137 if record['type'] in ['user']:
138 record = UserRecord(dict=record)
139 elif record['type'] in ['slice']:
140 record = SliceRecord(dict=record)
141 elif record['type'] in ['node']:
142 record = NodeRecord(dict=record)
143 elif record['type'] in ['authority', 'ma', 'sa']:
144 record = AuthorityRecord(dict=record)
146 record = SfaRecord(dict=record)
147 str = record.save_to_string()
148 f=codecs.open(filename, encoding='utf-8',mode="w")
155 def load_record_from_file(filename):
156 f=codecs.open(filename, encoding="utf-8", mode="r")
159 record = SfaRecord(string=str)
164 def unique_call_id(): return uuid.uuid4().urn
168 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user']
171 def default_sfi_dir ():
172 if os.path.isfile("./sfi_config"):
175 return os.path.expanduser("~/.sfi/")
177 # dummy to meet Sfi's expectations for its 'options' field
178 # i.e. s/t we can do setattr on
182 def __init__ (self,options=None):
183 if options is None: options=Sfi.DummyOptions()
184 for opt in Sfi.required_options:
185 if not hasattr(options,opt): setattr(options,opt,None)
186 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
187 self.options = options
189 self.authority = None
190 self.logger = sfi_logger
191 self.logger.enable_console()
192 self.available_names = [ tuple[0] for tuple in Sfi.available ]
193 self.available_dict = dict (Sfi.available)
195 # tuples command-name expected-args in the order in which they should appear in the help
198 ("list", "authority"),
201 ("update", "record"),
204 ("resources", "[slice_hrn]"),
205 ("create", "slice_hrn rspec"),
206 ("delete", "slice_hrn"),
207 ("status", "slice_hrn"),
208 ("start", "slice_hrn"),
209 ("stop", "slice_hrn"),
210 ("reset", "slice_hrn"),
211 ("renew", "slice_hrn time"),
212 ("shutdown", "slice_hrn"),
213 ("get_ticket", "slice_hrn rspec"),
214 ("redeem_ticket", "ticket"),
215 ("delegate", "name"),
216 ("create_gid", "[name]"),
217 ("get_trusted_certs", "cred"),
220 def print_command_help (self, options):
221 verbose=getattr(options,'verbose')
222 format3="%18s %-15s %s"
225 print format3%("command","cmd_args","description")
229 self.create_parser().print_help()
230 for command in self.available_names:
231 args=self.available_dict[command]
232 method=getattr(self,command,None)
234 if method: doc=getattr(method,'__doc__',"")
235 if not doc: doc="*** no doc found ***"
236 doc=doc.strip(" \t\n")
237 doc=doc.replace("\n","\n"+35*' ')
240 print format3%(command,args,doc)
242 self.create_command_parser(command).print_help()
244 def create_command_parser(self, command):
245 if command not in self.available_dict:
246 msg="Invalid command\n"
248 msg += ','.join(self.available_names)
249 self.logger.critical(msg)
252 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
253 % (command, self.available_dict[command]))
255 # user specifies remote aggregate/sm/component
256 if command in ("resources", "slices", "create", "delete", "start", "stop",
257 "restart", "shutdown", "get_ticket", "renew", "status"):
258 parser.add_option("-c", "--component", dest="component", default=None,
259 help="component hrn")
260 parser.add_option("-d", "--delegate", dest="delegate", default=None,
262 help="Include a credential delegated to the user's root"+\
263 "authority in set of credentials for this call")
265 # registy filter option
266 if command in ("list", "show", "remove"):
267 parser.add_option("-t", "--type", dest="type", type="choice",
268 help="type filter ([all]|user|slice|authority|node|aggregate)",
269 choices=("all", "user", "slice", "authority", "node", "aggregate"),
272 if command in ("resources"):
273 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
274 help="schema type and version of resulting RSpec")
275 parser.add_option("-f", "--format", dest="format", type="choice",
276 help="display format ([xml]|dns|ip)", default="xml",
277 choices=("xml", "dns", "ip"))
278 #panos: a new option to define the type of information about resources a user is interested in
279 parser.add_option("-i", "--info", dest="info",
280 help="optional component information", default=None)
283 # 'create' does return the new rspec, makes sense to save that too
284 if command in ("resources", "show", "list", "create_gid", 'create'):
285 parser.add_option("-o", "--output", dest="file",
286 help="output XML to file", metavar="FILE", default=None)
288 if command in ("show", "list"):
289 parser.add_option("-f", "--format", dest="format", type="choice",
290 help="display format ([text]|xml)", default="text",
291 choices=("text", "xml"))
293 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
294 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
295 choices=("xml", "xmllist", "hrnlist"))
297 if command in ("status", "version"):
298 parser.add_option("-o", "--output", dest="file",
299 help="output dictionary to file", metavar="FILE", default=None)
300 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
301 help="output file format ([text]|pickled)", default="text",
302 choices=("text","pickled"))
304 if command in ("delegate"):
305 parser.add_option("-u", "--user",
306 action="store_true", dest="delegate_user", default=False,
307 help="delegate user credential")
308 parser.add_option("-s", "--slice", dest="delegate_slice",
309 help="delegate slice credential", metavar="HRN", default=None)
311 if command in ("version"):
312 parser.add_option("-R","--registry-version",
313 action="store_true", dest="version_registry", default=False,
314 help="probe registry version instead of sliceapi")
315 parser.add_option("-l","--local",
316 action="store_true", dest="version_local", default=False,
317 help="display version of the local client")
322 def create_parser(self):
324 # Generate command line parser
325 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
326 description="Commands: %s"%(" ".join(self.available_names)))
327 parser.add_option("-r", "--registry", dest="registry",
328 help="root registry", metavar="URL", default=None)
329 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
330 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
331 parser.add_option("-d", "--dir", dest="sfi_dir",
332 help="config & working directory - default is %default",
333 metavar="PATH", default=Sfi.default_sfi_dir())
334 parser.add_option("-u", "--user", dest="user",
335 help="user name", metavar="HRN", default=None)
336 parser.add_option("-a", "--auth", dest="auth",
337 help="authority name", metavar="HRN", default=None)
338 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
339 help="verbose mode - cumulative")
340 parser.add_option("-D", "--debug",
341 action="store_true", dest="debug", default=False,
342 help="Debug (xml-rpc) protocol messages")
343 # would it make sense to use ~/.ssh/id_rsa as a default here ?
344 parser.add_option("-k", "--private-key",
345 action="store", dest="user_private_key", default=None,
346 help="point to the private key file to use if not yet installed in sfi_dir")
347 parser.add_option("-t", "--timeout", dest="timeout", default=None,
348 help="Amout of time to wait before timing out the request")
349 parser.add_option("-?", "--commands",
350 action="store_true", dest="command_help", default=False,
351 help="one page summary on commands & exit")
352 parser.disable_interspersed_args()
357 def print_help (self):
358 self.sfi_parser.print_help()
359 self.command_parser.print_help()
362 # Main: parse arguments and dispatch to command
364 def dispatch(self, command, command_options, command_args):
365 return getattr(self, command)(command_options, command_args)
368 self.sfi_parser = self.create_parser()
369 (options, args) = self.sfi_parser.parse_args()
370 if options.command_help:
371 self.print_command_help(options)
373 self.options = options
375 self.logger.setLevelFromOptVerbose(self.options.verbose)
378 self.logger.critical("No command given. Use -h for help.")
379 self.print_command_help(options)
383 self.command_parser = self.create_command_parser(command)
384 (command_options, command_args) = self.command_parser.parse_args(args[1:])
385 self.command_options = command_options
389 self.logger.info("Command=%s" % command)
392 self.dispatch(command, command_options, command_args)
394 self.logger.critical ("Unknown command %s"%command)
401 def read_config(self):
402 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
404 config = Config (config_file)
406 self.logger.critical("Failed to read configuration file %s"%config_file)
407 self.logger.info("Make sure to remove the export clauses and to add quotes")
408 if self.options.verbose==0:
409 self.logger.info("Re-run with -v for more details")
411 self.logger.log_exc("Could not read config file %s"%config_file)
416 if (self.options.sm is not None):
417 self.sm_url = self.options.sm
418 elif hasattr(config, "SFI_SM"):
419 self.sm_url = config.SFI_SM
421 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
425 if (self.options.registry is not None):
426 self.reg_url = self.options.registry
427 elif hasattr(config, "SFI_REGISTRY"):
428 self.reg_url = config.SFI_REGISTRY
430 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
434 if (self.options.user is not None):
435 self.user = self.options.user
436 elif hasattr(config, "SFI_USER"):
437 self.user = config.SFI_USER
439 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
443 if (self.options.auth is not None):
444 self.authority = self.options.auth
445 elif hasattr(config, "SFI_AUTH"):
446 self.authority = config.SFI_AUTH
448 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
455 # Get various credential and spec files
457 # Establishes limiting conventions
458 # - conflates MAs and SAs
459 # - assumes last token in slice name is unique
461 # Bootstraps credentials
462 # - bootstrap user credential from self-signed certificate
463 # - bootstrap authority credential from user credential
464 # - bootstrap slice credential from user credential
467 # init self-signed cert, user credentials and gid
468 def bootstrap (self):
469 bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
470 # if -k is provided, use this to initialize private key
471 if self.options.user_private_key:
472 bootstrap.init_private_key_if_missing (self.options.user_private_key)
474 # trigger legacy compat code if needed
475 # the name has changed from just <leaf>.pkey to <hrn>.pkey
476 if not os.path.isfile(bootstrap.private_key_filename()):
477 self.logger.info ("private key not found, trying legacy name")
479 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
480 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
481 bootstrap.init_private_key_if_missing (legacy_private_key)
482 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
484 self.logger.log_exc("Can't find private key ")
488 bootstrap.bootstrap_my_gid()
489 # extract what's needed
490 self.private_key = bootstrap.private_key()
491 self.my_credential_string = bootstrap.my_credential_string ()
492 self.my_gid = bootstrap.my_gid ()
493 self.bootstrap = bootstrap
496 def my_authority_credential_string(self):
497 if not self.authority:
498 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
500 return self.bootstrap.authority_credential_string (self.authority)
502 def slice_credential_string(self, name):
503 return self.bootstrap.slice_credential_string (name)
505 # xxx should be supported by sfaclientbootstrap as well
506 def delegate_cred(self, object_cred, hrn, type='authority'):
507 # the gid and hrn of the object we are delegating
508 if isinstance(object_cred, str):
509 object_cred = Credential(string=object_cred)
510 object_gid = object_cred.get_gid_object()
511 object_hrn = object_gid.get_hrn()
513 if not object_cred.get_privileges().get_all_delegate():
514 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
517 # the delegating user's gid
518 caller_gidfile = self.my_gid()
520 # the gid of the user who will be delegated to
521 delegee_gid = self.bootstrap.gid(hrn,type)
522 delegee_hrn = delegee_gid.get_hrn()
523 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
524 return dcred.save_to_string(save_parents=True)
527 # Management of the servers
532 if not hasattr (self, 'registry_proxy'):
533 self.logger.info("Contacting Registry at: %s"%self.reg_url)
534 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
535 timeout=self.options.timeout, verbose=self.options.debug)
536 return self.registry_proxy
540 if not hasattr (self, 'sliceapi_proxy'):
541 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
542 if hasattr(self.command_options,'component') and self.command_options.component:
543 # resolve the hrn at the registry
544 node_hrn = self.command_options.component
545 records = self.registry().Resolve(node_hrn, self.my_credential_string)
546 records = filter_records('node', records)
548 self.logger.warning("No such component:%r"% opts.component)
550 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
551 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
553 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
554 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
555 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
556 timeout=self.options.timeout, verbose=self.options.debug)
557 return self.sliceapi_proxy
559 def get_cached_server_version(self, server):
560 # check local cache first
563 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
564 cache_key = server.url + "-version"
566 cache = Cache(cache_file)
569 self.logger.info("Local cache not found at: %s" % cache_file)
572 version = cache.get(cache_key)
575 result = server.GetVersion()
576 version= ReturnValue.get_value(result)
577 # cache version for 20 minutes
578 cache.add(cache_key, version, ttl= 60*20)
579 self.logger.info("Updating cache file %s" % cache_file)
580 cache.save_to_file(cache_file)
584 ### resurrect this temporarily so we can support V1 aggregates for a while
585 def server_supports_options_arg(self, server):
587 Returns true if server support the optional call_id arg, false otherwise.
589 server_version = self.get_cached_server_version(server)
591 # xxx need to rewrite this
592 if int(server_version.get('geni_api')) >= 2:
596 def server_supports_call_id_arg(self, server):
597 server_version = self.get_cached_server_version(server)
599 if 'sfa' in server_version and 'code_tag' in server_version:
600 code_tag = server_version['code_tag']
601 code_tag_parts = code_tag.split("-")
602 version_parts = code_tag_parts[0].split(".")
603 major, minor = version_parts[0], version_parts[1]
604 rev = code_tag_parts[1]
605 if int(major) == 1 and minor == 0 and build >= 22:
609 ### ois = options if supported
610 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
611 def ois (self, server, option_dict):
612 if self.server_supports_options_arg (server):
614 elif self.server_supports_call_id_arg (server):
615 return [ unique_call_id () ]
619 ### cis = call_id if supported - like ois
620 def cis (self, server):
621 if self.server_supports_call_id_arg (server):
622 return [ unique_call_id ]
626 ######################################## miscell utilities
627 def get_rspec_file(self, rspec):
628 if (os.path.isabs(rspec)):
631 file = os.path.join(self.options.sfi_dir, rspec)
632 if (os.path.isfile(file)):
635 self.logger.critical("No such rspec file %s"%rspec)
638 def get_record_file(self, record):
639 if (os.path.isabs(record)):
642 file = os.path.join(self.options.sfi_dir, record)
643 if (os.path.isfile(file)):
646 self.logger.critical("No such registry record file %s"%record)
650 #==========================================================================
651 # Following functions implement the commands
653 # Registry-related commands
654 #==========================================================================
656 def version(self, options, args):
658 display an SFA server version (GetVersion)
659 or version information about sfi itself
661 if options.version_local:
662 version=version_core()
664 if options.version_registry:
665 server=self.registry()
667 server = self.sliceapi()
668 result = server.GetVersion()
669 version = ReturnValue.get_value(result)
670 pprinter = PrettyPrinter(indent=4)
671 pprinter.pprint(version)
673 save_variable_to_file(version, options.file, options.fileformat)
675 def list(self, options, args):
677 list entries in named authority registry (List)
684 list = self.registry().List(hrn, self.my_credential_string)
686 raise Exception, "Not enough parameters for the 'list' command"
688 # filter on person, slice, site, node, etc.
689 # THis really should be in the self.filter_records funct def comment...
690 list = filter_records(options.type, list)
692 print "%s (%s)" % (record['hrn'], record['type'])
694 save_records_to_file(options.file, list, options.fileformat)
697 def show(self, options, args):
699 show details about named registry record (Resolve)
705 records = self.registry().Resolve(hrn, self.my_credential_string)
706 records = filter_records(options.type, records)
708 self.logger.error("No record of type %s"% options.type)
709 for record in records:
710 if record['type'] in ['user']:
711 record = UserRecord(dict=record)
712 elif record['type'] in ['slice']:
713 record = SliceRecord(dict=record)
714 elif record['type'] in ['node']:
715 record = NodeRecord(dict=record)
716 elif record['type'].startswith('authority'):
717 record = AuthorityRecord(dict=record)
719 record = SfaRecord(dict=record)
720 if (options.format == "text"):
723 print record.save_to_string()
725 save_records_to_file(options.file, records, options.fileformat)
728 def add(self, options, args):
729 "add record into registry from xml file (Register)"
730 auth_cred = self.my_authority_credential_string()
734 record_filepath = args[0]
735 rec_file = self.get_record_file(record_filepath)
736 record = load_record_from_file(rec_file).as_dict()
737 return self.registry().Register(record, auth_cred)
739 def update(self, options, args):
740 "update record into registry from xml file (Update)"
744 rec_file = self.get_record_file(args[0])
745 record = load_record_from_file(rec_file)
746 if record['type'] == "user":
747 if record.get_name() == self.user:
748 cred = self.my_credential_string
750 cred = self.my_authority_credential_string()
751 elif record['type'] in ["slice"]:
753 cred = self.slice_credential_string(record.get_name())
754 except ServerException, e:
755 # XXX smbaker -- once we have better error return codes, update this
756 # to do something better than a string compare
757 if "Permission error" in e.args[0]:
758 cred = self.my_authority_credential_string()
761 elif record.get_type() in ["authority"]:
762 cred = self.my_authority_credential_string()
763 elif record.get_type() == 'node':
764 cred = self.my_authority_credential_string()
766 raise "unknown record type" + record.get_type()
767 record = record.as_dict()
768 return self.registry().Update(record, cred)
770 def remove(self, options, args):
771 "remove registry record by name (Remove)"
772 auth_cred = self.my_authority_credential_string()
780 return self.registry().Remove(hrn, auth_cred, type)
782 # ==================================================================
783 # Slice-related commands
784 # ==================================================================
786 def slices(self, options, args):
787 "list instantiated slices (ListSlices) - returns urn's"
788 server = self.sliceapi()
790 creds = [self.my_credential_string]
792 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
793 creds.append(delegated_cred)
794 # options and call_id when supported
796 api_options['call_id']=unique_call_id()
797 result = server.ListSlices(creds, *self.ois(server,api_options))
798 value = ReturnValue.get_value(result)
802 # show rspec for named slice
803 def resources(self, options, args):
805 with no arg, discover available resources, (ListResources)
806 or with an slice hrn, shows currently provisioned resources
808 server = self.sliceapi()
813 creds.append(self.slice_credential_string(args[0]))
815 creds.append(self.my_credential_string)
817 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
820 if self.server_supports_options_arg(server):
821 # with v2 everything goes in options inclusing the subject slice
825 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
827 api_options['info'] = options.info
828 if options.rspec_version:
829 version_manager = VersionManager()
830 server_version = self.get_cached_server_version(server)
831 if 'sfa' in server_version:
832 # just request the version the client wants
833 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
835 # this must be a protogeni aggregate. We should request a v2 ad rspec
836 # regardless of what the client user requested
837 api_options['geni_rspec_version'] = version_manager.get_version('ProtoGENI 2').to_dict()
839 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
840 # always send call_id to v2 servers
841 api_options ['call_id'] = unique_call_id()
843 result = server.ListResources (creds, api_options)
849 # xxx looks like we can pass a hrn and not a urn here ??
850 # last arg. is a raw call_id when supported
851 result = server.ListResources (creds, hrn, *self.cis(server))
853 result = server.ListResources (creds, *self.cis(server))
854 value = ReturnValue.get_value(result)
855 if options.file is None:
856 display_rspec(value, options.format)
858 save_rspec_to_file(value, options.file)
861 def create(self, options, args):
863 create or update named slice with given rspec
865 server = self.sliceapi()
867 # xxx do we need to check usage (len(args)) ?
870 slice_urn = hrn_to_urn(slice_hrn, 'slice')
873 creds = [self.slice_credential_string(slice_hrn)]
874 delegated_cred = None
875 server_version = self.get_cached_server_version(server)
876 if server_version.get('interface') == 'slicemgr':
877 # delegate our cred to the slice manager
878 # do not delegate cred to slicemgr...not working at the moment
880 #if server_version.get('hrn'):
881 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
882 #elif server_version.get('urn'):
883 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
886 rspec_file = self.get_rspec_file(args[1])
887 rspec = open(rspec_file).read()
890 # need to pass along user keys to the aggregate.
892 # { urn: urn:publicid:IDN+emulab.net+user+alice
893 # keys: [<ssh key A>, <ssh key B>]
896 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
897 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
898 slice_record = slice_records[0]
899 user_hrns = slice_record['researcher']
900 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
901 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
903 if 'sfa' not in server_version:
904 users = pg_users_arg(user_records)
906 rspec.filter({'component_manager_id': server_version['urn']})
907 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
909 users = sfa_users_arg(user_records, slice_record)
911 # do not append users, keys, or slice tags. Anything
912 # not contained in this request will be removed from the slice
914 # CreateSliver has supported the options argument for a while now so it should
915 # be safe to assume this server support it
917 api_options ['append'] = False
918 api_options ['call_id'] = unique_call_id()
920 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
921 value = ReturnValue.get_value(result)
922 if options.file is None:
925 save_rspec_to_file (value, options.file)
928 def delete(self, options, args):
930 delete named slice (DeleteSliver)
932 server = self.sliceapi()
936 slice_urn = hrn_to_urn(slice_hrn, 'slice')
939 slice_cred = self.slice_credential_string(slice_hrn)
942 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
943 creds.append(delegated_cred)
945 # options and call_id when supported
947 api_options ['call_id'] = unique_call_id()
948 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
949 # xxx no ReturnValue ??
952 def status(self, options, args):
954 retrieve slice status (SliverStatus)
956 server = self.sliceapi()
960 slice_urn = hrn_to_urn(slice_hrn, 'slice')
963 slice_cred = self.slice_credential_string(slice_hrn)
966 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
967 creds.append(delegated_cred)
969 # options and call_id when supported
971 api_options['call_id']=unique_call_id()
972 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
973 value = ReturnValue.get_value(result)
976 save_variable_to_file(value, options.file, options.fileformat)
978 def start(self, options, args):
980 start named slice (Start)
982 server = self.sliceapi()
986 slice_urn = hrn_to_urn(slice_hrn, 'slice')
989 slice_cred = self.slice_credential_string(args[0])
992 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
993 creds.append(delegated_cred)
994 # xxx Thierry - does this not need an api_options as well ?
995 return server.Start(slice_urn, creds)
997 def stop(self, options, args):
999 stop named slice (Stop)
1001 server = self.sliceapi()
1004 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1006 slice_cred = self.slice_credential_string(args[0])
1007 creds = [slice_cred]
1008 if options.delegate:
1009 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1010 creds.append(delegated_cred)
1011 return server.Stop(slice_urn, creds)
1014 def reset(self, options, args):
1016 reset named slice (reset_slice)
1018 server = self.sliceapi()
1021 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1023 slice_cred = self.slice_credential_string(args[0])
1024 creds = [slice_cred]
1025 if options.delegate:
1026 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1027 creds.append(delegated_cred)
1028 return server.reset_slice(creds, slice_urn)
1030 def renew(self, options, args):
1032 renew slice (RenewSliver)
1034 server = self.sliceapi()
1037 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1039 slice_cred = self.slice_credential_string(args[0])
1040 creds = [slice_cred]
1041 if options.delegate:
1042 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1043 creds.append(delegated_cred)
1046 # options and call_id when supported
1048 api_options['call_id']=unique_call_id()
1049 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1050 value = ReturnValue.get_value(result)
1054 def shutdown(self, options, args):
1056 shutdown named slice (Shutdown)
1058 server = self.sliceapi()
1061 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)
1068 return server.Shutdown(slice_urn, creds)
1071 def get_ticket(self, options, args):
1073 get a ticket for the specified slice
1075 server = self.sliceapi()
1077 slice_hrn, rspec_path = args[0], args[1]
1078 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1080 slice_cred = self.slice_credential_string(slice_hrn)
1081 creds = [slice_cred]
1082 if options.delegate:
1083 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1084 creds.append(delegated_cred)
1086 rspec_file = self.get_rspec_file(rspec_path)
1087 rspec = open(rspec_file).read()
1088 # options and call_id when supported
1090 api_options['call_id']=unique_call_id()
1091 # get ticket at the server
1092 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1094 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1095 self.logger.info("writing ticket to %s"%file)
1096 ticket = SfaTicket(string=ticket_string)
1097 ticket.save_to_file(filename=file, save_parents=True)
1099 def redeem_ticket(self, options, args):
1101 Connects to nodes in a slice and redeems a ticket
1102 (slice hrn is retrieved from the ticket)
1104 ticket_file = args[0]
1106 # get slice hrn from the ticket
1107 # use this to get the right slice credential
1108 ticket = SfaTicket(filename=ticket_file)
1110 ticket_string = ticket.save_to_string(save_parents=True)
1112 slice_hrn = ticket.gidObject.get_hrn()
1113 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1114 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1115 slice_cred = self.slice_credential_string(slice_hrn)
1117 # get a list of node hostnames from the RSpec
1118 tree = etree.parse(StringIO(ticket.rspec))
1119 root = tree.getroot()
1120 hostnames = root.xpath("./network/site/node/hostname/text()")
1122 # create an xmlrpc connection to the component manager at each of these
1123 # components and gall redeem_ticket
1125 for hostname in hostnames:
1127 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1128 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1129 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1130 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1131 timeout=self.options.timeout, verbose=self.options.debug)
1132 server.RedeemTicket(ticket_string, slice_cred)
1133 self.logger.info("Success")
1134 except socket.gaierror:
1135 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1136 except Exception, e:
1137 self.logger.log_exc(e.message)
1140 def create_gid(self, options, args):
1142 Create a GID (CreateGid)
1147 target_hrn = args[0]
1148 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1150 filename = options.file
1152 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1153 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1154 GID(string=gid).save_to_file(filename)
1157 def delegate(self, options, args):
1159 (locally) create delegate credential for use by given hrn
1161 delegee_hrn = args[0]
1162 if options.delegate_user:
1163 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1164 elif options.delegate_slice:
1165 slice_cred = self.slice_credential_string(options.delegate_slice)
1166 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1168 self.logger.warning("Must specify either --user or --slice <hrn>")
1170 delegated_cred = Credential(string=cred)
1171 object_hrn = delegated_cred.get_gid_object().get_hrn()
1172 if options.delegate_user:
1173 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1174 + get_leaf(object_hrn) + ".cred")
1175 elif options.delegate_slice:
1176 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1177 + get_leaf(object_hrn) + ".cred")
1179 delegated_cred.save_to_file(dest_fn, save_parents=True)
1181 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1183 def get_trusted_certs(self, options, args):
1185 return uhe trusted certs at this interface (get_trusted_certs)
1187 trusted_certs = self.registry().get_trusted_certs()
1188 for trusted_cert in trusted_certs:
1189 gid = GID(string=trusted_cert)
1191 cert = Certificate(string=trusted_cert)
1192 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())