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 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
558 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
559 timeout=self.options.timeout, verbose=self.options.debug)
560 return self.sliceapi_proxy
562 def get_cached_server_version(self, server):
563 # check local cache first
566 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
567 cache_key = server.url + "-version"
569 cache = Cache(cache_file)
572 self.logger.info("Local cache not found at: %s" % cache_file)
575 version = cache.get(cache_key)
578 result = server.GetVersion()
579 version= ReturnValue.get_value(result)
580 # cache version for 20 minutes
581 cache.add(cache_key, version, ttl= 60*20)
582 self.logger.info("Updating cache file %s" % cache_file)
583 cache.save_to_file(cache_file)
587 ### resurrect this temporarily so we can support V1 aggregates for a while
588 def server_supports_options_arg(self, server):
590 Returns true if server support the optional call_id arg, false otherwise.
592 server_version = self.get_cached_server_version(server)
594 # xxx need to rewrite this
595 if int(server_version.get('geni_api')) >= 2:
599 def server_supports_call_id_arg(self, server):
600 server_version = self.get_cached_server_version(server)
602 if 'sfa' in server_version and 'code_tag' in server_version:
603 code_tag = server_version['code_tag']
604 code_tag_parts = code_tag.split("-")
605 version_parts = code_tag_parts[0].split(".")
606 major, minor = version_parts[0], version_parts[1]
607 rev = code_tag_parts[1]
608 if int(major) == 1 and minor == 0 and build >= 22:
612 ### ois = options if supported
613 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
614 def ois (self, server, option_dict):
615 if self.server_supports_options_arg (server):
617 elif self.server_supports_call_id_arg (server):
618 return [ unique_call_id () ]
622 ### cis = call_id if supported - like ois
623 def cis (self, server):
624 if self.server_supports_call_id_arg (server):
625 return [ unique_call_id ]
629 ######################################## miscell utilities
630 def get_rspec_file(self, rspec):
631 if (os.path.isabs(rspec)):
634 file = os.path.join(self.options.sfi_dir, rspec)
635 if (os.path.isfile(file)):
638 self.logger.critical("No such rspec file %s"%rspec)
641 def get_record_file(self, record):
642 if (os.path.isabs(record)):
645 file = os.path.join(self.options.sfi_dir, record)
646 if (os.path.isfile(file)):
649 self.logger.critical("No such registry record file %s"%record)
653 #==========================================================================
654 # Following functions implement the commands
656 # Registry-related commands
657 #==========================================================================
659 def version(self, options, args):
661 display an SFA server version (GetVersion)
662 or version information about sfi itself
664 if options.version_local:
665 version=version_core()
667 if options.version_registry:
668 server=self.registry()
670 server = self.sliceapi()
671 result = server.GetVersion()
672 version = ReturnValue.get_value(result)
673 pprinter = PrettyPrinter(indent=4)
674 pprinter.pprint(version)
676 save_variable_to_file(version, options.file, options.fileformat)
678 def list(self, options, args):
680 list entries in named authority registry (List)
687 list = self.registry().List(hrn, self.my_credential_string)
689 raise Exception, "Not enough parameters for the 'list' command"
691 # filter on person, slice, site, node, etc.
692 # THis really should be in the self.filter_records funct def comment...
693 list = filter_records(options.type, list)
695 print "%s (%s)" % (record['hrn'], record['type'])
697 save_records_to_file(options.file, list, options.fileformat)
700 def show(self, options, args):
702 show details about named registry record (Resolve)
708 records = self.registry().Resolve(hrn, self.my_credential_string)
709 records = filter_records(options.type, records)
711 self.logger.error("No record of type %s"% options.type)
712 for record in records:
713 if record['type'] in ['user']:
714 record = UserRecord(dict=record)
715 elif record['type'] in ['slice']:
716 record = SliceRecord(dict=record)
717 elif record['type'] in ['node']:
718 record = NodeRecord(dict=record)
719 elif record['type'].startswith('authority'):
720 record = AuthorityRecord(dict=record)
722 record = SfaRecord(dict=record)
723 if (options.format == "text"):
726 print record.save_to_string()
728 save_records_to_file(options.file, records, options.fileformat)
731 def add(self, options, args):
732 "add record into registry from xml file (Register)"
733 auth_cred = self.my_authority_credential_string()
737 record_filepath = args[0]
738 rec_file = self.get_record_file(record_filepath)
739 record = load_record_from_file(rec_file).as_dict()
740 return self.registry().Register(record, auth_cred)
742 def update(self, options, args):
743 "update record into registry from xml file (Update)"
747 rec_file = self.get_record_file(args[0])
748 record = load_record_from_file(rec_file)
749 if record['type'] == "user":
750 if record.get_name() == self.user:
751 cred = self.my_credential_string
753 cred = self.my_authority_credential_string()
754 elif record['type'] in ["slice"]:
756 cred = self.slice_credential_string(record.get_name())
757 except ServerException, e:
758 # XXX smbaker -- once we have better error return codes, update this
759 # to do something better than a string compare
760 if "Permission error" in e.args[0]:
761 cred = self.my_authority_credential_string()
764 elif record.get_type() in ["authority"]:
765 cred = self.my_authority_credential_string()
766 elif record.get_type() == 'node':
767 cred = self.my_authority_credential_string()
769 raise "unknown record type" + record.get_type()
770 record = record.as_dict()
771 return self.registry().Update(record, cred)
773 def remove(self, options, args):
774 "remove registry record by name (Remove)"
775 auth_cred = self.my_authority_credential_string()
783 return self.registry().Remove(hrn, auth_cred, type)
785 # ==================================================================
786 # Slice-related commands
787 # ==================================================================
789 def slices(self, options, args):
790 "list instantiated slices (ListSlices) - returns urn's"
791 server = self.sliceapi()
793 creds = [self.my_credential_string]
795 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
796 creds.append(delegated_cred)
797 # options and call_id when supported
799 api_options['call_id']=unique_call_id()
800 result = server.ListSlices(creds, *self.ois(server,api_options))
801 value = ReturnValue.get_value(result)
805 # show rspec for named slice
806 def resources(self, options, args):
808 with no arg, discover available resources, (ListResources)
809 or with an slice hrn, shows currently provisioned resources
811 server = self.sliceapi()
816 creds.append(self.slice_credential_string(args[0]))
818 creds.append(self.my_credential_string)
820 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
822 # no need to check if server accepts the options argument since the options has
823 # been a required argument since v1 API
825 # always send call_id to v2 servers
826 api_options ['call_id'] = unique_call_id()
827 # ask for cached value if available
828 api_options ['cached'] = True
831 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
833 api_options['info'] = options.info
835 if options.current == True:
836 api_options['cached'] = False
838 api_options['cached'] = True
839 if options.rspec_version:
840 version_manager = VersionManager()
841 server_version = self.get_cached_server_version(server)
842 if 'sfa' in server_version:
843 # just request the version the client wants
844 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
846 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
848 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
849 result = server.ListResources (creds, api_options)
850 value = ReturnValue.get_value(result)
851 if options.file is None:
852 display_rspec(value, options.format)
854 save_rspec_to_file(value, options.file)
857 def create(self, options, args):
859 create or update named slice with given rspec
861 server = self.sliceapi()
863 # xxx do we need to check usage (len(args)) ?
866 slice_urn = hrn_to_urn(slice_hrn, 'slice')
869 creds = [self.slice_credential_string(slice_hrn)]
870 delegated_cred = None
871 server_version = self.get_cached_server_version(server)
872 if server_version.get('interface') == 'slicemgr':
873 # delegate our cred to the slice manager
874 # do not delegate cred to slicemgr...not working at the moment
876 #if server_version.get('hrn'):
877 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
878 #elif server_version.get('urn'):
879 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
882 rspec_file = self.get_rspec_file(args[1])
883 rspec = open(rspec_file).read()
886 # need to pass along user keys to the aggregate.
888 # { urn: urn:publicid:IDN+emulab.net+user+alice
889 # keys: [<ssh key A>, <ssh key B>]
892 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
893 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
894 slice_record = slice_records[0]
895 user_hrns = slice_record['researcher']
896 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
897 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
899 if 'sfa' not in server_version:
900 users = pg_users_arg(user_records)
902 rspec.filter({'component_manager_id': server_version['urn']})
903 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
905 users = sfa_users_arg(user_records, slice_record)
907 # do not append users, keys, or slice tags. Anything
908 # not contained in this request will be removed from the slice
910 # CreateSliver has supported the options argument for a while now so it should
911 # be safe to assume this server support it
913 api_options ['append'] = False
914 api_options ['call_id'] = unique_call_id()
916 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
917 value = ReturnValue.get_value(result)
918 if options.file is None:
921 save_rspec_to_file (value, options.file)
924 def delete(self, options, args):
926 delete named slice (DeleteSliver)
928 server = self.sliceapi()
932 slice_urn = hrn_to_urn(slice_hrn, 'slice')
935 slice_cred = self.slice_credential_string(slice_hrn)
938 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
939 creds.append(delegated_cred)
941 # options and call_id when supported
943 api_options ['call_id'] = unique_call_id()
944 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
945 # xxx no ReturnValue ??
948 def status(self, options, args):
950 retrieve slice status (SliverStatus)
952 server = self.sliceapi()
956 slice_urn = hrn_to_urn(slice_hrn, 'slice')
959 slice_cred = self.slice_credential_string(slice_hrn)
962 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
963 creds.append(delegated_cred)
965 # options and call_id when supported
967 api_options['call_id']=unique_call_id()
968 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
969 value = ReturnValue.get_value(result)
972 save_variable_to_file(value, options.file, options.fileformat)
974 def start(self, options, args):
976 start named slice (Start)
978 server = self.sliceapi()
982 slice_urn = hrn_to_urn(slice_hrn, 'slice')
985 slice_cred = self.slice_credential_string(args[0])
988 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
989 creds.append(delegated_cred)
990 # xxx Thierry - does this not need an api_options as well ?
991 return server.Start(slice_urn, creds)
993 def stop(self, options, args):
995 stop named slice (Stop)
997 server = self.sliceapi()
1000 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1002 slice_cred = self.slice_credential_string(args[0])
1003 creds = [slice_cred]
1004 if options.delegate:
1005 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1006 creds.append(delegated_cred)
1007 return server.Stop(slice_urn, creds)
1010 def reset(self, options, args):
1012 reset named slice (reset_slice)
1014 server = self.sliceapi()
1017 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1019 slice_cred = self.slice_credential_string(args[0])
1020 creds = [slice_cred]
1021 if options.delegate:
1022 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1023 creds.append(delegated_cred)
1024 return server.reset_slice(creds, slice_urn)
1026 def renew(self, options, args):
1028 renew slice (RenewSliver)
1030 server = self.sliceapi()
1033 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1035 slice_cred = self.slice_credential_string(args[0])
1036 creds = [slice_cred]
1037 if options.delegate:
1038 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1039 creds.append(delegated_cred)
1042 # options and call_id when supported
1044 api_options['call_id']=unique_call_id()
1045 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1046 value = ReturnValue.get_value(result)
1050 def shutdown(self, options, args):
1052 shutdown named slice (Shutdown)
1054 server = self.sliceapi()
1057 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1059 slice_cred = self.slice_credential_string(slice_hrn)
1060 creds = [slice_cred]
1061 if options.delegate:
1062 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1063 creds.append(delegated_cred)
1064 return server.Shutdown(slice_urn, creds)
1067 def get_ticket(self, options, args):
1069 get a ticket for the specified slice
1071 server = self.sliceapi()
1073 slice_hrn, rspec_path = args[0], args[1]
1074 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1076 slice_cred = self.slice_credential_string(slice_hrn)
1077 creds = [slice_cred]
1078 if options.delegate:
1079 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1080 creds.append(delegated_cred)
1082 rspec_file = self.get_rspec_file(rspec_path)
1083 rspec = open(rspec_file).read()
1084 # options and call_id when supported
1086 api_options['call_id']=unique_call_id()
1087 # get ticket at the server
1088 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1090 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1091 self.logger.info("writing ticket to %s"%file)
1092 ticket = SfaTicket(string=ticket_string)
1093 ticket.save_to_file(filename=file, save_parents=True)
1095 def redeem_ticket(self, options, args):
1097 Connects to nodes in a slice and redeems a ticket
1098 (slice hrn is retrieved from the ticket)
1100 ticket_file = args[0]
1102 # get slice hrn from the ticket
1103 # use this to get the right slice credential
1104 ticket = SfaTicket(filename=ticket_file)
1106 ticket_string = ticket.save_to_string(save_parents=True)
1108 slice_hrn = ticket.gidObject.get_hrn()
1109 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1110 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1111 slice_cred = self.slice_credential_string(slice_hrn)
1113 # get a list of node hostnames from the RSpec
1114 tree = etree.parse(StringIO(ticket.rspec))
1115 root = tree.getroot()
1116 hostnames = root.xpath("./network/site/node/hostname/text()")
1118 # create an xmlrpc connection to the component manager at each of these
1119 # components and gall redeem_ticket
1121 for hostname in hostnames:
1123 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1124 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1125 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1126 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1127 timeout=self.options.timeout, verbose=self.options.debug)
1128 server.RedeemTicket(ticket_string, slice_cred)
1129 self.logger.info("Success")
1130 except socket.gaierror:
1131 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1132 except Exception, e:
1133 self.logger.log_exc(e.message)
1136 def create_gid(self, options, args):
1138 Create a GID (CreateGid)
1143 target_hrn = args[0]
1144 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1146 filename = options.file
1148 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1149 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1150 GID(string=gid).save_to_file(filename)
1153 def delegate(self, options, args):
1155 (locally) create delegate credential for use by given hrn
1157 delegee_hrn = args[0]
1158 if options.delegate_user:
1159 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1160 elif options.delegate_slice:
1161 slice_cred = self.slice_credential_string(options.delegate_slice)
1162 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1164 self.logger.warning("Must specify either --user or --slice <hrn>")
1166 delegated_cred = Credential(string=cred)
1167 object_hrn = delegated_cred.get_gid_object().get_hrn()
1168 if options.delegate_user:
1169 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1170 + get_leaf(object_hrn) + ".cred")
1171 elif options.delegate_slice:
1172 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1173 + get_leaf(object_hrn) + ".cred")
1175 delegated_cred.save_to_file(dest_fn, save_parents=True)
1177 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1179 def get_trusted_certs(self, options, args):
1181 return uhe trusted certs at this interface (get_trusted_certs)
1183 trusted_certs = self.registry().get_trusted_certs()
1184 for trusted_cert in trusted_certs:
1185 gid = GID(string=trusted_cert)
1187 cert = Certificate(string=trusted_cert)
1188 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())