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 rec_record = Record(dict=record_dict)
151 str = record.save_to_string()
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"),
224 def print_command_help (self, options):
225 verbose=getattr(options,'verbose')
226 format3="%18s %-15s %s"
229 print format3%("command","cmd_args","description")
233 self.create_parser().print_help()
234 for command in self.available_names:
235 args=self.available_dict[command]
236 method=getattr(self,command,None)
238 if method: doc=getattr(method,'__doc__',"")
239 if not doc: doc="*** no doc found ***"
240 doc=doc.strip(" \t\n")
241 doc=doc.replace("\n","\n"+35*' ')
244 print format3%(command,args,doc)
246 self.create_command_parser(command).print_help()
248 def create_command_parser(self, command):
249 if command not in self.available_dict:
250 msg="Invalid command\n"
252 msg += ','.join(self.available_names)
253 self.logger.critical(msg)
256 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
257 % (command, self.available_dict[command]))
259 # user specifies remote aggregate/sm/component
260 if command in ("resources", "slices", "create", "delete", "start", "stop",
261 "restart", "shutdown", "get_ticket", "renew", "status"):
262 parser.add_option("-d", "--delegate", dest="delegate", default=None,
264 help="Include a credential delegated to the user's root"+\
265 "authority in set of credentials for this call")
267 # registy filter option
268 if command in ("list", "show", "remove"):
269 parser.add_option("-t", "--type", dest="type", type="choice",
270 help="type filter ([all]|user|slice|authority|node|aggregate)",
271 choices=("all", "user", "slice", "authority", "node", "aggregate"),
273 if command in ("resources"):
275 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
276 help="schema type and version of resulting RSpec")
277 # disable/enable cached rspecs
278 parser.add_option("-c", "--current", dest="current", default=False,
280 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
282 parser.add_option("-f", "--format", dest="format", type="choice",
283 help="display format ([xml]|dns|ip)", default="xml",
284 choices=("xml", "dns", "ip"))
285 #panos: a new option to define the type of information about resources a user is interested in
286 parser.add_option("-i", "--info", dest="info",
287 help="optional component information", default=None)
290 # 'create' does return the new rspec, makes sense to save that too
291 if command in ("resources", "show", "list", "create_gid", 'create'):
292 parser.add_option("-o", "--output", dest="file",
293 help="output XML to file", metavar="FILE", default=None)
295 if command in ("show", "list"):
296 parser.add_option("-f", "--format", dest="format", type="choice",
297 help="display format ([text]|xml)", default="text",
298 choices=("text", "xml"))
300 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
301 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
302 choices=("xml", "xmllist", "hrnlist"))
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("-R", "--raw", dest="raw", default=None,
332 help="Save raw, unparsed server response to a file")
333 parser.add_option("", "--rawformat", dest="rawformat", type="choice",
334 help="raw file format ([text]|pickled|json)", default="text",
335 choices=("text","pickled","json"))
336 parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
337 help="text string to write before and after raw output")
338 parser.add_option("-d", "--dir", dest="sfi_dir",
339 help="config & working directory - default is %default",
340 metavar="PATH", default=Sfi.default_sfi_dir())
341 parser.add_option("-u", "--user", dest="user",
342 help="user name", metavar="HRN", default=None)
343 parser.add_option("-a", "--auth", dest="auth",
344 help="authority name", metavar="HRN", default=None)
345 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
346 help="verbose mode - cumulative")
347 parser.add_option("-D", "--debug",
348 action="store_true", dest="debug", default=False,
349 help="Debug (xml-rpc) protocol messages")
350 # would it make sense to use ~/.ssh/id_rsa as a default here ?
351 parser.add_option("-k", "--private-key",
352 action="store", dest="user_private_key", default=None,
353 help="point to the private key file to use if not yet installed in sfi_dir")
354 parser.add_option("-t", "--timeout", dest="timeout", default=None,
355 help="Amout of time to wait before timing out the request")
356 parser.add_option("-?", "--commands",
357 action="store_true", dest="command_help", default=False,
358 help="one page summary on commands & exit")
359 parser.disable_interspersed_args()
364 def print_help (self):
365 self.sfi_parser.print_help()
366 self.command_parser.print_help()
369 # Main: parse arguments and dispatch to command
371 def dispatch(self, command, command_options, command_args):
372 return getattr(self, command)(command_options, command_args)
375 self.sfi_parser = self.create_parser()
376 (options, args) = self.sfi_parser.parse_args()
377 if options.command_help:
378 self.print_command_help(options)
380 self.options = options
382 self.logger.setLevelFromOptVerbose(self.options.verbose)
385 self.logger.critical("No command given. Use -h for help.")
386 self.print_command_help(options)
390 self.command_parser = self.create_command_parser(command)
391 (command_options, command_args) = self.command_parser.parse_args(args[1:])
392 self.command_options = command_options
396 self.logger.info("Command=%s" % command)
399 self.dispatch(command, command_options, command_args)
401 self.logger.critical ("Unknown command %s"%command)
408 def read_config(self):
409 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
411 config = Config (config_file)
413 self.logger.critical("Failed to read configuration file %s"%config_file)
414 self.logger.info("Make sure to remove the export clauses and to add quotes")
415 if self.options.verbose==0:
416 self.logger.info("Re-run with -v for more details")
418 self.logger.log_exc("Could not read config file %s"%config_file)
423 if (self.options.sm is not None):
424 self.sm_url = self.options.sm
425 elif hasattr(config, "SFI_SM"):
426 self.sm_url = config.SFI_SM
428 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
432 if (self.options.registry is not None):
433 self.reg_url = self.options.registry
434 elif hasattr(config, "SFI_REGISTRY"):
435 self.reg_url = config.SFI_REGISTRY
437 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
441 if (self.options.user is not None):
442 self.user = self.options.user
443 elif hasattr(config, "SFI_USER"):
444 self.user = config.SFI_USER
446 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
450 if (self.options.auth is not None):
451 self.authority = self.options.auth
452 elif hasattr(config, "SFI_AUTH"):
453 self.authority = config.SFI_AUTH
455 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
462 # Get various credential and spec files
464 # Establishes limiting conventions
465 # - conflates MAs and SAs
466 # - assumes last token in slice name is unique
468 # Bootstraps credentials
469 # - bootstrap user credential from self-signed certificate
470 # - bootstrap authority credential from user credential
471 # - bootstrap slice credential from user credential
474 # init self-signed cert, user credentials and gid
475 def bootstrap (self):
476 bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
477 # if -k is provided, use this to initialize private key
478 if self.options.user_private_key:
479 bootstrap.init_private_key_if_missing (self.options.user_private_key)
481 # trigger legacy compat code if needed
482 # the name has changed from just <leaf>.pkey to <hrn>.pkey
483 if not os.path.isfile(bootstrap.private_key_filename()):
484 self.logger.info ("private key not found, trying legacy name")
486 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
487 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
488 bootstrap.init_private_key_if_missing (legacy_private_key)
489 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
491 self.logger.log_exc("Can't find private key ")
495 bootstrap.bootstrap_my_gid()
496 # extract what's needed
497 self.private_key = bootstrap.private_key()
498 self.my_credential_string = bootstrap.my_credential_string ()
499 self.my_gid = bootstrap.my_gid ()
500 self.bootstrap = bootstrap
503 def my_authority_credential_string(self):
504 if not self.authority:
505 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
507 return self.bootstrap.authority_credential_string (self.authority)
509 def slice_credential_string(self, name):
510 return self.bootstrap.slice_credential_string (name)
512 # xxx should be supported by sfaclientbootstrap as well
513 def delegate_cred(self, object_cred, hrn, type='authority'):
514 # the gid and hrn of the object we are delegating
515 if isinstance(object_cred, str):
516 object_cred = Credential(string=object_cred)
517 object_gid = object_cred.get_gid_object()
518 object_hrn = object_gid.get_hrn()
520 if not object_cred.get_privileges().get_all_delegate():
521 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
524 # the delegating user's gid
525 caller_gidfile = self.my_gid()
527 # the gid of the user who will be delegated to
528 delegee_gid = self.bootstrap.gid(hrn,type)
529 delegee_hrn = delegee_gid.get_hrn()
530 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
531 return dcred.save_to_string(save_parents=True)
534 # Management of the servers
539 if not hasattr (self, 'registry_proxy'):
540 self.logger.info("Contacting Registry at: %s"%self.reg_url)
541 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
542 timeout=self.options.timeout, verbose=self.options.debug)
543 return self.registry_proxy
547 if not hasattr (self, 'sliceapi_proxy'):
548 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
549 if hasattr(self.command_options,'component') and self.command_options.component:
550 # resolve the hrn at the registry
551 node_hrn = self.command_options.component
552 records = self.registry().Resolve(node_hrn, self.my_credential_string)
553 records = filter_records('node', records)
555 self.logger.warning("No such component:%r"% opts.component)
557 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
558 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
560 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
561 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
562 self.sm_url = 'http://' + self.sm_url
563 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
564 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
565 timeout=self.options.timeout, verbose=self.options.debug)
566 return self.sliceapi_proxy
568 def get_cached_server_version(self, server):
569 # check local cache first
572 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
573 cache_key = server.url + "-version"
575 cache = Cache(cache_file)
578 self.logger.info("Local cache not found at: %s" % cache_file)
581 version = cache.get(cache_key)
584 result = server.GetVersion()
585 version= ReturnValue.get_value(result)
586 # cache version for 20 minutes
587 cache.add(cache_key, version, ttl= 60*20)
588 self.logger.info("Updating cache file %s" % cache_file)
589 cache.save_to_file(cache_file)
593 ### resurrect this temporarily so we can support V1 aggregates for a while
594 def server_supports_options_arg(self, server):
596 Returns true if server support the optional call_id arg, false otherwise.
598 server_version = self.get_cached_server_version(server)
600 # xxx need to rewrite this
601 if int(server_version.get('geni_api')) >= 2:
605 def server_supports_call_id_arg(self, server):
606 server_version = self.get_cached_server_version(server)
608 if 'sfa' in server_version and 'code_tag' in server_version:
609 code_tag = server_version['code_tag']
610 code_tag_parts = code_tag.split("-")
611 version_parts = code_tag_parts[0].split(".")
612 major, minor = version_parts[0], version_parts[1]
613 rev = code_tag_parts[1]
614 if int(major) == 1 and minor == 0 and build >= 22:
618 ### ois = options if supported
619 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
620 def ois (self, server, option_dict):
621 if self.server_supports_options_arg (server):
623 elif self.server_supports_call_id_arg (server):
624 return [ unique_call_id () ]
628 ### cis = call_id if supported - like ois
629 def cis (self, server):
630 if self.server_supports_call_id_arg (server):
631 return [ unique_call_id ]
635 ######################################## miscell utilities
636 def get_rspec_file(self, rspec):
637 if (os.path.isabs(rspec)):
640 file = os.path.join(self.options.sfi_dir, rspec)
641 if (os.path.isfile(file)):
644 self.logger.critical("No such rspec file %s"%rspec)
647 def get_record_file(self, record):
648 if (os.path.isabs(record)):
651 file = os.path.join(self.options.sfi_dir, record)
652 if (os.path.isfile(file)):
655 self.logger.critical("No such registry record file %s"%record)
659 #==========================================================================
660 # Following functions implement the commands
662 # Registry-related commands
663 #==========================================================================
665 def version(self, options, args):
667 display an SFA server version (GetVersion)
668 or version information about sfi itself
670 if options.version_local:
671 version=version_core()
673 if options.version_registry:
674 server=self.registry()
676 server = self.sliceapi()
677 result = server.GetVersion()
678 version = ReturnValue.get_value(result)
680 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
682 pprinter = PrettyPrinter(indent=4)
683 pprinter.pprint(version)
685 def list(self, options, args):
687 list entries in named authority registry (List)
694 list = self.registry().List(hrn, self.my_credential_string)
696 raise Exception, "Not enough parameters for the 'list' command"
698 # filter on person, slice, site, node, etc.
699 # THis really should be in the self.filter_records funct def comment...
700 list = filter_records(options.type, list)
702 print "%s (%s)" % (record['hrn'], record['type'])
704 save_records_to_file(options.file, list, options.fileformat)
707 def show(self, options, args):
709 show details about named registry record (Resolve)
715 record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
716 record_dicts = filter_records(options.type, record_dicts)
718 self.logger.error("No record of type %s"% options.type)
719 records = [ Record(dict=record_dict) for record_dict in record_dicts ]
720 for record in records:
721 if (options.format == "text"): record.dump()
722 else: print record.save_as_xml()
724 save_records_to_file(options.file, record_dicts, options.fileformat)
727 def add(self, options, args):
728 "add record into registry from xml file (Register)"
729 auth_cred = self.my_authority_credential_string()
733 record_filepath = args[0]
734 rec_file = self.get_record_file(record_filepath)
735 record = load_record_from_file(rec_file).todict()
736 return self.registry().Register(record, auth_cred)
738 def update(self, options, args):
739 "update record into registry from xml file (Update)"
743 rec_file = self.get_record_file(args[0])
744 record = load_record_from_file(rec_file)
745 if record.type == "user":
746 if record.hrn == self.user:
747 cred = self.my_credential_string
749 cred = self.my_authority_credential_string()
750 elif record.type in ["slice"]:
752 cred = self.slice_credential_string(record.hrn)
753 except ServerException, e:
754 # XXX smbaker -- once we have better error return codes, update this
755 # to do something better than a string compare
756 if "Permission error" in e.args[0]:
757 cred = self.my_authority_credential_string()
760 elif record.type in ["authority"]:
761 cred = self.my_authority_credential_string()
762 elif record.type == 'node':
763 cred = self.my_authority_credential_string()
765 raise "unknown record type" + record.type
766 record_dict = record.todict()
767 return self.registry().Update(record_dict, cred)
769 def remove(self, options, args):
770 "remove registry record by name (Remove)"
771 auth_cred = self.my_authority_credential_string()
779 return self.registry().Remove(hrn, auth_cred, type)
781 # ==================================================================
782 # Slice-related commands
783 # ==================================================================
785 def slices(self, options, args):
786 "list instantiated slices (ListSlices) - returns urn's"
787 server = self.sliceapi()
789 creds = [self.my_credential_string]
791 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
792 creds.append(delegated_cred)
793 # options and call_id when supported
795 api_options['call_id']=unique_call_id()
796 result = server.ListSlices(creds, *self.ois(server,api_options))
797 value = ReturnValue.get_value(result)
799 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
804 # show rspec for named slice
805 def resources(self, options, args):
807 with no arg, discover available resources, (ListResources)
808 or with an slice hrn, shows currently provisioned resources
810 server = self.sliceapi()
815 creds.append(self.slice_credential_string(args[0]))
817 creds.append(self.my_credential_string)
819 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
821 # no need to check if server accepts the options argument since the options has
822 # been a required argument since v1 API
824 # always send call_id to v2 servers
825 api_options ['call_id'] = unique_call_id()
826 # ask for cached value if available
827 api_options ['cached'] = True
830 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
832 api_options['info'] = options.info
834 if options.current == True:
835 api_options['cached'] = False
837 api_options['cached'] = True
838 if options.rspec_version:
839 version_manager = VersionManager()
840 server_version = self.get_cached_server_version(server)
841 if 'sfa' in server_version:
842 # just request the version the client wants
843 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
845 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
847 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
848 result = server.ListResources (creds, api_options)
849 value = ReturnValue.get_value(result)
851 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
852 if options.file is not None:
853 save_rspec_to_file(value, options.file)
854 if (self.options.raw is None) and (options.file is None):
855 display_rspec(value, options.format)
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)
921 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
922 if options.file is not None:
923 save_rspec_to_file (value, options.file)
924 if (self.options.raw is None) and (options.file is None):
929 def delete(self, options, args):
931 delete named slice (DeleteSliver)
933 server = self.sliceapi()
937 slice_urn = hrn_to_urn(slice_hrn, 'slice')
940 slice_cred = self.slice_credential_string(slice_hrn)
943 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
944 creds.append(delegated_cred)
946 # options and call_id when supported
948 api_options ['call_id'] = unique_call_id()
949 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
950 value = ReturnValue.get_value(result)
952 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
957 def status(self, options, args):
959 retrieve slice status (SliverStatus)
961 server = self.sliceapi()
965 slice_urn = hrn_to_urn(slice_hrn, 'slice')
968 slice_cred = self.slice_credential_string(slice_hrn)
971 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
972 creds.append(delegated_cred)
974 # options and call_id when supported
976 api_options['call_id']=unique_call_id()
977 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
978 value = ReturnValue.get_value(result)
980 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
984 def start(self, options, args):
986 start named slice (Start)
988 server = self.sliceapi()
992 slice_urn = hrn_to_urn(slice_hrn, 'slice')
995 slice_cred = self.slice_credential_string(args[0])
998 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
999 creds.append(delegated_cred)
1000 # xxx Thierry - does this not need an api_options as well ?
1001 result = server.Start(slice_urn, creds)
1002 value = ReturnValue.get_value(result)
1003 if self.options.raw:
1004 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1009 def stop(self, options, args):
1011 stop named slice (Stop)
1013 server = self.sliceapi()
1016 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1018 slice_cred = self.slice_credential_string(args[0])
1019 creds = [slice_cred]
1020 if options.delegate:
1021 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1022 creds.append(delegated_cred)
1023 result = server.Stop(slice_urn, creds)
1024 value = ReturnValue.get_value(result)
1025 if self.options.raw:
1026 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1032 def reset(self, options, args):
1034 reset named slice (reset_slice)
1036 server = self.sliceapi()
1039 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1041 slice_cred = self.slice_credential_string(args[0])
1042 creds = [slice_cred]
1043 if options.delegate:
1044 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1045 creds.append(delegated_cred)
1046 result = server.reset_slice(creds, slice_urn)
1047 value = ReturnValue.get_value(result)
1048 if self.options.raw:
1049 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1054 def renew(self, options, args):
1056 renew slice (RenewSliver)
1058 server = self.sliceapi()
1061 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1063 slice_cred = self.slice_credential_string(args[0])
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)
1070 # options and call_id when supported
1072 api_options['call_id']=unique_call_id()
1073 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1074 value = ReturnValue.get_value(result)
1075 if self.options.raw:
1076 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1082 def shutdown(self, options, args):
1084 shutdown named slice (Shutdown)
1086 server = self.sliceapi()
1089 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1091 slice_cred = self.slice_credential_string(slice_hrn)
1092 creds = [slice_cred]
1093 if options.delegate:
1094 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1095 creds.append(delegated_cred)
1096 result = server.Shutdown(slice_urn, creds)
1097 value = ReturnValue.get_value(result)
1098 if self.options.raw:
1099 save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1105 def get_ticket(self, options, args):
1107 get a ticket for the specified slice
1109 server = self.sliceapi()
1111 slice_hrn, rspec_path = args[0], args[1]
1112 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1114 slice_cred = self.slice_credential_string(slice_hrn)
1115 creds = [slice_cred]
1116 if options.delegate:
1117 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1118 creds.append(delegated_cred)
1120 rspec_file = self.get_rspec_file(rspec_path)
1121 rspec = open(rspec_file).read()
1122 # options and call_id when supported
1124 api_options['call_id']=unique_call_id()
1125 # get ticket at the server
1126 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1128 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1129 self.logger.info("writing ticket to %s"%file)
1130 ticket = SfaTicket(string=ticket_string)
1131 ticket.save_to_file(filename=file, save_parents=True)
1133 def redeem_ticket(self, options, args):
1135 Connects to nodes in a slice and redeems a ticket
1136 (slice hrn is retrieved from the ticket)
1138 ticket_file = args[0]
1140 # get slice hrn from the ticket
1141 # use this to get the right slice credential
1142 ticket = SfaTicket(filename=ticket_file)
1144 ticket_string = ticket.save_to_string(save_parents=True)
1146 slice_hrn = ticket.gidObject.get_hrn()
1147 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1148 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1149 slice_cred = self.slice_credential_string(slice_hrn)
1151 # get a list of node hostnames from the RSpec
1152 tree = etree.parse(StringIO(ticket.rspec))
1153 root = tree.getroot()
1154 hostnames = root.xpath("./network/site/node/hostname/text()")
1156 # create an xmlrpc connection to the component manager at each of these
1157 # components and gall redeem_ticket
1159 for hostname in hostnames:
1161 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1162 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1163 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1164 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1165 timeout=self.options.timeout, verbose=self.options.debug)
1166 server.RedeemTicket(ticket_string, slice_cred)
1167 self.logger.info("Success")
1168 except socket.gaierror:
1169 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1170 except Exception, e:
1171 self.logger.log_exc(e.message)
1174 def create_gid(self, options, args):
1176 Create a GID (CreateGid)
1181 target_hrn = args[0]
1182 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1184 filename = options.file
1186 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1187 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1188 GID(string=gid).save_to_file(filename)
1191 def delegate(self, options, args):
1193 (locally) create delegate credential for use by given hrn
1195 delegee_hrn = args[0]
1196 if options.delegate_user:
1197 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1198 elif options.delegate_slice:
1199 slice_cred = self.slice_credential_string(options.delegate_slice)
1200 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1202 self.logger.warning("Must specify either --user or --slice <hrn>")
1204 delegated_cred = Credential(string=cred)
1205 object_hrn = delegated_cred.get_gid_object().get_hrn()
1206 if options.delegate_user:
1207 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1208 + get_leaf(object_hrn) + ".cred")
1209 elif options.delegate_slice:
1210 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1211 + get_leaf(object_hrn) + ".cred")
1213 delegated_cred.save_to_file(dest_fn, save_parents=True)
1215 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1217 def get_trusted_certs(self, options, args):
1219 return uhe trusted certs at this interface (get_trusted_certs)
1221 trusted_certs = self.registry().get_trusted_certs()
1222 for trusted_cert in trusted_certs:
1223 gid = GID(string=trusted_cert)
1225 cert = Certificate(string=trusted_cert)
1226 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())