2 # sfi.py - basic SFA command-line client
3 # the actual binary in sfa/clientbin essentially runs main()
4 # this module is used in sfascan
15 from lxml import etree
16 from StringIO import StringIO
17 from optparse import OptionParser
18 from pprint import PrettyPrinter
20 from sfa.trust.certificate import Keypair, Certificate
21 from sfa.trust.gid import GID
22 from sfa.trust.credential import Credential
23 from sfa.trust.sfaticket import SfaTicket
25 from sfa.util.sfalogging import sfi_logger
26 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn
27 from sfa.util.config import Config
28 from sfa.util.version import version_core
29 from sfa.util.cache import Cache
31 from sfa.storage.record import SfaRecord, UserRecord, SliceRecord, NodeRecord, AuthorityRecord
33 from sfa.rspecs.rspec import RSpec
34 from sfa.rspecs.rspec_converter import RSpecConverter
35 from sfa.rspecs.version_manager import VersionManager
37 from sfa.client.sfaclientlib import SfaClientBootstrap
38 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
39 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
40 from sfa.client.return_value import ReturnValue
44 # utility methods here
46 def display_rspec(rspec, format='rspec'):
48 tree = etree.parse(StringIO(rspec))
50 result = root.xpath("./network/site/node/hostname/text()")
51 elif format in ['ip']:
52 # The IP address is not yet part of the new RSpec
53 # so this doesn't do anything yet.
54 tree = etree.parse(StringIO(rspec))
56 result = root.xpath("./network/site/node/ipv4/text()")
63 def display_list(results):
64 for result in results:
67 def display_records(recordList, dump=False):
68 ''' Print all fields in the record'''
69 for record in recordList:
70 display_record(record, dump)
72 def display_record(record, dump=False):
76 info = record.getdict()
77 print "%s (%s)" % (info['hrn'], info['type'])
81 def filter_records(type, records):
83 for record in records:
84 if (record['type'] == type) or (type == "all"):
85 filtered_records.append(record)
86 return filtered_records
90 def save_variable_to_file(var, filename, format="text"):
91 f = open(filename, "w")
94 elif format == "pickled":
95 f.write(pickle.dumps(var))
97 # this should never happen
98 print "unknown output format", format
101 def save_rspec_to_file(rspec, filename):
102 if not filename.endswith(".rspec"):
103 filename = filename + ".rspec"
104 f = open(filename, 'w')
109 def save_records_to_file(filename, recordList, format="xml"):
112 for record in recordList:
114 save_record_to_file(filename + "." + str(index), record)
116 save_record_to_file(filename, record)
118 elif format == "xmllist":
119 f = open(filename, "w")
120 f.write("<recordlist>\n")
121 for record in recordList:
122 record = SfaRecord(dict=record)
123 f.write('<record hrn="' + record.get_name() + '" type="' + record.get_type() + '" />\n')
124 f.write("</recordlist>\n")
126 elif format == "hrnlist":
127 f = open(filename, "w")
128 for record in recordList:
129 record = SfaRecord(dict=record)
130 f.write(record.get_name() + "\n")
133 # this should never happen
134 print "unknown output format", format
136 def save_record_to_file(filename, record):
137 if record['type'] in ['user']:
138 record = UserRecord(dict=record)
139 elif record['type'] in ['slice']:
140 record = SliceRecord(dict=record)
141 elif record['type'] in ['node']:
142 record = NodeRecord(dict=record)
143 elif record['type'] in ['authority', 'ma', 'sa']:
144 record = AuthorityRecord(dict=record)
146 record = SfaRecord(dict=record)
147 str = record.save_to_string()
148 f=codecs.open(filename, encoding='utf-8',mode="w")
155 def load_record_from_file(filename):
156 f=codecs.open(filename, encoding="utf-8", mode="r")
159 record = SfaRecord(string=str)
164 def unique_call_id(): return uuid.uuid4().urn
168 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user']
171 def default_sfi_dir ():
172 if os.path.isfile("./sfi_config"):
175 return os.path.expanduser("~/.sfi/")
177 # dummy to meet Sfi's expectations for its 'options' field
178 # i.e. s/t we can do setattr on
182 def __init__ (self,options=None):
183 if options is None: options=Sfi.DummyOptions()
184 for opt in Sfi.required_options:
185 if not hasattr(options,opt): setattr(options,opt,None)
186 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
187 self.options = options
189 self.authority = None
190 self.logger = sfi_logger
191 self.logger.enable_console()
192 self.available_names = [ tuple[0] for tuple in Sfi.available ]
193 self.available_dict = dict (Sfi.available)
195 # tuples command-name expected-args in the order in which they should appear in the help
198 ("list", "authority"),
201 ("update", "record"),
204 ("resources", "[slice_hrn]"),
205 ("create", "slice_hrn rspec"),
206 ("delete", "slice_hrn"),
207 ("status", "slice_hrn"),
208 ("start", "slice_hrn"),
209 ("stop", "slice_hrn"),
210 ("reset", "slice_hrn"),
211 ("renew", "slice_hrn time"),
212 ("shutdown", "slice_hrn"),
213 ("get_ticket", "slice_hrn rspec"),
214 ("redeem_ticket", "ticket"),
215 ("delegate", "name"),
216 ("create_gid", "[name]"),
217 ("get_trusted_certs", "cred"),
220 def print_command_help (self, options):
221 verbose=getattr(options,'verbose')
222 format3="%18s %-15s %s"
225 print format3%("command","cmd_args","description")
229 self.create_parser().print_help()
230 for command in self.available_names:
231 args=self.available_dict[command]
232 method=getattr(self,command,None)
234 if method: doc=getattr(method,'__doc__',"")
235 if not doc: doc="*** no doc found ***"
236 doc=doc.strip(" \t\n")
237 doc=doc.replace("\n","\n"+35*' ')
240 print format3%(command,args,doc)
242 self.create_command_parser(command).print_help()
244 def create_command_parser(self, command):
245 if command not in self.available_dict:
246 msg="Invalid command\n"
248 msg += ','.join(self.available_names)
249 self.logger.critical(msg)
252 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
253 % (command, self.available_dict[command]))
255 # user specifies remote aggregate/sm/component
256 if command in ("resources", "slices", "create", "delete", "start", "stop",
257 "restart", "shutdown", "get_ticket", "renew", "status"):
258 parser.add_option("-c", "--component", dest="component", default=None,
259 help="component hrn")
260 parser.add_option("-d", "--delegate", dest="delegate", default=None,
262 help="Include a credential delegated to the user's root"+\
263 "authority in set of credentials for this call")
265 # registy filter option
266 if command in ("list", "show", "remove"):
267 parser.add_option("-t", "--type", dest="type", type="choice",
268 help="type filter ([all]|user|slice|authority|node|aggregate)",
269 choices=("all", "user", "slice", "authority", "node", "aggregate"),
272 if command in ("resources"):
273 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
274 help="schema type and version of resulting RSpec")
275 parser.add_option("-f", "--format", dest="format", type="choice",
276 help="display format ([xml]|dns|ip)", default="xml",
277 choices=("xml", "dns", "ip"))
278 #panos: a new option to define the type of information about resources a user is interested in
279 parser.add_option("-i", "--info", dest="info",
280 help="optional component information", default=None)
283 # 'create' does return the new rspec, makes sense to save that too
284 if command in ("resources", "show", "list", "create_gid", 'create'):
285 parser.add_option("-o", "--output", dest="file",
286 help="output XML to file", metavar="FILE", default=None)
288 if command in ("show", "list"):
289 parser.add_option("-f", "--format", dest="format", type="choice",
290 help="display format ([text]|xml)", default="text",
291 choices=("text", "xml"))
293 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
294 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
295 choices=("xml", "xmllist", "hrnlist"))
297 if command in ("status", "version"):
298 parser.add_option("-o", "--output", dest="file",
299 help="output dictionary to file", metavar="FILE", default=None)
300 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
301 help="output file format ([text]|pickled)", default="text",
302 choices=("text","pickled"))
304 if command in ("delegate"):
305 parser.add_option("-u", "--user",
306 action="store_true", dest="delegate_user", default=False,
307 help="delegate user credential")
308 parser.add_option("-s", "--slice", dest="delegate_slice",
309 help="delegate slice credential", metavar="HRN", default=None)
311 if command in ("version"):
312 parser.add_option("-R","--registry-version",
313 action="store_true", dest="version_registry", default=False,
314 help="probe registry version instead of sliceapi")
315 parser.add_option("-l","--local",
316 action="store_true", dest="version_local", default=False,
317 help="display version of the local client")
322 def create_parser(self):
324 # Generate command line parser
325 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
326 description="Commands: %s"%(" ".join(self.available_names)))
327 parser.add_option("-r", "--registry", dest="registry",
328 help="root registry", metavar="URL", default=None)
329 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
330 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
331 parser.add_option("-d", "--dir", dest="sfi_dir",
332 help="config & working directory - default is %default",
333 metavar="PATH", default=Sfi.default_sfi_dir())
334 parser.add_option("-u", "--user", dest="user",
335 help="user name", metavar="HRN", default=None)
336 parser.add_option("-a", "--auth", dest="auth",
337 help="authority name", metavar="HRN", default=None)
338 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
339 help="verbose mode - cumulative")
340 parser.add_option("-D", "--debug",
341 action="store_true", dest="debug", default=False,
342 help="Debug (xml-rpc) protocol messages")
343 parser.add_option("-p", "--protocol", dest="protocol", default="xmlrpc",
344 help="RPC protocol (xmlrpc or soap)")
345 # would it make sense to use ~/.ssh/id_rsa as a default here ?
346 parser.add_option("-k", "--private-key",
347 action="store", dest="user_private_key", default=None,
348 help="point to the private key file to use if not yet installed in sfi_dir")
349 parser.add_option("-t", "--timeout", dest="timeout", default=None,
350 help="Amout of time to wait before timing out the request")
351 parser.add_option("-?", "--commands",
352 action="store_true", dest="command_help", default=False,
353 help="one page summary on commands & exit")
354 parser.disable_interspersed_args()
359 def print_help (self):
360 self.sfi_parser.print_help()
361 self.command_parser.print_help()
364 # Main: parse arguments and dispatch to command
366 def dispatch(self, command, command_options, command_args):
367 return getattr(self, command)(command_options, command_args)
370 self.sfi_parser = self.create_parser()
371 (options, args) = self.sfi_parser.parse_args()
372 if options.command_help:
373 self.print_command_help(options)
375 self.options = options
377 self.logger.setLevelFromOptVerbose(self.options.verbose)
380 self.logger.critical("No command given. Use -h for help.")
381 self.print_command_help(options)
385 self.command_parser = self.create_command_parser(command)
386 (command_options, command_args) = self.command_parser.parse_args(args[1:])
387 self.command_options = command_options
391 self.logger.info("Command=%s" % command)
394 self.dispatch(command, command_options, command_args)
396 self.logger.critical ("Unknown command %s"%command)
403 def read_config(self):
404 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
406 config = Config (config_file)
408 self.logger.critical("Failed to read configuration file %s"%config_file)
409 self.logger.info("Make sure to remove the export clauses and to add quotes")
410 if self.options.verbose==0:
411 self.logger.info("Re-run with -v for more details")
413 self.logger.log_exc("Could not read config file %s"%config_file)
418 if (self.options.sm is not None):
419 self.sm_url = self.options.sm
420 elif hasattr(config, "SFI_SM"):
421 self.sm_url = config.SFI_SM
423 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
427 if (self.options.registry is not None):
428 self.reg_url = self.options.registry
429 elif hasattr(config, "SFI_REGISTRY"):
430 self.reg_url = config.SFI_REGISTRY
432 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
436 if (self.options.user is not None):
437 self.user = self.options.user
438 elif hasattr(config, "SFI_USER"):
439 self.user = config.SFI_USER
441 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
445 if (self.options.auth is not None):
446 self.authority = self.options.auth
447 elif hasattr(config, "SFI_AUTH"):
448 self.authority = config.SFI_AUTH
450 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
457 # Get various credential and spec files
459 # Establishes limiting conventions
460 # - conflates MAs and SAs
461 # - assumes last token in slice name is unique
463 # Bootstraps credentials
464 # - bootstrap user credential from self-signed certificate
465 # - bootstrap authority credential from user credential
466 # - bootstrap slice credential from user credential
469 # init self-signed cert, user credentials and gid
470 def bootstrap (self):
471 bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
472 # if -k is provided, use this to initialize private key
473 if self.options.user_private_key:
474 bootstrap.init_private_key_if_missing (self.options.user_private_key)
476 # trigger legacy compat code if needed
477 # the name has changed from just <leaf>.pkey to <hrn>.pkey
478 if not os.path.isfile(bootstrap.private_key_filename()):
479 self.logger.info ("private key not found, trying legacy name")
481 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
482 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
483 bootstrap.init_private_key_if_missing (legacy_private_key)
484 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
486 self.logger.log_exc("Can't find private key ")
490 bootstrap.bootstrap_my_gid()
491 # extract what's needed
492 self.private_key = bootstrap.private_key()
493 self.my_credential_string = bootstrap.my_credential_string ()
494 self.my_gid = bootstrap.my_gid ()
495 self.bootstrap = bootstrap
498 def my_authority_credential_string(self):
499 if not self.authority:
500 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
502 return self.bootstrap.authority_credential_string (self.authority)
504 def slice_credential_string(self, name):
505 return self.bootstrap.slice_credential_string (name)
507 # xxx should be supported by sfaclientbootstrap as well
508 def delegate_cred(self, object_cred, hrn, type='authority'):
509 # the gid and hrn of the object we are delegating
510 if isinstance(object_cred, str):
511 object_cred = Credential(string=object_cred)
512 object_gid = object_cred.get_gid_object()
513 object_hrn = object_gid.get_hrn()
515 if not object_cred.get_privileges().get_all_delegate():
516 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
519 # the delegating user's gid
520 caller_gidfile = self.my_gid()
522 # the gid of the user who will be delegated to
523 delegee_gid = self.bootstrap.gid(hrn,type)
524 delegee_hrn = delegee_gid.get_hrn()
525 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
526 return dcred.save_to_string(save_parents=True)
529 # Management of the servers
534 if not hasattr (self, 'registry_proxy'):
535 self.logger.info("Contacting Registry at: %s"%self.reg_url)
536 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
537 timeout=self.options.timeout, verbose=self.options.debug)
538 return self.registry_proxy
542 if not hasattr (self, 'sliceapi_proxy'):
543 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
544 if hasattr(self.command_options,'component') and self.command_options.component:
545 # resolve the hrn at the registry
546 node_hrn = self.command_options.component
547 records = self.registry().Resolve(node_hrn, self.my_credential_string)
548 records = filter_records('node', records)
550 self.logger.warning("No such component:%r"% opts.component)
552 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
553 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
555 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
556 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
557 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
558 timeout=self.options.timeout, verbose=self.options.debug)
559 return self.sliceapi_proxy
561 def get_cached_server_version(self, server):
562 # check local cache first
565 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
566 cache_key = server.url + "-version"
568 cache = Cache(cache_file)
571 self.logger.info("Local cache not found at: %s" % cache_file)
574 version = cache.get(cache_key)
577 result = server.GetVersion()
578 version= ReturnValue.get_value(result)
579 # cache version for 20 minutes
580 cache.add(cache_key, version, ttl= 60*20)
581 self.logger.info("Updating cache file %s" % cache_file)
582 cache.save_to_file(cache_file)
586 ### resurrect this temporarily so we can support V1 aggregates for a while
587 def server_supports_options_arg(self, server):
589 Returns true if server support the optional call_id arg, false otherwise.
591 server_version = self.get_cached_server_version(server)
593 # xxx need to rewrite this
594 if int(server_version.get('geni_api')) >= 2:
598 def server_supports_call_id_arg(self, server):
599 server_version = self.get_cached_server_version(server)
601 if 'sfa' in server_version and 'code_tag' in server_version:
602 code_tag = server_version['code_tag']
603 code_tag_parts = code_tag.split("-")
604 version_parts = code_tag_parts[0].split(".")
605 major, minor = version_parts[0], version_parts[1]
606 rev = code_tag_parts[1]
607 if int(major) == 1 and minor == 0 and build >= 22:
611 ### ois = options if supported
612 def ois (self, server, option_dict):
613 if self.server_supports_options_arg (server) : return [option_dict]
616 ######################################## miscell utilities
617 def get_rspec_file(self, rspec):
618 if (os.path.isabs(rspec)):
621 file = os.path.join(self.options.sfi_dir, rspec)
622 if (os.path.isfile(file)):
625 self.logger.critical("No such rspec file %s"%rspec)
628 def get_record_file(self, record):
629 if (os.path.isabs(record)):
632 file = os.path.join(self.options.sfi_dir, record)
633 if (os.path.isfile(file)):
636 self.logger.critical("No such registry record file %s"%record)
640 #==========================================================================
641 # Following functions implement the commands
643 # Registry-related commands
644 #==========================================================================
646 def version(self, options, args):
648 display an SFA server version (GetVersion)
649 or version information about sfi itself
651 if options.version_local:
652 version=version_core()
654 if options.version_registry:
655 server=self.registry()
657 server = self.sliceapi()
658 result = server.GetVersion()
659 version = ReturnValue.get_value(result)
660 pprinter = PrettyPrinter(indent=4)
661 pprinter.pprint(version)
663 save_variable_to_file(version, options.file, options.fileformat)
665 def list(self, options, args):
667 list entries in named authority registry (List)
674 list = self.registry().List(hrn, self.my_credential_string)
676 raise Exception, "Not enough parameters for the 'list' command"
678 # filter on person, slice, site, node, etc.
679 # THis really should be in the self.filter_records funct def comment...
680 list = filter_records(options.type, list)
682 print "%s (%s)" % (record['hrn'], record['type'])
684 save_records_to_file(options.file, list, options.fileformat)
687 def show(self, options, args):
689 show details about named registry record (Resolve)
695 records = self.registry().Resolve(hrn, self.my_credential_string)
696 records = filter_records(options.type, records)
698 self.logger.error("No record of type %s"% options.type)
699 for record in records:
700 if record['type'] in ['user']:
701 record = UserRecord(dict=record)
702 elif record['type'] in ['slice']:
703 record = SliceRecord(dict=record)
704 elif record['type'] in ['node']:
705 record = NodeRecord(dict=record)
706 elif record['type'].startswith('authority'):
707 record = AuthorityRecord(dict=record)
709 record = SfaRecord(dict=record)
710 if (options.format == "text"):
713 print record.save_to_string()
715 save_records_to_file(options.file, records, options.fileformat)
718 def add(self, options, args):
719 "add record into registry from xml file (Register)"
720 auth_cred = self.my_authority_credential_string()
724 record_filepath = args[0]
725 rec_file = self.get_record_file(record_filepath)
726 record = load_record_from_file(rec_file).as_dict()
727 return self.registry().Register(record, auth_cred)
729 def update(self, options, args):
730 "update record into registry from xml file (Update)"
734 rec_file = self.get_record_file(args[0])
735 record = load_record_from_file(rec_file)
736 if record['type'] == "user":
737 if record.get_name() == self.user:
738 cred = self.my_credential_string
740 cred = self.my_authority_credential_string()
741 elif record['type'] in ["slice"]:
743 cred = self.slice_credential_string(record.get_name())
744 except ServerException, e:
745 # XXX smbaker -- once we have better error return codes, update this
746 # to do something better than a string compare
747 if "Permission error" in e.args[0]:
748 cred = self.my_authority_credential_string()
751 elif record.get_type() in ["authority"]:
752 cred = self.my_authority_credential_string()
753 elif record.get_type() == 'node':
754 cred = self.my_authority_credential_string()
756 raise "unknown record type" + record.get_type()
757 record = record.as_dict()
758 return self.registry().Update(record, cred)
760 def remove(self, options, args):
761 "remove registry record by name (Remove)"
762 auth_cred = self.my_authority_credential_string()
770 return self.registry().Remove(hrn, auth_cred, type)
772 # ==================================================================
773 # Slice-related commands
774 # ==================================================================
776 def slices(self, options, args):
777 "list instantiated slices (ListSlices) - returns urn's"
778 server = self.sliceapi()
780 creds = [self.my_credential_string]
782 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
783 creds.append(delegated_cred)
784 call_args.append(creds)
785 if self.server_supports_options_arg(server):
787 api_options ['call_id'] = unique_call_id()
788 args.append(api_options)
789 elif self.server_supports_call_id_arg(server):
790 args.append(unique_call_id())
791 result = server.ListSlices(*call_args)
792 value = ReturnValue.get_value(result)
796 # show rspec for named slice
797 def resources(self, options, args):
799 with no arg, discover available resources, (ListResources)
800 or with an slice hrn, shows currently provisioned resources
802 server = self.sliceapi()
808 creds.append(self.slice_credential_string(args[0]))
810 creds.append(self.my_credential_string)
812 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
813 call_args.append(creds)
815 # set options and callid
816 print self.server_supports_options_arg(server)
817 if self.server_supports_options_arg(server):
821 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
823 api_options['info'] = options.info
824 if options.rspec_version:
825 version_manager = VersionManager()
826 server_version = self.get_cached_server_version(server)
827 if 'sfa' in server_version:
828 # just request the version the client wants
829 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
831 # this must be a protogeni aggregate. We should request a v2 ad rspec
832 # regardless of what the client user requested
833 api_options['geni_rspec_version'] = version_manager.get_version('ProtoGENI 2').to_dict()
835 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
836 api_options ['call_id'] = unique_call_id()
837 call_args.append(api_options)
841 call_args.append(hrn)
842 if self.server_supports_call_id_arg(server):
843 call_args.append(unique_call_id)
844 result = server.ListResources(*call_args)
845 value = ReturnValue.get_value(result)
846 if options.file is None:
847 display_rspec(value, options.format)
849 save_rspec_to_file(value, options.file)
852 def create(self, options, args):
854 create or update named slice with given rspec
856 server = self.sliceapi()
860 slice_urn = hrn_to_urn(slice_hrn, 'slice')
861 call_args.append(slice_urn)
863 creds = [self.slice_credential_string(slice_hrn)]
864 call_args.append(creds)
865 delegated_cred = None
866 server_version = self.get_cached_server_version(server)
867 if server_version.get('interface') == 'slicemgr':
868 # delegate our cred to the slice manager
869 # do not delegate cred to slicemgr...not working at the moment
871 #if server_version.get('hrn'):
872 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
873 #elif server_version.get('urn'):
874 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
877 rspec_file = self.get_rspec_file(args[1])
878 rspec = open(rspec_file).read()
880 # need to pass along user keys to the aggregate.
882 # { urn: urn:publicid:IDN+emulab.net+user+alice
883 # keys: [<ssh key A>, <ssh key B>]
886 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
887 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
888 slice_record = slice_records[0]
889 user_hrns = slice_record['researcher']
890 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
891 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
893 if 'sfa' not in server_version:
894 users = pg_users_arg(user_records)
896 rspec.filter({'component_manager_id': server_version['urn']})
897 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
899 users = sfa_users_arg(user_records, slice_record)
901 call_args.append(rspec)
902 call_args.append(users)
903 # do not append users, keys, or slice tags. Anything
904 # not contained in this request will be removed from the slice
906 # CreateSliver has supported the options argument for a while now so it should
907 # be safe to assume this server support it
909 api_options ['append'] = False
910 api_options ['call_id'] = unique_call_id()
911 call_args.append(api_options)
912 result = server.CreateSliver(*call_args)
913 value = ReturnValue.get_value(result)
914 if options.file is None:
917 save_rspec_to_file (value, options.file)
920 def delete(self, options, args):
922 delete named slice (DeleteSliver)
924 server = self.sliceapi()
928 slice_urn = hrn_to_urn(slice_hrn, 'slice')
929 call_args.append(slice_urn)
931 slice_cred = self.slice_credential_string(slice_hrn)
934 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
935 creds.append(delegated_cred)
936 call_args.append(creds)
937 if self.server_supports_options_arg(server):
939 api_options ['call_id'] = unique_call_id()
940 call_args.append(api_options)
941 elif self.server_supports_call_id_arg(server):
942 call_args.append(unique_call_id())
943 return server.DeleteSliver(*call_args)
945 def status(self, options, args):
947 retrieve slice status (SliverStatus)
949 server = self.sliceapi()
953 slice_urn = hrn_to_urn(slice_hrn, 'slice')
954 call_args.append(slice_urn)
956 slice_cred = self.slice_credential_string(slice_hrn)
959 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
960 creds.append(delegated_cred)
961 call_args.append(creds)
962 # set options and call id
963 if self.server_supports_options_arg(server):
965 api_options ['call_id'] = unique_call_id()
966 call_args.append(api_options)
967 elif self.server_supports_call_id_arg(server):
968 call_args.append(unique_call_id())
969 result = server.SliverStatus(*call_args)
970 value = ReturnValue.get_value(result)
973 save_variable_to_file(value, options.file, options.fileformat)
975 def start(self, options, args):
977 start named slice (Start)
979 server = self.sliceapi()
983 slice_urn = hrn_to_urn(slice_hrn, 'slice')
984 call_args.append(slice_urn)
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)
998 slice_urn = hrn_to_urn(slice_hrn, 'slice')
999 slice_cred = self.slice_credential_string(args[0])
1000 creds = [slice_cred]
1001 if options.delegate:
1002 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1003 creds.append(delegated_cred)
1004 server = self.sliceapi()
1005 return server.Stop(slice_urn, creds)
1008 def reset(self, options, args):
1010 reset named slice (reset_slice)
1013 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1014 server = self.sliceapi()
1015 slice_cred = self.slice_credential_string(args[0])
1016 creds = [slice_cred]
1017 if options.delegate:
1018 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1019 creds.append(delegated_cred)
1020 return server.reset_slice(creds, slice_urn)
1022 def renew(self, options, args):
1024 renew slice (RenewSliver)
1026 server = self.sliceapi()
1030 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1031 call_args.append(slice_urn)
1033 slice_cred = self.slice_credential_string(args[0])
1034 creds = [slice_cred]
1035 if options.delegate:
1036 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1037 creds.append(delegated_cred)
1038 call_args.append(creds)
1040 call_args.append(time)
1041 if self.server_supports_options_arg(server):
1043 api_options ['call_id'] = unique_call_id()
1044 call_args.append(api_options)
1045 elif self.server_supports_call_id_arg(server):
1046 call_args.append(unique_call_id())
1047 result = server.RenewSliver(*call_args)
1048 value = ReturnValue.get_value(result)
1052 def shutdown(self, options, args):
1054 shutdown named slice (Shutdown)
1057 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1058 slice_cred = self.slice_credential_string(slice_hrn)
1059 creds = [slice_cred]
1060 if options.delegate:
1061 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1062 creds.append(delegated_cred)
1063 server = self.sliceapi()
1064 return server.Shutdown(slice_urn, creds)
1067 def get_ticket(self, options, args):
1069 get a ticket for the specified slice
1071 slice_hrn, rspec_path = args[0], args[1]
1072 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1073 slice_cred = self.slice_credential_string(slice_hrn)
1074 creds = [slice_cred]
1075 if options.delegate:
1076 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1077 creds.append(delegated_cred)
1078 rspec_file = self.get_rspec_file(rspec_path)
1079 rspec = open(rspec_file).read()
1080 server = self.sliceapi()
1081 ticket_string = server.GetTicket(slice_urn, creds, rspec, [])
1082 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1083 self.logger.info("writing ticket to %s"%file)
1084 ticket = SfaTicket(string=ticket_string)
1085 ticket.save_to_file(filename=file, save_parents=True)
1087 def redeem_ticket(self, options, args):
1089 Connects to nodes in a slice and redeems a ticket
1090 (slice hrn is retrieved from the ticket)
1092 ticket_file = args[0]
1094 # get slice hrn from the ticket
1095 # use this to get the right slice credential
1096 ticket = SfaTicket(filename=ticket_file)
1098 slice_hrn = ticket.gidObject.get_hrn()
1099 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1100 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1101 slice_cred = self.slice_credential_string(slice_hrn)
1103 # get a list of node hostnames from the RSpec
1104 tree = etree.parse(StringIO(ticket.rspec))
1105 root = tree.getroot()
1106 hostnames = root.xpath("./network/site/node/hostname/text()")
1108 # create an xmlrpc connection to the component manager at each of these
1109 # components and gall redeem_ticket
1111 for hostname in hostnames:
1113 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1114 server = self.server_proxy(hostname, CM_PORT, self.private_key, \
1115 self.my_gid, verbose=self.options.debug)
1116 server.RedeemTicket(ticket.save_to_string(save_parents=True), slice_cred)
1117 self.logger.info("Success")
1118 except socket.gaierror:
1119 self.logger.error("redeem_ticket failed: Component Manager not accepting requests")
1120 except Exception, e:
1121 self.logger.log_exc(e.message)
1124 def create_gid(self, options, args):
1126 Create a GID (CreateGid)
1131 target_hrn = args[0]
1132 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1134 filename = options.file
1136 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1137 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1138 GID(string=gid).save_to_file(filename)
1141 def delegate(self, options, args):
1143 (locally) create delegate credential for use by given hrn
1145 delegee_hrn = args[0]
1146 if options.delegate_user:
1147 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1148 elif options.delegate_slice:
1149 slice_cred = self.slice_credential_string(options.delegate_slice)
1150 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1152 self.logger.warning("Must specify either --user or --slice <hrn>")
1154 delegated_cred = Credential(string=cred)
1155 object_hrn = delegated_cred.get_gid_object().get_hrn()
1156 if options.delegate_user:
1157 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1158 + get_leaf(object_hrn) + ".cred")
1159 elif options.delegate_slice:
1160 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1161 + get_leaf(object_hrn) + ".cred")
1163 delegated_cred.save_to_file(dest_fn, save_parents=True)
1165 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1167 def get_trusted_certs(self, options, args):
1169 return uhe trusted certs at this interface (get_trusted_certs)
1171 trusted_certs = self.registry().get_trusted_certs()
1172 for trusted_cert in trusted_certs:
1173 gid = GID(string=trusted_cert)
1175 cert = Certificate(string=trusted_cert)
1176 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())