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.model import RegRecord, RegAuthority, RegUser, RegSlice, RegNode
32 from sfa.storage.model import make_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_variable_to_file(var, filename, format="text"):
92 f = open(filename, "w")
95 elif format == "pickled":
96 f.write(pickle.dumps(var))
98 # this should never happen
99 print "unknown output format", format
102 def save_rspec_to_file(rspec, filename):
103 if not filename.endswith(".rspec"):
104 filename = filename + ".rspec"
105 f = open(filename, 'w')
110 def save_records_to_file(filename, record_dicts, format="xml"):
113 for record_dict in record_dicts:
115 save_record_to_file(filename + "." + str(index), record_dict)
117 save_record_to_file(filename, record_dict)
119 elif format == "xmllist":
120 f = open(filename, "w")
121 f.write("<recordlist>\n")
122 for record_dict in record_dicts:
123 record_obj=make_record (dict=record_dict)
124 f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
125 f.write("</recordlist>\n")
127 elif format == "hrnlist":
128 f = open(filename, "w")
129 for record_dict in record_dicts:
130 record_obj=make_record (dict=record_dict)
131 f.write(record_obj.hrn + "\n")
134 # this should never happen
135 print "unknown output format", format
137 def save_record_to_file(filename, record_dict):
138 rec_record = make_record (dict=record_dict)
139 str = record.save_to_string()
140 f=codecs.open(filename, encoding='utf-8',mode="w")
147 def load_record_from_file(filename):
148 f=codecs.open(filename, encoding="utf-8", mode="r")
149 xml_string = f.read()
151 return make_record (xml=xml_string)
155 def unique_call_id(): return uuid.uuid4().urn
159 # dirty hack to make this class usable from the outside
160 required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user', 'user_private_key']
163 def default_sfi_dir ():
164 if os.path.isfile("./sfi_config"):
167 return os.path.expanduser("~/.sfi/")
169 # dummy to meet Sfi's expectations for its 'options' field
170 # i.e. s/t we can do setattr on
174 def __init__ (self,options=None):
175 if options is None: options=Sfi.DummyOptions()
176 for opt in Sfi.required_options:
177 if not hasattr(options,opt): setattr(options,opt,None)
178 if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
179 self.options = options
181 self.authority = None
182 self.logger = sfi_logger
183 self.logger.enable_console()
184 self.available_names = [ tuple[0] for tuple in Sfi.available ]
185 self.available_dict = dict (Sfi.available)
187 # tuples command-name expected-args in the order in which they should appear in the help
190 ("list", "authority"),
193 ("update", "record"),
196 ("resources", "[slice_hrn]"),
197 ("create", "slice_hrn rspec"),
198 ("delete", "slice_hrn"),
199 ("status", "slice_hrn"),
200 ("start", "slice_hrn"),
201 ("stop", "slice_hrn"),
202 ("reset", "slice_hrn"),
203 ("renew", "slice_hrn time"),
204 ("shutdown", "slice_hrn"),
205 ("get_ticket", "slice_hrn rspec"),
206 ("redeem_ticket", "ticket"),
207 ("delegate", "name"),
208 ("create_gid", "[name]"),
209 ("get_trusted_certs", "cred"),
212 def print_command_help (self, options):
213 verbose=getattr(options,'verbose')
214 format3="%18s %-15s %s"
217 print format3%("command","cmd_args","description")
221 self.create_parser().print_help()
222 for command in self.available_names:
223 args=self.available_dict[command]
224 method=getattr(self,command,None)
226 if method: doc=getattr(method,'__doc__',"")
227 if not doc: doc="*** no doc found ***"
228 doc=doc.strip(" \t\n")
229 doc=doc.replace("\n","\n"+35*' ')
232 print format3%(command,args,doc)
234 self.create_command_parser(command).print_help()
236 def create_command_parser(self, command):
237 if command not in self.available_dict:
238 msg="Invalid command\n"
240 msg += ','.join(self.available_names)
241 self.logger.critical(msg)
244 parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
245 % (command, self.available_dict[command]))
247 # user specifies remote aggregate/sm/component
248 if command in ("resources", "slices", "create", "delete", "start", "stop",
249 "restart", "shutdown", "get_ticket", "renew", "status"):
250 parser.add_option("-d", "--delegate", dest="delegate", default=None,
252 help="Include a credential delegated to the user's root"+\
253 "authority in set of credentials for this call")
255 # registy filter option
256 if command in ("list", "show", "remove"):
257 parser.add_option("-t", "--type", dest="type", type="choice",
258 help="type filter ([all]|user|slice|authority|node|aggregate)",
259 choices=("all", "user", "slice", "authority", "node", "aggregate"),
261 if command in ("resources"):
263 parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
264 help="schema type and version of resulting RSpec")
265 # disable/enable cached rspecs
266 parser.add_option("-c", "--current", dest="current", default=False,
268 help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
270 parser.add_option("-f", "--format", dest="format", type="choice",
271 help="display format ([xml]|dns|ip)", default="xml",
272 choices=("xml", "dns", "ip"))
273 #panos: a new option to define the type of information about resources a user is interested in
274 parser.add_option("-i", "--info", dest="info",
275 help="optional component information", default=None)
278 # 'create' does return the new rspec, makes sense to save that too
279 if command in ("resources", "show", "list", "create_gid", 'create'):
280 parser.add_option("-o", "--output", dest="file",
281 help="output XML to file", metavar="FILE", default=None)
283 if command in ("show", "list"):
284 parser.add_option("-f", "--format", dest="format", type="choice",
285 help="display format ([text]|xml)", default="text",
286 choices=("text", "xml"))
288 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
289 help="output file format ([xml]|xmllist|hrnlist)", default="xml",
290 choices=("xml", "xmllist", "hrnlist"))
292 if command in ("status", "version"):
293 parser.add_option("-o", "--output", dest="file",
294 help="output dictionary to file", metavar="FILE", default=None)
295 parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
296 help="output file format ([text]|pickled)", default="text",
297 choices=("text","pickled"))
299 if command in ("delegate"):
300 parser.add_option("-u", "--user",
301 action="store_true", dest="delegate_user", default=False,
302 help="delegate user credential")
303 parser.add_option("-s", "--slice", dest="delegate_slice",
304 help="delegate slice credential", metavar="HRN", default=None)
306 if command in ("version"):
307 parser.add_option("-R","--registry-version",
308 action="store_true", dest="version_registry", default=False,
309 help="probe registry version instead of sliceapi")
310 parser.add_option("-l","--local",
311 action="store_true", dest="version_local", default=False,
312 help="display version of the local client")
317 def create_parser(self):
319 # Generate command line parser
320 parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
321 description="Commands: %s"%(" ".join(self.available_names)))
322 parser.add_option("-r", "--registry", dest="registry",
323 help="root registry", metavar="URL", default=None)
324 parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
325 help="slice API - in general a SM URL, but can be used to talk to an aggregate")
326 parser.add_option("-R", "--raw", dest="raw", action="store_true", default=False,
327 help="Display raw, unparsed server response")
328 parser.add_option("-d", "--dir", dest="sfi_dir",
329 help="config & working directory - default is %default",
330 metavar="PATH", default=Sfi.default_sfi_dir())
331 parser.add_option("-u", "--user", dest="user",
332 help="user name", metavar="HRN", default=None)
333 parser.add_option("-a", "--auth", dest="auth",
334 help="authority name", metavar="HRN", default=None)
335 parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
336 help="verbose mode - cumulative")
337 parser.add_option("-D", "--debug",
338 action="store_true", dest="debug", default=False,
339 help="Debug (xml-rpc) protocol messages")
340 # would it make sense to use ~/.ssh/id_rsa as a default here ?
341 parser.add_option("-k", "--private-key",
342 action="store", dest="user_private_key", default=None,
343 help="point to the private key file to use if not yet installed in sfi_dir")
344 parser.add_option("-t", "--timeout", dest="timeout", default=None,
345 help="Amout of time to wait before timing out the request")
346 parser.add_option("-?", "--commands",
347 action="store_true", dest="command_help", default=False,
348 help="one page summary on commands & exit")
349 parser.disable_interspersed_args()
354 def print_help (self):
355 self.sfi_parser.print_help()
356 self.command_parser.print_help()
359 # Main: parse arguments and dispatch to command
361 def dispatch(self, command, command_options, command_args):
362 return getattr(self, command)(command_options, command_args)
365 self.sfi_parser = self.create_parser()
366 (options, args) = self.sfi_parser.parse_args()
367 if options.command_help:
368 self.print_command_help(options)
370 self.options = options
372 self.logger.setLevelFromOptVerbose(self.options.verbose)
375 self.logger.critical("No command given. Use -h for help.")
376 self.print_command_help(options)
380 self.command_parser = self.create_command_parser(command)
381 (command_options, command_args) = self.command_parser.parse_args(args[1:])
382 self.command_options = command_options
386 self.logger.info("Command=%s" % command)
389 self.dispatch(command, command_options, command_args)
391 self.logger.critical ("Unknown command %s"%command)
398 def read_config(self):
399 config_file = os.path.join(self.options.sfi_dir,"sfi_config")
401 config = Config (config_file)
403 self.logger.critical("Failed to read configuration file %s"%config_file)
404 self.logger.info("Make sure to remove the export clauses and to add quotes")
405 if self.options.verbose==0:
406 self.logger.info("Re-run with -v for more details")
408 self.logger.log_exc("Could not read config file %s"%config_file)
413 if (self.options.sm is not None):
414 self.sm_url = self.options.sm
415 elif hasattr(config, "SFI_SM"):
416 self.sm_url = config.SFI_SM
418 self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
422 if (self.options.registry is not None):
423 self.reg_url = self.options.registry
424 elif hasattr(config, "SFI_REGISTRY"):
425 self.reg_url = config.SFI_REGISTRY
427 self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
431 if (self.options.user is not None):
432 self.user = self.options.user
433 elif hasattr(config, "SFI_USER"):
434 self.user = config.SFI_USER
436 self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
440 if (self.options.auth is not None):
441 self.authority = self.options.auth
442 elif hasattr(config, "SFI_AUTH"):
443 self.authority = config.SFI_AUTH
445 self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
452 # Get various credential and spec files
454 # Establishes limiting conventions
455 # - conflates MAs and SAs
456 # - assumes last token in slice name is unique
458 # Bootstraps credentials
459 # - bootstrap user credential from self-signed certificate
460 # - bootstrap authority credential from user credential
461 # - bootstrap slice credential from user credential
464 # init self-signed cert, user credentials and gid
465 def bootstrap (self):
466 bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
467 # if -k is provided, use this to initialize private key
468 if self.options.user_private_key:
469 bootstrap.init_private_key_if_missing (self.options.user_private_key)
471 # trigger legacy compat code if needed
472 # the name has changed from just <leaf>.pkey to <hrn>.pkey
473 if not os.path.isfile(bootstrap.private_key_filename()):
474 self.logger.info ("private key not found, trying legacy name")
476 legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
477 self.logger.debug("legacy_private_key=%s"%legacy_private_key)
478 bootstrap.init_private_key_if_missing (legacy_private_key)
479 self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
481 self.logger.log_exc("Can't find private key ")
485 bootstrap.bootstrap_my_gid()
486 # extract what's needed
487 self.private_key = bootstrap.private_key()
488 self.my_credential_string = bootstrap.my_credential_string ()
489 self.my_gid = bootstrap.my_gid ()
490 self.bootstrap = bootstrap
493 def my_authority_credential_string(self):
494 if not self.authority:
495 self.logger.critical("no authority specified. Use -a or set SF_AUTH")
497 return self.bootstrap.authority_credential_string (self.authority)
499 def slice_credential_string(self, name):
500 return self.bootstrap.slice_credential_string (name)
502 # xxx should be supported by sfaclientbootstrap as well
503 def delegate_cred(self, object_cred, hrn, type='authority'):
504 # the gid and hrn of the object we are delegating
505 if isinstance(object_cred, str):
506 object_cred = Credential(string=object_cred)
507 object_gid = object_cred.get_gid_object()
508 object_hrn = object_gid.get_hrn()
510 if not object_cred.get_privileges().get_all_delegate():
511 self.logger.error("Object credential %s does not have delegate bit set"%object_hrn)
514 # the delegating user's gid
515 caller_gidfile = self.my_gid()
517 # the gid of the user who will be delegated to
518 delegee_gid = self.bootstrap.gid(hrn,type)
519 delegee_hrn = delegee_gid.get_hrn()
520 dcred = object_cred.delegate(delegee_gid, self.private_key, caller_gidfile)
521 return dcred.save_to_string(save_parents=True)
524 # Management of the servers
529 if not hasattr (self, 'registry_proxy'):
530 self.logger.info("Contacting Registry at: %s"%self.reg_url)
531 self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid,
532 timeout=self.options.timeout, verbose=self.options.debug)
533 return self.registry_proxy
537 if not hasattr (self, 'sliceapi_proxy'):
538 # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
539 if hasattr(self.command_options,'component') and self.command_options.component:
540 # resolve the hrn at the registry
541 node_hrn = self.command_options.component
542 records = self.registry().Resolve(node_hrn, self.my_credential_string)
543 records = filter_records('node', records)
545 self.logger.warning("No such component:%r"% opts.component)
547 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
548 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
550 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
551 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
552 self.sm_url = 'http://' + self.sm_url
553 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
554 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid,
555 timeout=self.options.timeout, verbose=self.options.debug)
556 return self.sliceapi_proxy
558 def get_cached_server_version(self, server):
559 # check local cache first
562 cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
563 cache_key = server.url + "-version"
565 cache = Cache(cache_file)
568 self.logger.info("Local cache not found at: %s" % cache_file)
571 version = cache.get(cache_key)
574 result = server.GetVersion()
575 version= ReturnValue.get_value(result)
576 # cache version for 20 minutes
577 cache.add(cache_key, version, ttl= 60*20)
578 self.logger.info("Updating cache file %s" % cache_file)
579 cache.save_to_file(cache_file)
583 ### resurrect this temporarily so we can support V1 aggregates for a while
584 def server_supports_options_arg(self, server):
586 Returns true if server support the optional call_id arg, false otherwise.
588 server_version = self.get_cached_server_version(server)
590 # xxx need to rewrite this
591 if int(server_version.get('geni_api')) >= 2:
595 def server_supports_call_id_arg(self, server):
596 server_version = self.get_cached_server_version(server)
598 if 'sfa' in server_version and 'code_tag' in server_version:
599 code_tag = server_version['code_tag']
600 code_tag_parts = code_tag.split("-")
601 version_parts = code_tag_parts[0].split(".")
602 major, minor = version_parts[0], version_parts[1]
603 rev = code_tag_parts[1]
604 if int(major) == 1 and minor == 0 and build >= 22:
608 ### ois = options if supported
609 # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
610 def ois (self, server, option_dict):
611 if self.server_supports_options_arg (server):
613 elif self.server_supports_call_id_arg (server):
614 return [ unique_call_id () ]
618 ### cis = call_id if supported - like ois
619 def cis (self, server):
620 if self.server_supports_call_id_arg (server):
621 return [ unique_call_id ]
625 ######################################## miscell utilities
626 def get_rspec_file(self, rspec):
627 if (os.path.isabs(rspec)):
630 file = os.path.join(self.options.sfi_dir, rspec)
631 if (os.path.isfile(file)):
634 self.logger.critical("No such rspec file %s"%rspec)
637 def get_record_file(self, record):
638 if (os.path.isabs(record)):
641 file = os.path.join(self.options.sfi_dir, record)
642 if (os.path.isfile(file)):
645 self.logger.critical("No such registry record file %s"%record)
649 #==========================================================================
650 # Following functions implement the commands
652 # Registry-related commands
653 #==========================================================================
655 def version(self, options, args):
657 display an SFA server version (GetVersion)
658 or version information about sfi itself
660 if options.version_local:
661 version=version_core()
663 if options.version_registry:
664 server=self.registry()
666 server = self.sliceapi()
667 result = server.GetVersion()
668 version = ReturnValue.get_value(result)
669 pprinter = PrettyPrinter(indent=4)
670 pprinter.pprint(version)
672 save_variable_to_file(version, options.file, options.fileformat)
674 def list(self, options, args):
676 list entries in named authority registry (List)
683 list = self.registry().List(hrn, self.my_credential_string)
685 raise Exception, "Not enough parameters for the 'list' command"
687 # filter on person, slice, site, node, etc.
688 # THis really should be in the self.filter_records funct def comment...
689 list = filter_records(options.type, list)
691 print "%s (%s)" % (record['hrn'], record['type'])
693 save_records_to_file(options.file, list, options.fileformat)
696 def show(self, options, args):
698 show details about named registry record (Resolve)
704 record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
705 record_dicts = filter_records(options.type, record_dicts)
707 self.logger.error("No record of type %s"% options.type)
708 records = [ make_record (dict=record_dict) for record_dict in record_dicts ]
709 for record in records:
710 if (options.format == "text"): record.dump()
711 else: print record.save_as_xml()
713 save_records_to_file(options.file, record_dicts, options.fileformat)
716 def add(self, options, args):
717 "add record into registry from xml file (Register)"
718 auth_cred = self.my_authority_credential_string()
722 record_filepath = args[0]
723 rec_file = self.get_record_file(record_filepath)
724 record = load_record_from_file(rec_file).todict()
725 return self.registry().Register(record, auth_cred)
727 def update(self, options, args):
728 "update record into registry from xml file (Update)"
732 rec_file = self.get_record_file(args[0])
733 record = load_record_from_file(rec_file)
734 if record.type == "user":
735 if record.hrn == self.user:
736 cred = self.my_credential_string
738 cred = self.my_authority_credential_string()
739 elif record.type in ["slice"]:
741 cred = self.slice_credential_string(record.hrn)
742 except ServerException, e:
743 # XXX smbaker -- once we have better error return codes, update this
744 # to do something better than a string compare
745 if "Permission error" in e.args[0]:
746 cred = self.my_authority_credential_string()
749 elif record.type in ["authority"]:
750 cred = self.my_authority_credential_string()
751 elif record.type == 'node':
752 cred = self.my_authority_credential_string()
754 raise "unknown record type" + record.type
755 record_dict = record.todict()
756 return self.registry().Update(record_dict, cred)
758 def remove(self, options, args):
759 "remove registry record by name (Remove)"
760 auth_cred = self.my_authority_credential_string()
768 return self.registry().Remove(hrn, auth_cred, type)
770 # ==================================================================
771 # Slice-related commands
772 # ==================================================================
774 def slices(self, options, args):
775 "list instantiated slices (ListSlices) - returns urn's"
776 server = self.sliceapi()
778 creds = [self.my_credential_string]
780 delegated_cred = self.delegate_cred(self.my_credential_string, get_authority(self.authority))
781 creds.append(delegated_cred)
782 # options and call_id when supported
784 api_options['call_id']=unique_call_id()
785 result = server.ListSlices(creds, *self.ois(server,api_options))
786 value = ReturnValue.get_value(result)
793 # show rspec for named slice
794 def resources(self, options, args):
796 with no arg, discover available resources, (ListResources)
797 or with an slice hrn, shows currently provisioned resources
799 server = self.sliceapi()
804 creds.append(self.slice_credential_string(args[0]))
806 creds.append(self.my_credential_string)
808 creds.append(self.delegate_cred(cred, get_authority(self.authority)))
810 # no need to check if server accepts the options argument since the options has
811 # been a required argument since v1 API
813 # always send call_id to v2 servers
814 api_options ['call_id'] = unique_call_id()
815 # ask for cached value if available
816 api_options ['cached'] = True
819 api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
821 api_options['info'] = options.info
823 if options.current == True:
824 api_options['cached'] = False
826 api_options['cached'] = True
827 if options.rspec_version:
828 version_manager = VersionManager()
829 server_version = self.get_cached_server_version(server)
830 if 'sfa' in server_version:
831 # just request the version the client wants
832 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
834 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
836 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
837 result = server.ListResources (creds, api_options)
838 value = ReturnValue.get_value(result)
839 if options.file is None:
843 display_rspec(value, options.format)
845 save_rspec_to_file(value, options.file)
848 def create(self, options, args):
850 create or update named slice with given rspec
852 server = self.sliceapi()
854 # xxx do we need to check usage (len(args)) ?
857 slice_urn = hrn_to_urn(slice_hrn, 'slice')
860 creds = [self.slice_credential_string(slice_hrn)]
861 delegated_cred = None
862 server_version = self.get_cached_server_version(server)
863 if server_version.get('interface') == 'slicemgr':
864 # delegate our cred to the slice manager
865 # do not delegate cred to slicemgr...not working at the moment
867 #if server_version.get('hrn'):
868 # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
869 #elif server_version.get('urn'):
870 # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
873 rspec_file = self.get_rspec_file(args[1])
874 rspec = open(rspec_file).read()
877 # need to pass along user keys to the aggregate.
879 # { urn: urn:publicid:IDN+emulab.net+user+alice
880 # keys: [<ssh key A>, <ssh key B>]
883 slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
884 if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
885 slice_record = slice_records[0]
886 user_hrns = slice_record['researcher']
887 user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
888 user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
890 if 'sfa' not in server_version:
891 users = pg_users_arg(user_records)
893 rspec.filter({'component_manager_id': server_version['urn']})
894 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
896 users = sfa_users_arg(user_records, slice_record)
898 # do not append users, keys, or slice tags. Anything
899 # not contained in this request will be removed from the slice
901 # CreateSliver has supported the options argument for a while now so it should
902 # be safe to assume this server support it
904 api_options ['append'] = False
905 api_options ['call_id'] = unique_call_id()
907 result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
908 value = ReturnValue.get_value(result)
909 if options.file is None:
915 save_rspec_to_file (value, options.file)
918 def delete(self, options, args):
920 delete named slice (DeleteSliver)
922 server = self.sliceapi()
926 slice_urn = hrn_to_urn(slice_hrn, 'slice')
929 slice_cred = self.slice_credential_string(slice_hrn)
932 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
933 creds.append(delegated_cred)
935 # options and call_id when supported
937 api_options ['call_id'] = unique_call_id()
938 result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
939 value = ReturnValue.get_value(result)
946 def status(self, options, args):
948 retrieve slice status (SliverStatus)
950 server = self.sliceapi()
954 slice_urn = hrn_to_urn(slice_hrn, 'slice')
957 slice_cred = self.slice_credential_string(slice_hrn)
960 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
961 creds.append(delegated_cred)
963 # options and call_id when supported
965 api_options['call_id']=unique_call_id()
966 result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
967 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')
986 slice_cred = self.slice_credential_string(args[0])
989 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
990 creds.append(delegated_cred)
991 # xxx Thierry - does this not need an api_options as well ?
992 result = server.Start(slice_urn, creds)
993 value = ReturnValue.get_value(result)
1000 def stop(self, options, args):
1002 stop named slice (Stop)
1004 server = self.sliceapi()
1007 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1009 slice_cred = self.slice_credential_string(args[0])
1010 creds = [slice_cred]
1011 if options.delegate:
1012 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1013 creds.append(delegated_cred)
1014 result = server.Stop(slice_urn, creds)
1015 value = ReturnValue.get_value(result)
1016 if self.options.raw:
1023 def reset(self, options, args):
1025 reset named slice (reset_slice)
1027 server = self.sliceapi()
1030 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1032 slice_cred = self.slice_credential_string(args[0])
1033 creds = [slice_cred]
1034 if options.delegate:
1035 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1036 creds.append(delegated_cred)
1037 result = server.reset_slice(creds, slice_urn)
1038 value = ReturnValue.get_value(result)
1039 if self.options.raw:
1045 def renew(self, options, args):
1047 renew slice (RenewSliver)
1049 server = self.sliceapi()
1052 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1054 slice_cred = self.slice_credential_string(args[0])
1055 creds = [slice_cred]
1056 if options.delegate:
1057 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1058 creds.append(delegated_cred)
1061 # options and call_id when supported
1063 api_options['call_id']=unique_call_id()
1064 result = server.RenewSliver(slice_urn, creds, time, *self.ois(server,api_options))
1065 value = ReturnValue.get_value(result)
1066 if self.options.raw:
1073 def shutdown(self, options, args):
1075 shutdown named slice (Shutdown)
1077 server = self.sliceapi()
1080 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1082 slice_cred = self.slice_credential_string(slice_hrn)
1083 creds = [slice_cred]
1084 if options.delegate:
1085 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1086 creds.append(delegated_cred)
1087 result = server.Shutdown(slice_urn, creds)
1088 value = ReturnValue.get_value(result)
1089 if self.options.raw:
1096 def get_ticket(self, options, args):
1098 get a ticket for the specified slice
1100 server = self.sliceapi()
1102 slice_hrn, rspec_path = args[0], args[1]
1103 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1105 slice_cred = self.slice_credential_string(slice_hrn)
1106 creds = [slice_cred]
1107 if options.delegate:
1108 delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority))
1109 creds.append(delegated_cred)
1111 rspec_file = self.get_rspec_file(rspec_path)
1112 rspec = open(rspec_file).read()
1113 # options and call_id when supported
1115 api_options['call_id']=unique_call_id()
1116 # get ticket at the server
1117 ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1119 file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1120 self.logger.info("writing ticket to %s"%file)
1121 ticket = SfaTicket(string=ticket_string)
1122 ticket.save_to_file(filename=file, save_parents=True)
1124 def redeem_ticket(self, options, args):
1126 Connects to nodes in a slice and redeems a ticket
1127 (slice hrn is retrieved from the ticket)
1129 ticket_file = args[0]
1131 # get slice hrn from the ticket
1132 # use this to get the right slice credential
1133 ticket = SfaTicket(filename=ticket_file)
1135 ticket_string = ticket.save_to_string(save_parents=True)
1137 slice_hrn = ticket.gidObject.get_hrn()
1138 slice_urn = hrn_to_urn(slice_hrn, 'slice')
1139 #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1140 slice_cred = self.slice_credential_string(slice_hrn)
1142 # get a list of node hostnames from the RSpec
1143 tree = etree.parse(StringIO(ticket.rspec))
1144 root = tree.getroot()
1145 hostnames = root.xpath("./network/site/node/hostname/text()")
1147 # create an xmlrpc connection to the component manager at each of these
1148 # components and gall redeem_ticket
1150 for hostname in hostnames:
1152 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1153 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1154 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1155 server = self.server_proxy(hostname, CM_PORT, self.private_key,
1156 timeout=self.options.timeout, verbose=self.options.debug)
1157 server.RedeemTicket(ticket_string, slice_cred)
1158 self.logger.info("Success")
1159 except socket.gaierror:
1160 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1161 except Exception, e:
1162 self.logger.log_exc(e.message)
1165 def create_gid(self, options, args):
1167 Create a GID (CreateGid)
1172 target_hrn = args[0]
1173 gid = self.registry().CreateGid(self.my_credential_string, target_hrn, self.bootstrap.my_gid_string())
1175 filename = options.file
1177 filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1178 self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1179 GID(string=gid).save_to_file(filename)
1182 def delegate(self, options, args):
1184 (locally) create delegate credential for use by given hrn
1186 delegee_hrn = args[0]
1187 if options.delegate_user:
1188 cred = self.delegate_cred(self.my_credential_string, delegee_hrn, 'user')
1189 elif options.delegate_slice:
1190 slice_cred = self.slice_credential_string(options.delegate_slice)
1191 cred = self.delegate_cred(slice_cred, delegee_hrn, 'slice')
1193 self.logger.warning("Must specify either --user or --slice <hrn>")
1195 delegated_cred = Credential(string=cred)
1196 object_hrn = delegated_cred.get_gid_object().get_hrn()
1197 if options.delegate_user:
1198 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_"
1199 + get_leaf(object_hrn) + ".cred")
1200 elif options.delegate_slice:
1201 dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_"
1202 + get_leaf(object_hrn) + ".cred")
1204 delegated_cred.save_to_file(dest_fn, save_parents=True)
1206 self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
1208 def get_trusted_certs(self, options, args):
1210 return uhe trusted certs at this interface (get_trusted_certs)
1212 trusted_certs = self.registry().get_trusted_certs()
1213 for trusted_cert in trusted_certs:
1214 gid = GID(string=trusted_cert)
1216 cert = Certificate(string=trusted_cert)
1217 self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())