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("-d", "--delegate", dest="delegate", default=None,
260 help="Include a credential delegated to the user's root"+\
261 "authority in set of credentials for this call")
263 # registy filter option
264 if command in ("list", "show", "remove"):
265 parser.add_option("-t", "--type", dest="type", type="choice",
266 help="type filter ([all]|user|slice|authority|node|aggregate)",
267 choices=("all", "user", "slice", "authority", "node", "aggregate"),
269 if command in ("resources"):
271 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
272 help="schema type and version of resulting RSpec")
273 # disable/enable cached rspecs
274 parser.add_option("-c", "--current", dest="current", default=False,
276 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
278 parser.add_option("-f", "--format", dest="format", type="choice",
279 help="display format ([xml]|dns|ip)", default="xml",
280 choices=("xml", "dns", "ip"))
281 #panos: a new option to define the type of information about resources a user is interested in
282 parser.add_option("-i", "--info", dest="info",
283 help="optional component information", default=None)
286 # 'create' does return the new rspec, makes sense to save that too
287 if command in ("resources", "show", "list", "create_gid", 'create'):
288 parser.add_option("-o", "--output", dest="file",
289 help="output XML to file", metavar="FILE", default=None)
291 if command in ("show", "list"):
292 parser.add_option("-f", "--format", dest="format", type="choice",
293 help="display format ([text]|xml)", default="text",
294 choices=("text", "xml"))
296 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
297 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
298 choices=("xml", "xmllist", "hrnlist"))
300 if command in ("status", "version"):
301 parser.add_option("-o", "--output", dest="file",
302 help="output dictionary to file", metavar="FILE", default=None)
303 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
304 help="output file format ([text]|pickled)", default="text",
305 choices=("text","pickled"))
307 if command in ("delegate"):
308 parser.add_option("-u", "--user",
309 action="store_true", dest="delegate_user", default=False,
310 help="delegate user credential")
311 parser.add_option("-s", "--slice", dest="delegate_slice",
312 help="delegate slice credential", metavar="HRN", default=None)
314 if command in ("version"):
315 parser.add_option("-R","--registry-version",
316 action="store_true", dest="version_registry", default=False,
317 help="probe registry version instead of sliceapi")
318 parser.add_option("-l","--local",
319 action="store_true", dest="version_local", default=False,
320 help="display version of the local client")
325 def create_parser(self):
327 # Generate command line parser
328 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
329 description="Commands: %s"%(" ".join(self.available_names)))
330 parser.add_option("-r", "--registry", dest="registry",
331 help="root registry", metavar="URL", default=None)
332 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
333 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
334 parser.add_option("-d", "--dir", dest="sfi_dir",
335 help="config & working directory - default is %default",
336 metavar="PATH", default=Sfi.default_sfi_dir())
337 parser.add_option("-u", "--user", dest="user",
338 help="user name", metavar="HRN", default=None)
339 parser.add_option("-a", "--auth", dest="auth",
340 help="authority name", metavar="HRN", default=None)
341 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
342 help="verbose mode - cumulative")
343 parser.add_option("-D", "--debug",
344 action="store_true", dest="debug", default=False,
345 help="Debug (xml-rpc) protocol messages")
346 # would it make sense to use ~/.ssh/id_rsa as a default here ?
347 parser.add_option("-k", "--private-key",
348 action="store", dest="user_private_key", default=None,
349 help="point to the private key file to use if not yet installed in sfi_dir")
350 parser.add_option("-t", "--timeout", dest="timeout", default=None,
351 help="Amout of time to wait before timing out the request")
352 parser.add_option("-?", "--commands",
353 action="store_true", dest="command_help", default=False,
354 help="one page summary on commands & exit")
355 parser.disable_interspersed_args()
360 def print_help (self):
361 self.sfi_parser.print_help()
362 self.command_parser.print_help()
365 # Main: parse arguments and dispatch to command
367 def dispatch(self, command, command_options, command_args):
368 return getattr(self, command)(command_options, command_args)
371 self.sfi_parser = self.create_parser()
372 (options, args) = self.sfi_parser.parse_args()
373 if options.command_help:
374 self.print_command_help(options)
376 self.options = options
378 self.logger.setLevelFromOptVerbose(self.options.verbose)
381 self.logger.critical("No command given. Use -h for help.")
382 self.print_command_help(options)
386 self.command_parser = self.create_command_parser(command)
387 (command_options, command_args) = self.command_parser.parse_args(args[1:])
388 self.command_options = command_options
392 self.logger.info("Command=%s" % command)
395 self.dispatch(command, command_options, command_args)
397 self.logger.critical ("Unknown command %s"%command)
404 def read_config(self):
405 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
407 config = Config (config_file)
409 self.logger.critical("Failed to read configuration file %s"%config_file)
410 self.logger.info("Make sure to remove the export clauses and to add quotes")
411 if self.options.verbose==0:
412 self.logger.info("Re-run with -v for more details")
414 self.logger.log_exc("Could not read config file %s"%config_file)
419 if (self.options.sm is not None):
420 self.sm_url = self.options.sm
421 elif hasattr(config, "SFI_SM"):
422 self.sm_url = config.SFI_SM
424 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
428 if (self.options.registry is not None):
429 self.reg_url = self.options.registry
430 elif hasattr(config, "SFI_REGISTRY"):
431 self.reg_url = config.SFI_REGISTRY
433 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
437 if (self.options.user is not None):
438 self.user = self.options.user
439 elif hasattr(config, "SFI_USER"):
440 self.user = config.SFI_USER
442 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
446 if (self.options.auth is not None):
447 self.authority = self.options.auth
448 elif hasattr(config, "SFI_AUTH"):
449 self.authority = config.SFI_AUTH
451 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
458 # Get various credential and spec files
460 # Establishes limiting conventions
461 # - conflates MAs and SAs
462 # - assumes last token in slice name is unique
464 # Bootstraps credentials
465 # - bootstrap user credential from self-signed certificate
466 # - bootstrap authority credential from user credential
467 # - bootstrap slice credential from user credential
470 # init self-signed cert, user credentials and gid
471 def bootstrap (self):
472 bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
473 # if -k is provided, use this to initialize private key
474 if self.options.user_private_key:
475 bootstrap.init_private_key_if_missing (self.options.user_private_key)
477 # trigger legacy compat code if needed
478 # the name has changed from just <leaf>.pkey to <hrn>.pkey
479 if not os.path.isfile(bootstrap.private_key_filename()):
480 self.logger.info ("private key not found, trying legacy name")
482 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
483 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
484 bootstrap.init_private_key_if_missing (legacy_private_key)
485 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
487 self.logger.log_exc("Can't find private key ")
491 bootstrap.bootstrap_my_gid()
492 # extract what's needed
493 self.private_key = bootstrap.private_key()
494 self.my_credential_string = bootstrap.my_credential_string ()
495 self.my_gid = bootstrap.my_gid ()
496 self.bootstrap = bootstrap
499 def my_authority_credential_string(self):
500 if not self.authority:
501 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
503 return self.bootstrap.authority_credential_string (self.authority)
505 def slice_credential_string(self, name):
506 return self.bootstrap.slice_credential_string (name)
508 # xxx should be supported by sfaclientbootstrap as well
509 def delegate_cred(self, object_cred, hrn, type='authority'):
510 # the gid and hrn of the object we are delegating
511 if isinstance(object_cred, str):
512 object_cred = Credential(string=object_cred)
513 object_gid = object_cred.get_gid_object()
514 object_hrn = object_gid.get_hrn()
516 if not object_cred.get_privileges().get_all_delegate():
517 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
520 # the delegating user's gid
521 caller_gidfile = self.my_gid()
523 # the gid of the user who will be delegated to
524 delegee_gid = self.bootstrap.gid(hrn,type)
525 delegee_hrn = delegee_gid.get_hrn()
526 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
527 return dcred.save_to_string(save_parents=True)
530 # Management of the servers
535 if not hasattr (self, 'registry_proxy'):
536 self.logger.info("Contacting Registry at: %s"%self.reg_url)
537 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
538 timeout=self.options.timeout, verbose=self.options.debug)
539 return self.registry_proxy
543 if not hasattr (self, 'sliceapi_proxy'):
544 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
545 if hasattr(self.command_options,'component') and self.command_options.component:
546 # resolve the hrn at the registry
547 node_hrn = self.command_options.component
548 records = self.registry().Resolve(node_hrn, self.my_credential_string)
549 records = filter_records('node', records)
551 self.logger.warning("No such component:%r"% opts.component)
553 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
554 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
556 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
557 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
558 self.sm_url = 'http://' + self.sm_url
559 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
560 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
561 timeout=self.options.timeout, verbose=self.options.debug)
562 return self.sliceapi_proxy
564 def get_cached_server_version(self, server):
565 # check local cache first
568 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
569 cache_key = server.url + "-version"
571 cache = Cache(cache_file)
574 self.logger.info("Local cache not found at: %s" % cache_file)
577 version = cache.get(cache_key)
580 result = server.GetVersion()
581 version= ReturnValue.get_value(result)
582 # cache version for 20 minutes
583 cache.add(cache_key, version, ttl= 60*20)
584 self.logger.info("Updating cache file %s" % cache_file)
585 cache.save_to_file(cache_file)
589 ### resurrect this temporarily so we can support V1 aggregates for a while
590 def server_supports_options_arg(self, server):
592 Returns true if server support the optional call_id arg, false otherwise.
594 server_version = self.get_cached_server_version(server)
596 # xxx need to rewrite this
597 if int(server_version.get('geni_api')) >= 2:
601 def server_supports_call_id_arg(self, server):
602 server_version = self.get_cached_server_version(server)
604 if 'sfa' in server_version and 'code_tag' in server_version:
605 code_tag = server_version['code_tag']
606 code_tag_parts = code_tag.split("-")
607 version_parts = code_tag_parts[0].split(".")
608 major, minor = version_parts[0], version_parts[1]
609 rev = code_tag_parts[1]
610 if int(major) == 1 and minor == 0 and build >= 22:
614 ### ois = options if supported
615 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
616 def ois (self, server, option_dict):
617 if self.server_supports_options_arg (server):
619 elif self.server_supports_call_id_arg (server):
620 return [ unique_call_id () ]
624 ### cis = call_id if supported - like ois
625 def cis (self, server):
626 if self.server_supports_call_id_arg (server):
627 return [ unique_call_id ]
631 ######################################## miscell utilities
632 def get_rspec_file(self, rspec):
633 if (os.path.isabs(rspec)):
636 file = os.path.join(self.options.sfi_dir, rspec)
637 if (os.path.isfile(file)):
640 self.logger.critical("No such rspec file %s"%rspec)
643 def get_record_file(self, record):
644 if (os.path.isabs(record)):
647 file = os.path.join(self.options.sfi_dir, record)
648 if (os.path.isfile(file)):
651 self.logger.critical("No such registry record file %s"%record)
655 #==========================================================================
656 # Following functions implement the commands
658 # Registry-related commands
659 #==========================================================================
661 def version(self, options, args):
663 display an SFA server version (GetVersion)
664 or version information about sfi itself
666 if options.version_local:
667 version=version_core()
669 if options.version_registry:
670 server=self.registry()
672 server = self.sliceapi()
673 result = server.GetVersion()
674 version = ReturnValue.get_value(result)
675 pprinter = PrettyPrinter(indent=4)
676 pprinter.pprint(version)
678 save_variable_to_file(version, options.file, options.fileformat)
680 def list(self, options, args):
682 list entries in named authority registry (List)
689 list = self.registry().List(hrn, self.my_credential_string)
691 raise Exception, "Not enough parameters for the 'list' command"
693 # filter on person, slice, site, node, etc.
694 # THis really should be in the self.filter_records funct def comment...
695 list = filter_records(options.type, list)
697 print "%s (%s)" % (record['hrn'], record['type'])
699 save_records_to_file(options.file, list, options.fileformat)
702 def show(self, options, args):
704 show details about named registry record (Resolve)
710 records = self.registry().Resolve(hrn, self.my_credential_string)
711 records = filter_records(options.type, records)
713 self.logger.error("No record of type %s"% options.type)
714 for record in records:
715 if record['type'] in ['user']:
716 record = UserRecord(dict=record)
717 elif record['type'] in ['slice']:
718 record = SliceRecord(dict=record)
719 elif record['type'] in ['node']:
720 record = NodeRecord(dict=record)
721 elif record['type'].startswith('authority'):
722 record = AuthorityRecord(dict=record)
724 record = SfaRecord(dict=record)
725 if (options.format == "text"):
728 print record.save_to_string()
730 save_records_to_file(options.file, records, options.fileformat)
733 def add(self, options, args):
734 "add record into registry from xml file (Register)"
735 auth_cred = self.my_authority_credential_string()
739 record_filepath = args[0]
740 rec_file = self.get_record_file(record_filepath)
741 record = load_record_from_file(rec_file).as_dict()
742 return self.registry().Register(record, auth_cred)
744 def update(self, options, args):
745 "update record into registry from xml file (Update)"
749 rec_file = self.get_record_file(args[0])
750 record = load_record_from_file(rec_file)
751 if record['type'] == "user":
752 if record.get_name() == self.user:
753 cred = self.my_credential_string
755 cred = self.my_authority_credential_string()
756 elif record['type'] in ["slice"]:
758 cred = self.slice_credential_string(record.get_name())
759 except ServerException, e:
760 # XXX smbaker -- once we have better error return codes, update this
761 # to do something better than a string compare
762 if "Permission error" in e.args[0]:
763 cred = self.my_authority_credential_string()
766 elif record.get_type() in ["authority"]:
767 cred = self.my_authority_credential_string()
768 elif record.get_type() == 'node':
769 cred = self.my_authority_credential_string()
771 raise "unknown record type" + record.get_type()
772 record = record.as_dict()
773 return self.registry().Update(record, cred)
775 def remove(self, options, args):
776 "remove registry record by name (Remove)"
777 auth_cred = self.my_authority_credential_string()
785 return self.registry().Remove(hrn, auth_cred, type)
787 # ==================================================================
788 # Slice-related commands
789 # ==================================================================
791 def slices(self, options, args):
792 "list instantiated slices (ListSlices) - returns urn's"
793 server = self.sliceapi()
795 creds = [self.my_credential_string]
797 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
798 creds.append(delegated_cred)
799 # options and call_id when supported
801 api_options['call_id']=unique_call_id()
802 result = server.ListSlices(creds, *self.ois(server,api_options))
803 value = ReturnValue.get_value(result)
807 # show rspec for named slice
808 def resources(self, options, args):
810 with no arg, discover available resources, (ListResources)
811 or with an slice hrn, shows currently provisioned resources
813 server = self.sliceapi()
818 creds.append(self.slice_credential_string(args[0]))
820 creds.append(self.my_credential_string)
822 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
824 # no need to check if server accepts the options argument since the options has
825 # been a required argument since v1 API
827 # always send call_id to v2 servers
828 api_options ['call_id'] = unique_call_id()
829 # ask for cached value if available
830 api_options ['cached'] = True
833 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
835 api_options['info'] = options.info
837 if options.current == True:
838 api_options['cached'] = False
840 api_options['cached'] = True
841 if options.rspec_version:
842 version_manager = VersionManager()
843 server_version = self.get_cached_server_version(server)
844 if 'sfa' in server_version:
845 # just request the version the client wants
846 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
848 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
850 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
851 result = server.ListResources (creds, api_options)
852 value = ReturnValue.get_value(result)
853 if options.file is None:
854 display_rspec(value, options.format)
856 save_rspec_to_file(value, options.file)
859 def create(self, options, args):
861 create or update named slice with given rspec
863 server = self.sliceapi()
865 # xxx do we need to check usage (len(args)) ?
868 slice_urn = hrn_to_urn(slice_hrn, 'slice')
871 creds = [self.slice_credential_string(slice_hrn)]
872 delegated_cred = None
873 server_version = self.get_cached_server_version(server)
874 if server_version.get('interface') == 'slicemgr':
875 # delegate our cred to the slice manager
876 # do not delegate cred to slicemgr...not working at the moment
878 #if server_version.get('hrn'):
879 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
880 #elif server_version.get('urn'):
881 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
884 rspec_file = self.get_rspec_file(args[1])
885 rspec = open(rspec_file).read()
888 # need to pass along user keys to the aggregate.
890 # { urn: urn:publicid:IDN+emulab.net+user+alice
891 # keys: [<ssh key A>, <ssh key B>]
894 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
895 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
896 slice_record = slice_records[0]
897 user_hrns = slice_record['researcher']
898 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
899 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
901 if 'sfa' not in server_version:
902 users = pg_users_arg(user_records)
904 rspec.filter({'component_manager_id': server_version['urn']})
905 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
907 users = sfa_users_arg(user_records, slice_record)
909 # do not append users, keys, or slice tags. Anything
910 # not contained in this request will be removed from the slice
912 # CreateSliver has supported the options argument for a while now so it should
913 # be safe to assume this server support it
915 api_options ['append'] = False
916 api_options ['call_id'] = unique_call_id()
918 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
919 value = ReturnValue.get_value(result)
920 if options.file is None:
923 save_rspec_to_file (value, options.file)
926 def delete(self, options, args):
928 delete named slice (DeleteSliver)
930 server = self.sliceapi()
934 slice_urn = hrn_to_urn(slice_hrn, 'slice')
937 slice_cred = self.slice_credential_string(slice_hrn)
940 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
941 creds.append(delegated_cred)
943 # options and call_id when supported
945 api_options ['call_id'] = unique_call_id()
946 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
947 # xxx no ReturnValue ??
950 def status(self, options, args):
952 retrieve slice status (SliverStatus)
954 server = self.sliceapi()
958 slice_urn = hrn_to_urn(slice_hrn, 'slice')
961 slice_cred = self.slice_credential_string(slice_hrn)
964 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
965 creds.append(delegated_cred)
967 # options and call_id when supported
969 api_options['call_id']=unique_call_id()
970 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
971 value = ReturnValue.get_value(result)
974 save_variable_to_file(value, options.file, options.fileformat)
976 def start(self, options, args):
978 start named slice (Start)
980 server = self.sliceapi()
984 slice_urn = hrn_to_urn(slice_hrn, 'slice')
987 slice_cred = self.slice_credential_string(args[0])
990 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
991 creds.append(delegated_cred)
992 # xxx Thierry - does this not need an api_options as well ?
993 return server.Start(slice_urn, creds)
995 def stop(self, options, args):
997 stop named slice (Stop)
999 server = self.sliceapi()
1002 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1004 slice_cred = self.slice_credential_string(args[0])
1005 creds = [slice_cred]
1006 if options.delegate:
1007 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1008 creds.append(delegated_cred)
1009 return server.Stop(slice_urn, creds)
1012 def reset(self, options, args):
1014 reset named slice (reset_slice)
1016 server = self.sliceapi()
1019 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1021 slice_cred = self.slice_credential_string(args[0])
1022 creds = [slice_cred]
1023 if options.delegate:
1024 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1025 creds.append(delegated_cred)
1026 return server.reset_slice(creds, slice_urn)
1028 def renew(self, options, args):
1030 renew slice (RenewSliver)
1032 server = self.sliceapi()
1035 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1037 slice_cred = self.slice_credential_string(args[0])
1038 creds = [slice_cred]
1039 if options.delegate:
1040 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1041 creds.append(delegated_cred)
1044 # options and call_id when supported
1046 api_options['call_id']=unique_call_id()
1047 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1048 value = ReturnValue.get_value(result)
1052 def shutdown(self, options, args):
1054 shutdown named slice (Shutdown)
1056 server = self.sliceapi()
1059 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1061 slice_cred = self.slice_credential_string(slice_hrn)
1062 creds = [slice_cred]
1063 if options.delegate:
1064 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1065 creds.append(delegated_cred)
1066 return server.Shutdown(slice_urn, creds)
1069 def get_ticket(self, options, args):
1071 get a ticket for the specified slice
1073 server = self.sliceapi()
1075 slice_hrn, rspec_path = args[0], args[1]
1076 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1078 slice_cred = self.slice_credential_string(slice_hrn)
1079 creds = [slice_cred]
1080 if options.delegate:
1081 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1082 creds.append(delegated_cred)
1084 rspec_file = self.get_rspec_file(rspec_path)
1085 rspec = open(rspec_file).read()
1086 # options and call_id when supported
1088 api_options['call_id']=unique_call_id()
1089 # get ticket at the server
1090 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1092 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1093 self.logger.info("writing ticket to %s"%file)
1094 ticket = SfaTicket(string=ticket_string)
1095 ticket.save_to_file(filename=file, save_parents=True)
1097 def redeem_ticket(self, options, args):
1099 Connects to nodes in a slice and redeems a ticket
1100 (slice hrn is retrieved from the ticket)
1102 ticket_file = args[0]
1104 # get slice hrn from the ticket
1105 # use this to get the right slice credential
1106 ticket = SfaTicket(filename=ticket_file)
1108 ticket_string = ticket.save_to_string(save_parents=True)
1110 slice_hrn = ticket.gidObject.get_hrn()
1111 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1112 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1113 slice_cred = self.slice_credential_string(slice_hrn)
1115 # get a list of node hostnames from the RSpec
1116 tree = etree.parse(StringIO(ticket.rspec))
1117 root = tree.getroot()
1118 hostnames = root.xpath("./network/site/node/hostname/text()")
1120 # create an xmlrpc connection to the component manager at each of these
1121 # components and gall redeem_ticket
1123 for hostname in hostnames:
1125 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1126 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1127 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1128 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1129 timeout=self.options.timeout, verbose=self.options.debug)
1130 server.RedeemTicket(ticket_string, slice_cred)
1131 self.logger.info("Success")
1132 except socket.gaierror:
1133 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1134 except Exception, e:
1135 self.logger.log_exc(e.message)
1138 def create_gid(self, options, args):
1140 Create a GID (CreateGid)
1145 target_hrn = args[0]
1146 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1148 filename = options.file
1150 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1151 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1152 GID(string=gid).save_to_file(filename)
1155 def delegate(self, options, args):
1157 (locally) create delegate credential for use by given hrn
1159 delegee_hrn = args[0]
1160 if options.delegate_user:
1161 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1162 elif options.delegate_slice:
1163 slice_cred = self.slice_credential_string(options.delegate_slice)
1164 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1166 self.logger.warning("Must specify either --user or --slice <hrn>")
1168 delegated_cred = Credential(string=cred)
1169 object_hrn = delegated_cred.get_gid_object().get_hrn()
1170 if options.delegate_user:
1171 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1172 + get_leaf(object_hrn) + ".cred")
1173 elif options.delegate_slice:
1174 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1175 + get_leaf(object_hrn) + ".cred")
1177 delegated_cred.save_to_file(dest_fn, save_parents=True)
1179 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1181 def get_trusted_certs(self, options, args):
1183 return uhe trusted certs at this interface (get_trusted_certs)
1185 trusted_certs = self.registry().get_trusted_certs()
1186 for trusted_cert in trusted_certs:
1187 gid = GID(string=trusted_cert)
1189 cert = Certificate(string=trusted_cert)
1190 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())