X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=sfa%2Fclient%2Fsfi.py;h=83803e1cf2530ca332f5f45f57e649e7682d983d;hb=fd395e1944dcd49f10a4d5b27ce4983ad389fb96;hp=29149bdeeccd94cf7ce62dcb0d2319b736111320;hpb=207cebe87eb97f65994d277fce9760522e3f4182;p=sfa.git diff --git a/sfa/client/sfi.py b/sfa/client/sfi.py old mode 100755 new mode 100644 index 29149bde..20b878ed --- a/sfa/client/sfi.py +++ b/sfa/client/sfi.py @@ -1,48 +1,64 @@ -#! /usr/bin/env python +""" +sfi.py - basic SFA command-line client +this module is also used in sfascan +""" -# sfi -- slice-based facility interface +# pylint: disable=c0111, c0413 -# xxx NOTE this will soon be reviewed to take advantage of sfaclientlib +from __future__ import print_function import sys sys.path.append('.') -import os, os.path -#import tempfile -import socket +import os +import os.path +import re import datetime import codecs import pickle +import json +import shutil from lxml import etree -from StringIO import StringIO from optparse import OptionParser +from pprint import PrettyPrinter +from tempfile import mkstemp from sfa.trust.certificate import Keypair, Certificate from sfa.trust.gid import GID from sfa.trust.credential import Credential from sfa.trust.sfaticket import SfaTicket -from sfa.util.sfalogging import sfi_logger -from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn +from sfa.util.faults import SfaInvalidArgument +from sfa.util.sfalogging import init_logger, logger +from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn from sfa.util.config import Config from sfa.util.version import version_core from sfa.util.cache import Cache +from sfa.util.printable import printable +from sfa.util.py23 import StringIO -from sfa.storage.record import SfaRecord, UserRecord, SliceRecord, NodeRecord, AuthorityRecord +from sfa.storage.record import Record from sfa.rspecs.rspec import RSpec from sfa.rspecs.rspec_converter import RSpecConverter from sfa.rspecs.version_manager import VersionManager -from sfa.client.return_value import ReturnValue -import sfa.client.sfaprotocol as sfaprotocol +from sfa.client.sfaclientlib import SfaClientBootstrap +from sfa.client.sfaserverproxy import SfaServerProxy, ServerException from sfa.client.client_helper import pg_users_arg, sfa_users_arg +from sfa.client.return_value import ReturnValue +from sfa.client.candidates import Candidates +from sfa.client.manifolduploader import ManifoldUploader -AGGREGATE_PORT=12346 -CM_PORT=12346 +CM_PORT = 12346 +DEFAULT_RSPEC_VERSION = "GENI 3" + +from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \ + terminal_render, filter_records -# utility methods here # display methods + + def display_rspec(rspec, format='rspec'): if format in ['dns']: tree = etree.parse(StringIO(rspec)) @@ -57,24 +73,27 @@ def display_rspec(rspec, format='rspec'): else: result = rspec - print result + print(result) return + def display_list(results): for result in results: - print result + print(result) + def display_records(recordList, dump=False): ''' Print all fields in the record''' for record in recordList: display_record(record, dump) + def display_record(record, dump=False): if dump: - record.dump() + record.dump(sort=True) else: info = record.getdict() - print "%s (%s)" % (info['hrn'], info['type']) + print("{} ({})".format(info['hrn'], info['type'])) return @@ -86,90 +105,227 @@ def filter_records(type, records): return filtered_records +def credential_printable(cred): + credential = Credential(cred=cred) + result = "" + result += credential.pretty_cred() + result += "\n" + rights = credential.get_privileges() + result += "type={}\n".format(credential.type) + result += "version={}\n".format(credential.version) + result += "rights={}\n".format(rights) + return result + + +def show_credentials(cred_s): + if not isinstance(cred_s, list): + cred_s = [cred_s] + for cred in cred_s: + print("Using Credential {}".format(credential_printable(cred))) + # save methods -def save_variable_to_file(var, filename, format="text"): - f = open(filename, "w") + +# raw + + +def save_raw_to_file(var, filename, format='text', banner=None): + if filename == '-': + _save_raw_to_file(var, sys.stdout, format, banner) + else: + with open(filename, w) as fileobj: + _save_raw_to_file(var, fileobj, format, banner) + print("(Over)wrote {}".format(filename)) + + +def _save_raw_to_file(var, f, format, banner): if format == "text": - f.write(str(var)) + if banner: + f.write(banner + "\n") + f.write("{}".format(var)) + if banner: + f.write('\n' + banner + "\n") elif format == "pickled": f.write(pickle.dumps(var)) + elif format == "json": + f.write(json.dumps(var)) # python 2.6 else: # this should never happen - print "unknown output format", format + print("unknown output format", format) + +### def save_rspec_to_file(rspec, filename): if not filename.endswith(".rspec"): filename = filename + ".rspec" - f = open(filename, 'w') - f.write(rspec) - f.close() - return + with open(filename, 'w') as f: + f.write("{}".format(rspec)) + print("(Over)wrote {}".format(filename)) + + +def save_record_to_file(filename, record_dict): + record = Record(dict=record_dict) + xml = record.save_as_xml() + with codecs.open(filename, encoding='utf-8', mode="w") as f: + f.write(xml) + print("(Over)wrote {}".format(filename)) -def save_records_to_file(filename, recordList, format="xml"): + +def save_records_to_file(filename, record_dicts, format="xml"): if format == "xml": - index = 0 - for record in recordList: - if index > 0: - save_record_to_file(filename + "." + str(index), record) - else: - save_record_to_file(filename, record) - index = index + 1 + for index, record_dict in enumerate(record_dicts): + save_record_to_file(filename + "." + str(index), record_dict) elif format == "xmllist": - f = open(filename, "w") - f.write("\n") - for record in recordList: - record = SfaRecord(dict=record) - f.write('\n') - f.write("\n") - f.close() + with open(filename, "w") as f: + f.write("\n") + for record_dict in record_dicts: + record_obj = Record(dict=record_dict) + f.write('\n') + f.write("\n") + print("(Over)wrote {}".format(filename)) + elif format == "hrnlist": - f = open(filename, "w") - for record in recordList: - record = SfaRecord(dict=record) - f.write(record.get_name() + "\n") - f.close() + with open(filename, "w") as f: + for record_dict in record_dicts: + record_obj = Record(dict=record_dict) + f.write(record_obj.hrn + "\n") + print("(Over)wrote {}".format(filename)) + else: # this should never happen - print "unknown output format", format - -def save_record_to_file(filename, record): - if record['type'] in ['user']: - record = UserRecord(dict=record) - elif record['type'] in ['slice']: - record = SliceRecord(dict=record) - elif record['type'] in ['node']: - record = NodeRecord(dict=record) - elif record['type'] in ['authority', 'ma', 'sa']: - record = AuthorityRecord(dict=record) - else: - record = SfaRecord(dict=record) - str = record.save_to_string() - f=codecs.open(filename, encoding='utf-8',mode="w") - f.write(str) - f.close() - return + print("unknown output format", format) +# minimally check a key argument + + +def check_ssh_key(key): + good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$' + return re.match(good_ssh_key, key, re.IGNORECASE) # load methods -def load_record_from_file(filename): - f=codecs.open(filename, encoding="utf-8", mode="r") - str = f.read() - f.close() - record = SfaRecord(string=str) - return record +def normalize_type(type): + if type.startswith('au'): + return 'authority' + elif type.startswith('us'): + return 'user' + elif type.startswith('sl'): + return 'slice' + elif type.startswith('no'): + return 'node' + elif type.startswith('ag'): + return 'aggregate' + elif type.startswith('al'): + return 'all' + else: + print('unknown type {} - should start with one of au|us|sl|no|ag|al'.format(type)) + return None + + +def load_record_from_opts(options): + record_dict = {} + if hasattr(options, 'xrn') and options.xrn: + if hasattr(options, 'type') and options.type: + xrn = Xrn(options.xrn, options.type) + else: + xrn = Xrn(options.xrn) + record_dict['urn'] = xrn.get_urn() + record_dict['hrn'] = xrn.get_hrn() + record_dict['type'] = xrn.get_type() + if hasattr(options, 'key') and options.key: + try: + pubkey = open(options.key, 'r').read() + except IOError: + pubkey = options.key + if not check_ssh_key(pubkey): + raise SfaInvalidArgument( + name='key', msg="Could not find file, or wrong key format") + record_dict['reg-keys'] = [pubkey] + if hasattr(options, 'slices') and options.slices: + record_dict['slices'] = options.slices + if hasattr(options, 'reg_researchers') and options.reg_researchers is not None: + record_dict['reg-researchers'] = options.reg_researchers + if hasattr(options, 'email') and options.email: + record_dict['email'] = options.email + # authorities can have a name for standalone deployment + if hasattr(options, 'name') and options.name: + record_dict['name'] = options.name + if hasattr(options, 'reg_pis') and options.reg_pis: + record_dict['reg-pis'] = options.reg_pis + + # handle extra settings + record_dict.update(options.extras) + + return Record(dict=record_dict) + + +def load_record_from_file(filename): + with codecs.open(filename, encoding="utf-8", mode="r") as f: + xml_str = f.read() + return Record(xml=xml_str) + import uuid + + def unique_call_id(): return uuid.uuid4().urn +# a simple model for maintaing 3 doc attributes per command (instead of just one) +# essentially for the methods that implement a subcommand like sfi list +# we need to keep track of +# (*) doc a few lines that tell what it does, still located in __doc__ +# (*) args_string a simple one-liner that describes mandatory arguments +# (*) example well, one or several releant examples +# +# since __doc__ only accounts for one, we use this simple mechanism below +# however we keep doc in place for easier migration + +from functools import wraps + +# we use a list as well as a dict so we can keep track of the order +commands_list = [] +commands_dict = {} + + +def declare_command(args_string, example, aliases=None): + def wrap(m): + name = getattr(m, '__name__') + doc = getattr(m, '__doc__', "-- missing doc --") + doc = doc.strip(" \t\n") + commands_list.append(name) + # last item is 'canonical' name, so we can know which commands are + # aliases + command_tuple = (doc, args_string, example, name) + commands_dict[name] = command_tuple + if aliases is not None: + for alias in aliases: + commands_list.append(alias) + commands_dict[alias] = command_tuple + + @wraps(m) + def new_method(*args, **kwds): return m(*args, **kwds) + return new_method + return wrap + + +def remove_none_fields(record): + none_fields = [k for (k, v) in record.items() if v is None] + for k in none_fields: + del record[k] + +########## + + class Sfi: - - required_options=['verbose', 'debug', 'registry', 'sm', 'auth', 'user'] + + # dirty hack to make this class usable from the outside + required_options = ['verbose', 'debug', 'registry', + 'sm', 'auth', 'user', 'user_private_key'] @staticmethod - def default_sfi_dir (): - if os.path.isfile("./sfi_config"): + def default_sfi_dir(): + if os.path.isfile("./sfi_config"): return os.getcwd() else: return os.path.expanduser("~/.sfi/") @@ -179,1029 +335,1643 @@ class Sfi: class DummyOptions: pass - def __init__ (self,options=None): - if options is None: options=Sfi.DummyOptions() + def __init__(self, options=None): + if options is None: + options = Sfi.DummyOptions() for opt in Sfi.required_options: - if not hasattr(options,opt): setattr(options,opt,None) - if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir() + if not hasattr(options, opt): + setattr(options, opt, None) + if not hasattr(options, 'sfi_dir'): + options.sfi_dir = Sfi.default_sfi_dir() self.options = options - self.slicemgr = None - self.registry = None self.user = None self.authority = None - self.hashrequest = False - self.logger = sfi_logger - self.logger.enable_console() - - def create_cmd_parser(self, command): - cmdargs = {"list": "authority", - "show": "name", - "remove": "name", - "add": "record", - "update": "record", - "create_gid": "[name]", - "get_gid": [], - "get_trusted_certs": "cred", - "slices": "", - "resources": "[name]", - "create": "name rspec", - "get_ticket": "name rspec", - "redeem_ticket": "ticket", - "delete": "name", - "reset": "name", - "start": "name", - "stop": "name", - "delegate": "name", - "status": "name", - "renew": "name", - "shutdown": "name", - "version": "", - } - - if command not in cmdargs: - msg="Invalid command\n" - msg+="Commands: " - msg += ','.join(cmdargs.keys()) - self.logger.critical(msg) - sys.exit(2) - - parser = OptionParser(usage="sfi [sfi_options] %s [options] %s" \ - % (command, cmdargs[command])) - - # user specifies remote aggregate/sm/component - if command in ("resources", "slices", "create", "delete", "start", "stop", - "restart", "shutdown", "get_ticket", "renew", "status"): - parser.add_option("-a", "--aggregate", dest="aggregate", - default=None, help="aggregate host") - parser.add_option("-p", "--port", dest="port", - default=AGGREGATE_PORT, help="aggregate port") - parser.add_option("-c", "--component", dest="component", default=None, - help="component hrn") - parser.add_option("-d", "--delegate", dest="delegate", default=None, - action="store_true", - help="Include a credential delegated to the user's root"+\ - "authority in set of credentials for this call") - - # registy filter option - if command in ("list", "show", "remove"): - parser.add_option("-t", "--type", dest="type", type="choice", - help="type filter ([all]|user|slice|authority|node|aggregate)", - choices=("all", "user", "slice", "authority", "node", "aggregate"), - default="all") - # display formats - if command in ("resources"): - parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1", - help="schema type and version of resulting RSpec") - parser.add_option("-f", "--format", dest="format", type="choice", - help="display format ([xml]|dns|ip)", default="xml", - choices=("xml", "dns", "ip")) - #panos: a new option to define the type of information about resources a user is interested in - parser.add_option("-i", "--info", dest="info", - help="optional component information", default=None) - - - # 'create' does return the new rspec, makes sense to save that too - if command in ("resources", "show", "list", "create_gid", 'create'): - parser.add_option("-o", "--output", dest="file", - help="output XML to file", metavar="FILE", default=None) - - if command in ("show", "list"): - parser.add_option("-f", "--format", dest="format", type="choice", - help="display format ([text]|xml)", default="text", - choices=("text", "xml")) - - parser.add_option("-F", "--fileformat", dest="fileformat", type="choice", - help="output file format ([xml]|xmllist|hrnlist)", default="xml", - choices=("xml", "xmllist", "hrnlist")) - - if command in ("status", "version"): - parser.add_option("-o", "--output", dest="file", - help="output dictionary to file", metavar="FILE", default=None) - parser.add_option("-F", "--fileformat", dest="fileformat", type="choice", - help="output file format ([text]|pickled)", default="text", - choices=("text","pickled")) - - if command in ("delegate"): - parser.add_option("-u", "--user", - action="store_true", dest="delegate_user", default=False, - help="delegate user credential") - parser.add_option("-s", "--slice", dest="delegate_slice", - help="delegate slice credential", metavar="HRN", default=None) - - if command in ("version"): - parser.add_option("-a", "--aggregate", dest="aggregate", - default=None, help="aggregate host") - parser.add_option("-p", "--port", dest="port", - default=AGGREGATE_PORT, help="aggregate port") - parser.add_option("-R","--registry-version", - action="store_true", dest="version_registry", default=False, - help="probe registry version instead of slicemgr") - parser.add_option("-l","--local", - action="store_true", dest="version_local", default=False, - help="display version of the local client") - - return parser - - - def create_parser(self): + logger.enable_console() + # various auxiliary material that we keep at hand + self.command = None + # need to call this other than just 'config' as we have a + # command/method with that name + self.config_instance = None + self.config_file = None + self.client_bootstrap = None + + # suitable if no reasonable command has been provided + def print_commands_help(self, options): + verbose = getattr(options, 'verbose') + format3 = "%10s %-35s %s" + format3offset = 47 + line = 80 * '-' + if not verbose: + print(format3 % ("command", "cmd_args", "description")) + print(line) + else: + print(line) + self.create_parser_global().print_help() + # preserve order from the code + for command in commands_list: + try: + (doc, args_string, example, canonical) = commands_dict[command] + except: + print("Cannot find info on command %s - skipped" % command) + continue + if verbose: + print(line) + if command == canonical: + doc = doc.replace("\n", "\n" + format3offset * ' ') + print(format3 % (command, args_string, doc)) + if verbose: + self.create_parser_command(command).print_help() + else: + print(format3 % (command, "<>" % canonical, "")) + # now if a known command was found we can be more verbose on that one + def print_help(self): + print("==================== Generic sfi usage") + self.sfi_parser.print_help() + (doc, _, example, canonical) = commands_dict[self.command] + if canonical != self.command: + print("\n==================== NOTE: {} is an alias for genuine {}" + .format(self.command, canonical)) + self.command = canonical + print("\n==================== Purpose of {}".format(self.command)) + print(doc) + print("\n==================== Specific usage for {}".format(self.command)) + self.command_parser.print_help() + if example: + print("\n==================== {} example(s)".format(self.command)) + print(example) + + def create_parser_global(self): # Generate command line parser - parser = OptionParser(usage="sfi [options] command [command_options] [command_args]", - description="Commands: gid,list,show,remove,add,update,nodes,slices,resources,create,delete,start,stop,reset") + parser = OptionParser(add_help_option=False, + usage="sfi [sfi_options] command [cmd_options] [cmd_args]", + description="Commands: {}".format(" ".join(commands_list))) parser.add_option("-r", "--registry", dest="registry", - help="root registry", metavar="URL", default=None) - parser.add_option("-s", "--slicemgr", dest="sm", - help="slice manager", metavar="URL", default=None) + help="root registry", metavar="URL", default=None) + parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL", + help="slice API - in general a SM URL, but can be used to talk to an aggregate") + parser.add_option("-R", "--raw", dest="raw", default=None, + help="Save raw, unparsed server response to a file") + parser.add_option("", "--rawformat", dest="rawformat", type="choice", + help="raw file format ([text]|pickled|json)", default="text", + choices=("text", "pickled", "json")) + parser.add_option("", "--rawbanner", dest="rawbanner", default=None, + help="text string to write before and after raw output") parser.add_option("-d", "--dir", dest="sfi_dir", - help="config & working directory - default is " + Sfi.default_sfi_dir(), - metavar="PATH", default=Sfi.default_sfi_dir()) + help="config & working directory - default is %default", + metavar="PATH", default=Sfi.default_sfi_dir()) parser.add_option("-u", "--user", dest="user", - help="user name", metavar="HRN", default=None) + help="user name", metavar="HRN", default=None) parser.add_option("-a", "--auth", dest="auth", - help="authority name", metavar="HRN", default=None) + help="authority name", metavar="HRN", default=None) parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0, - help="verbose mode - cumulative") + help="verbose mode - cumulative") parser.add_option("-D", "--debug", action="store_true", dest="debug", default=False, help="Debug (xml-rpc) protocol messages") - parser.add_option("-p", "--protocol", dest="protocol", default="xmlrpc", - help="RPC protocol (xmlrpc or soap)") - parser.add_option("-k", "--hashrequest", - action="store_true", dest="hashrequest", default=False, - help="Create a hash of the request that will be authenticated on the server") + # would it make sense to use ~/.ssh/id_rsa as a default here ? + parser.add_option("-k", "--private-key", + action="store", dest="user_private_key", default=None, + help="point to the private key file to use if not yet installed in sfi_dir") parser.add_option("-t", "--timeout", dest="timeout", default=None, - help="Amout of time tom wait before timing out the request") + help="Amout of time to wait before timing out the request") + parser.add_option("-h", "--help", + action="store_true", dest="help", default=False, + help="one page summary on commands & exit") parser.disable_interspersed_args() return parser - + def create_parser_command(self, command): + if command not in commands_dict: + msg = "Invalid command\n" + msg += "Commands: " + msg += ','.join(commands_list) + logger.critical(msg) + sys.exit(2) + + # retrieve args_string + (_, args_string, __, canonical) = commands_dict[command] + + parser = OptionParser(add_help_option=False, + usage="sfi [sfi_options] {} [cmd_options] {}" + .format(command, args_string)) + parser.add_option("-h", "--help", dest='help', action='store_true', default=False, + help="Summary of one command usage") + + if canonical in ("config"): + parser.add_option('-m', '--myslice', dest='myslice', action='store_true', default=False, + help='how myslice config variables as well') + + if canonical in ("version"): + parser.add_option("-l", "--local", + action="store_true", dest="version_local", default=False, + help="display version of the local client") + + if canonical in ("version", "trusted", "introspect"): + parser.add_option("-R", "--registry_interface", + action="store_true", dest="registry_interface", default=False, + help="target the registry interface instead of slice interface") + + if canonical in ("register", "update"): + parser.add_option('-x', '--xrn', dest='xrn', + metavar='', help='object hrn/urn (mandatory)') + parser.add_option('-t', '--type', dest='type', metavar='', + help='object type (2 first chars is enough)', default=None) + parser.add_option('-e', '--email', dest='email', + default="", help="email (mandatory for users)") + parser.add_option('-n', '--name', dest='name', + default="", help="name (optional for authorities)") + parser.add_option('-k', '--key', dest='key', metavar='', help='public key string or file', + default=None) + parser.add_option('-s', '--slices', dest='slices', metavar='', help='Set/replace slice xrns', + default='', type="str", action='callback', callback=optparse_listvalue_callback) + parser.add_option('-r', '--researchers', dest='reg_researchers', metavar='', + help='Set/replace slice researchers - use -r none to reset', default=None, type="str", action='callback', + callback=optparse_listvalue_callback) + parser.add_option('-p', '--pis', dest='reg_pis', metavar='', help='Set/replace Principal Investigators/Project Managers', + default='', type="str", action='callback', callback=optparse_listvalue_callback) + parser.add_option('-X', '--extra', dest='extras', default={}, type='str', metavar="", + action="callback", callback=optparse_dictvalue_callback, nargs=1, + help="set extra/testbed-dependent flags, e.g. --extra enabled=true") + + # user specifies remote aggregate/sm/component + if canonical in ("resources", "describe", "allocate", "provision", "delete", "allocate", "provision", + "action", "shutdown", "renew", "status"): + parser.add_option("-d", "--delegate", dest="delegate", default=None, + action="store_true", + help="Include a credential delegated to the user's root" + + "authority in set of credentials for this call") + + # show_credential option + if canonical in ("list", "resources", "describe", "provision", "allocate", "register", + "update", "remove", "delete", "status", "renew"): + parser.add_option("-C", "--credential", dest='show_credential', action='store_true', default=False, + help="show credential(s) used in human-readable form") + if canonical in ("renew"): + parser.add_option("-l", "--as-long-as-possible", dest='alap', action='store_true', default=False, + help="renew as long as possible") + # registy filter option + if canonical in ("list", "show", "remove"): + parser.add_option("-t", "--type", dest="type", metavar="", + default="all", + help="type filter - 2 first chars is enough ([all]|user|slice|authority|node|aggregate)") + if canonical in ("show"): + parser.add_option("-k", "--key", dest="keys", action="append", default=[], + help="specify specific keys to be displayed from record") + parser.add_option("-n", "--no-details", dest="no_details", action="store_true", default=False, + help="call Resolve without the 'details' option") + if canonical in ("resources", "describe"): + # rspec version + parser.add_option("-r", "--rspec-version", dest="rspec_version", default=DEFAULT_RSPEC_VERSION, + help="schema type and version of resulting RSpec (default:{})".format(DEFAULT_RSPEC_VERSION)) + # disable/enable cached rspecs + parser.add_option("-c", "--current", dest="current", default=False, + action="store_true", + help="Request the current rspec bypassing the cache. Cached rspecs are returned by default") + # display formats + parser.add_option("-f", "--format", dest="format", type="choice", + help="display format ([xml]|dns|ip)", default="xml", + choices=("xml", "dns", "ip")) + # panos: a new option to define the type of information about + # resources a user is interested in + parser.add_option("-i", "--info", dest="info", + help="optional component information", default=None) + # a new option to retrieve or not reservation-oriented RSpecs + # (leases) + parser.add_option("-l", "--list_leases", dest="list_leases", type="choice", + help="Retrieve or not reservation-oriented RSpecs ([resources]|leases|all)", + choices=("all", "resources", "leases"), default="resources") + + if canonical in ("resources", "describe", "allocate", "provision", "show", "list", "gid"): + parser.add_option("-o", "--output", dest="file", + help="output XML to file", metavar="FILE", default=None) + + if canonical in ("show", "list"): + parser.add_option("-f", "--format", dest="format", type="choice", + help="display format ([text]|xml)", default="text", + choices=("text", "xml")) + + parser.add_option("-F", "--fileformat", dest="fileformat", type="choice", + help="output file format ([xml]|xmllist|hrnlist)", default="xml", + choices=("xml", "xmllist", "hrnlist")) + if canonical == 'list': + parser.add_option("-r", "--recursive", dest="recursive", action='store_true', + help="list all child records", default=False) + parser.add_option("-v", "--verbose", dest="verbose", action='store_true', + help="gives details, like user keys", default=False) + if canonical in ("delegate"): + parser.add_option("-u", "--user", + action="store_true", dest="delegate_user", default=False, + help="delegate your own credentials; default if no other option is provided") + parser.add_option("-s", "--slice", dest="delegate_slices", action='append', default=[], + metavar="slice_hrn", help="delegate cred. for slice HRN") + parser.add_option("-a", "--auths", dest='delegate_auths', action='append', default=[], + metavar='auth_hrn', help="delegate cred for auth HRN") + # this primarily is a shorthand for -A my_hrn^ + parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true', + help="delegate your PI credentials, so s.t. like -A your_hrn^") + parser.add_option("-A", "--to-authority", dest='delegate_to_authority', action='store_true', default=False, + help="""by default the mandatory argument is expected to be a user, +use this if you mean an authority instead""") + + if canonical in ("myslice"): + parser.add_option("-p", "--password", dest='password', action='store', default=None, + help="specify mainfold password on the command line") + parser.add_option("-s", "--slice", dest="delegate_slices", action='append', default=[], + metavar="slice_hrn", help="delegate cred. for slice HRN") + parser.add_option("-a", "--auths", dest='delegate_auths', action='append', default=[], + metavar='auth_hrn', help="delegate PI cred for auth HRN") + parser.add_option('-d', '--delegate', dest='delegate', + help="Override 'delegate' from the config file") + parser.add_option('-b', '--backend', dest='backend', + help="Override 'backend' from the config file") + + return parser + + # + # Main: parse arguments and dispatch to command + # + def dispatch(self, command, command_options, command_args): + (doc, args_string, example, canonical) = commands_dict[command] + method = getattr(self, canonical, None) + if not method: + print("sfi: unknown command {}".format(command)) + raise SystemExit("Unknown command {}".format(command)) + for arg in command_args: + if 'help' in arg or arg == '-h': + self.print_help() + sys.exit(1) + return method(command_options, command_args) + + def main(self): + init_logger('cli') + self.sfi_parser = self.create_parser_global() + (options, args) = self.sfi_parser.parse_args() + if options.help: + self.print_commands_help(options) + sys.exit(1) + self.options = options + + logger.setLevelFromOptVerbose(self.options.verbose) + + if len(args) <= 0: + logger.critical("No command given. Use -h for help.") + self.print_commands_help(options) + return -1 + + # complete / find unique match with command set + command_candidates = Candidates(commands_list) + input = args[0] + command = command_candidates.only_match(input) + if not command: + self.print_commands_help(options) + sys.exit(1) + # second pass options parsing + self.command = command + self.command_parser = self.create_parser_command(command) + (command_options, command_args) = self.command_parser.parse_args( + args[1:]) + if command_options.help: + self.print_help() + sys.exit(1) + self.command_options = command_options + + # allow incoming types on 2 characters only + if hasattr(command_options, 'type'): + command_options.type = normalize_type(command_options.type) + if not command_options.type: + sys.exit(1) + + self.read_config() + self.bootstrap() + logger.debug("Command={}".format(self.command)) + + try: + retcod = self.dispatch(command, command_options, command_args) + except SystemExit: + return 1 + except: + logger.log_exc("sfi command {} failed".format(command)) + return 1 + return retcod + + #################### def read_config(self): - config_file = os.path.join(self.options.sfi_dir,"sfi_config") - try: - config = Config (config_file) - except: - self.logger.critical("Failed to read configuration file %s"%config_file) - self.logger.info("Make sure to remove the export clauses and to add quotes") - if self.options.verbose==0: - self.logger.info("Re-run with -v for more details") - else: - self.logger.log_exc("Could not read config file %s"%config_file) - sys.exit(1) - - errors = 0 - # Set SliceMgr URL - if (self.options.sm is not None): - self.sm_url = self.options.sm - elif hasattr(config, "SFI_SM"): - self.sm_url = config.SFI_SM - else: - self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file) - errors += 1 - - # Set Registry URL - if (self.options.registry is not None): - self.reg_url = self.options.registry - elif hasattr(config, "SFI_REGISTRY"): - self.reg_url = config.SFI_REGISTRY - else: - self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file) - errors += 1 - - - # Set user HRN - if (self.options.user is not None): - self.user = self.options.user - elif hasattr(config, "SFI_USER"): - self.user = config.SFI_USER - else: - self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file) - errors += 1 - - # Set authority HRN - if (self.options.auth is not None): - self.authority = self.options.auth - elif hasattr(config, "SFI_AUTH"): - self.authority = config.SFI_AUTH - else: - self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file) - errors += 1 - - if errors: - sys.exit(1) + config_file = os.path.join(self.options.sfi_dir, "sfi_config") + shell_config_file = os.path.join(self.options.sfi_dir, "sfi_config.sh") + try: + if Config.is_ini(config_file): + config = Config(config_file) + else: + # try upgrading from shell config format + fp, fn = mkstemp(suffix='sfi_config', text=True) + config = Config(fn) + # we need to preload the sections we want parsed + # from the shell config + config.add_section('sfi') + # sface users should be able to use this same file to configure + # their stuff + config.add_section('sface') + # manifold users should be able to specify the details + # of their backend server here for 'sfi myslice' + config.add_section('myslice') + config.load(config_file) + # back up old config + shutil.move(config_file, shell_config_file) + # write new config + config.save(config_file) + except: + logger.critical( + "Failed to read configuration file {}".format(config_file)) + logger.info( + "Make sure to remove the export clauses and to add quotes") + if self.options.verbose == 0: + logger.info("Re-run with -v for more details") + else: + logger.log_exc( + "Could not read config file {}".format(config_file)) + sys.exit(1) + + self.config_instance = config + errors = 0 + # Set SliceMgr URL + if (self.options.sm is not None): + self.sm_url = self.options.sm + elif hasattr(config, "SFI_SM"): + self.sm_url = config.SFI_SM + else: + logger.error( + "You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in {}".format(config_file)) + errors += 1 + + # Set Registry URL + if (self.options.registry is not None): + self.reg_url = self.options.registry + elif hasattr(config, "SFI_REGISTRY"): + self.reg_url = config.SFI_REGISTRY + else: + logger.error( + "You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in {}".format(config_file)) + errors += 1 + + # Set user HRN + if (self.options.user is not None): + self.user = self.options.user + elif hasattr(config, "SFI_USER"): + self.user = config.SFI_USER + else: + logger.error( + "You need to set e.g. SFI_USER='plc.princeton.username' in {}".format(config_file)) + errors += 1 + + # Set authority HRN + if (self.options.auth is not None): + self.authority = self.options.auth + elif hasattr(config, "SFI_AUTH"): + self.authority = config.SFI_AUTH + else: + logger.error( + "You need to set e.g. SFI_AUTH='plc.princeton' in {}".format(config_file)) + errors += 1 + + self.config_file = config_file + if errors: + sys.exit(1) # - # Establish Connection to SliceMgr and Registry Servers + # Get various credential and spec files # - def set_servers(self): - - self.read_config() - # Get key and certificate - key_file = self.get_key_file() - cert_file = self.get_cert_file(key_file) - self.key_file = key_file - self.cert_file = cert_file - self.cert = GID(filename=cert_file) - self.logger.info("Contacting Registry at: %s"%self.reg_url) - self.registry = sfaprotocol.server_proxy(self.reg_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug) - self.logger.info("Contacting Slice Manager at: %s"%self.sm_url) - self.slicemgr = sfaprotocol.server_proxy(self.sm_url, key_file, cert_file, timeout=self.options.timeout, verbose=self.options.debug) - return + # Establishes limiting conventions + # - conflates MAs and SAs + # - assumes last token in slice name is unique + # + # Bootstraps credentials + # - bootstrap user credential from self-signed certificate + # - bootstrap authority credential from user credential + # - bootstrap slice credential from user credential + # + + # init self-signed cert, user credentials and gid + def bootstrap(self): + if self.options.verbose: + logger.info( + "Initializing SfaClientBootstrap with {}".format(self.reg_url)) + client_bootstrap = SfaClientBootstrap(self.user, self.reg_url, self.options.sfi_dir, + logger=logger) + # if -k is provided, use this to initialize private key + if self.options.user_private_key: + client_bootstrap.init_private_key_if_missing( + self.options.user_private_key) + else: + # trigger legacy compat code if needed + # the name has changed from just .pkey to .pkey + if not os.path.isfile(client_bootstrap.private_key_filename()): + logger.info("private key not found, trying legacy name") + try: + legacy_private_key = os.path.join(self.options.sfi_dir, "{}.pkey" + .format(Xrn.unescape(get_leaf(self.user)))) + logger.debug("legacy_private_key={}" + .format(legacy_private_key)) + client_bootstrap.init_private_key_if_missing( + legacy_private_key) + logger.info("Copied private key from legacy location {}" + .format(legacy_private_key)) + except: + logger.log_exc("Can't find private key ") + sys.exit(1) + + # make it bootstrap + client_bootstrap.bootstrap_my_gid() + # extract what's needed + self.private_key = client_bootstrap.private_key() + self.my_credential_string = client_bootstrap.my_credential_string() + self.my_credential = {'geni_type': 'geni_sfa', + 'geni_version': '3', + 'geni_value': self.my_credential_string} + self.my_gid = client_bootstrap.my_gid() + self.client_bootstrap = client_bootstrap + + def my_authority_credential_string(self): + if not self.authority: + logger.critical( + "no authority specified. Use -a or set SF_AUTH") + sys.exit(-1) + return self.client_bootstrap.authority_credential_string(self.authority) + + def authority_credential_string(self, auth_hrn): + return self.client_bootstrap.authority_credential_string(auth_hrn) + + def slice_credential_string(self, name): + return self.client_bootstrap.slice_credential_string(name) + + def slice_credential(self, name): + return {'geni_type': 'geni_sfa', + 'geni_version': '3', + 'geni_value': self.slice_credential_string(name)} + + # xxx should be supported by sfaclientbootstrap as well + def delegate_cred(self, object_cred, hrn, type='authority'): + # the gid and hrn of the object we are delegating + if isinstance(object_cred, str): + object_cred = Credential(string=object_cred) + object_gid = object_cred.get_gid_object() + object_hrn = object_gid.get_hrn() + + if not object_cred.get_privileges().get_all_delegate(): + logger.error("Object credential {} does not have delegate bit set" + .format(object_hrn)) + return + + # the delegating user's gid + caller_gidfile = self.my_gid() + + # the gid of the user who will be delegated to + delegee_gid = self.client_bootstrap.gid(hrn, type) + delegee_hrn = delegee_gid.get_hrn() + dcred = object_cred.delegate( + delegee_gid, self.private_key, caller_gidfile) + return dcred.save_to_string(save_parents=True) + + # + # Management of the servers + # + + def registry(self): + # cache the result + if not hasattr(self, 'registry_proxy'): + logger.info("Contacting Registry at: {}".format(self.reg_url)) + self.registry_proxy \ + = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, + timeout=self.options.timeout, verbose=self.options.debug) + return self.registry_proxy + + def sliceapi(self): + # cache the result + if not hasattr(self, 'sliceapi_proxy'): + # if the command exposes the --component option, figure it's + # hostname and connect at CM_PORT + if hasattr(self.command_options, 'component') and self.command_options.component: + # resolve the hrn at the registry + node_hrn = self.command_options.component + records = self.registry().Resolve(node_hrn, self.my_credential_string) + records = filter_records('node', records) + if not records: + logger.warning( + "No such component:{}".format(opts.component)) + record = records[0] + cm_url = "http://{}:{}/".format(record['hostname'], CM_PORT) + self.sliceapi_proxy = SfaServerProxy( + cm_url, self.private_key, self.my_gid) + else: + # otherwise use what was provided as --sliceapi, or SFI_SM in + # the config + if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'): + self.sm_url = 'http://' + self.sm_url + logger.info( + "Contacting Slice Manager at: {}".format(self.sm_url)) + self.sliceapi_proxy \ + = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, + timeout=self.options.timeout, verbose=self.options.debug) + return self.sliceapi_proxy def get_cached_server_version(self, server): # check local cache first cache = None - version = None - cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat') + version = None + cache_file = os.path.join(self.options.sfi_dir, 'sfi_cache.dat') cache_key = server.url + "-version" try: cache = Cache(cache_file) except IOError: cache = Cache() - self.logger.info("Local cache not found at: %s" % cache_file) + logger.info("Local cache not found at: {}".format(cache_file)) if cache: version = cache.get(cache_key) - if not version: + if not version: result = server.GetVersion() - version= ReturnValue.get_value(result) - # cache version for 24 hours - cache.add(cache_key, version, ttl= 60*60*24) - self.logger.info("Updating cache file %s" % cache_file) + version = ReturnValue.get_value(result) + # cache version for 20 minutes + cache.add(cache_key, version, ttl=60 * 20) + logger.info("Updating cache file {}".format(cache_file)) cache.save_to_file(cache_file) - return version - + return version + # resurrect this temporarily so we can support V1 aggregates for a while def server_supports_options_arg(self, server): """ - Returns true if server support the optional call_id arg, false otherwise. + Returns true if server support the optional call_id arg, false otherwise. """ server_version = self.get_cached_server_version(server) + result = False + # xxx need to rewrite this + if int(server_version.get('geni_api')) >= 2: + result = True + return result + + def server_supports_call_id_arg(self, server): + server_version = self.get_cached_server_version(server) + result = False if 'sfa' in server_version and 'code_tag' in server_version: code_tag = server_version['code_tag'] code_tag_parts = code_tag.split("-") - version_parts = code_tag_parts[0].split(".") major, minor = version_parts[0], version_parts[1] rev = code_tag_parts[1] - if int(major) >= 1: - if int(minor) >= 2: - return True - return False - - # - # Get various credential and spec files - # - # Establishes limiting conventions - # - conflates MAs and SAs - # - assumes last token in slice name is unique - # - # Bootstraps credentials - # - bootstrap user credential from self-signed certificate - # - bootstrap authority credential from user credential - # - bootstrap slice credential from user credential - # - - - def get_key_file(self): - file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".pkey") - if (os.path.isfile(file)): - return file - else: - self.logger.error("Key file %s does not exist"%file) - sys.exit(-1) - return - - def get_cert_file(self, key_file): - - cert_file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cert") - if (os.path.isfile(cert_file)): - # we'd perfer to use Registry issued certs instead of self signed certs. - # if this is a Registry cert (GID) then we are done - gid = GID(filename=cert_file) - if gid.get_urn(): - return cert_file - - # generate self signed certificate - k = Keypair(filename=key_file) - cert = Certificate(subject=self.user) - cert.set_pubkey(k) - cert.set_issuer(k, self.user) - cert.sign() - self.logger.info("Writing self-signed certificate to %s"%cert_file) - cert.save_to_file(cert_file) - self.cert = cert - # try to get registry issued cert - try: - self.logger.info("Getting Registry issued cert") - self.read_config() - # *hack. need to set registry before _get_gid() is called - self.registry = sfaprotocol.server_proxy(self.reg_url, key_file, cert_file, - timeout=self.options.timeout, verbose=self.options.debug) - gid = self._get_gid(type='user') - self.registry = None - self.logger.info("Writing certificate to %s"%cert_file) - gid.save_to_file(cert_file) - except: - self.logger.info("Failed to download Registry issued cert") + if int(major) == 1 and minor == 0 and build >= 22: + result = True + return result + + # ois = options if supported + # to be used in something like serverproxy.Method(arg1, arg2, + # *self.ois(api_options)) + def ois(self, server, option_dict): + if self.server_supports_options_arg(server): + return [option_dict] + elif self.server_supports_call_id_arg(server): + return [unique_call_id()] + else: + return [] - return cert_file + # cis = call_id if supported - like ois + def cis(self, server): + if self.server_supports_call_id_arg(server): + return [unique_call_id] + else: + return [] - def get_cached_gid(self, file): - """ - Return a cached gid - """ - gid = None + # miscell utilities + def get_rspec_file(self, rspec): + if (os.path.isabs(rspec)): + file = rspec + else: + file = os.path.join(self.options.sfi_dir, rspec) if (os.path.isfile(file)): - gid = GID(filename=file) - return gid + return file + else: + logger.critical("No such rspec file {}".format(rspec)) + sys.exit(1) - # xxx opts unused - def get_gid(self, opts, args): - """ - Get the specify gid and save it to file - """ - hrn = None - if args: - hrn = args[0] - gid = self._get_gid(hrn) - self.logger.debug("Sfi.get_gid-> %s" % gid.save_to_string(save_parents=True)) - return gid - - def _get_gid(self, hrn=None, type=None): - """ - git_gid helper. Retrive the gid from the registry and save it to file. - """ - - if not hrn: - hrn = self.user - - gidfile = os.path.join(self.options.sfi_dir, hrn + ".gid") - gid = self.get_cached_gid(gidfile) - if not gid: - user_cred = self.get_user_cred() - records = self.registry.Resolve(hrn, user_cred.save_to_string(save_parents=True)) - if not records: - raise RecordNotFound(args[0]) - record = records[0] - if type: - record=None - for rec in records: - if type == rec['type']: - record = rec - if not record: - raise RecordNotFound(args[0]) - - gid = GID(string=record['gid']) - self.logger.info("Writing gid to %s"%gidfile) - gid.save_to_file(filename=gidfile) - return gid - - - def get_cached_credential(self, file): - """ - Return a cached credential only if it hasn't expired. - """ - if (os.path.isfile(file)): - credential = Credential(filename=file) - # make sure it isnt expired - if not credential.get_expiration or \ - datetime.datetime.today() < credential.get_expiration(): - return credential - return None - - def get_user_cred(self): - file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cred") - return self.get_cred(file, 'user', self.user) - - def get_auth_cred(self): - if not self.authority: - self.logger.critical("no authority specified. Use -a or set SF_AUTH") - sys.exit(-1) - file = os.path.join(self.options.sfi_dir, self.authority + ".cred") - return self.get_cred(file, 'authority', self.authority) - - def get_slice_cred(self, name): - file = os.path.join(self.options.sfi_dir, "slice_" + get_leaf(name) + ".cred") - return self.get_cred(file, 'slice', name) - - def get_cred(self, file, type, hrn): - # attempt to load a cached credential - cred = self.get_cached_credential(file) - if not cred: - if type in ['user']: - cert_string = self.cert.save_to_string(save_parents=True) - user_name = self.user.replace(self.authority + ".", '') - if user_name.count(".") > 0: - user_name = user_name.replace(".", '_') - self.user = self.authority + "." + user_name - cred_str = self.registry.GetSelfCredential(cert_string, hrn, "user") - else: - # bootstrap slice credential from user credential - user_cred = self.get_user_cred().save_to_string(save_parents=True) - cred_str = self.registry.GetCredential(user_cred, hrn, type) - - if not cred_str: - self.logger.critical("Failed to get %s credential" % type) - sys.exit(-1) - - cred = Credential(string=cred_str) - cred.save_to_file(file, save_parents=True) - self.logger.info("Writing %s credential to %s" %(type, file)) - - return cred - - - def get_rspec_file(self, rspec): - if (os.path.isabs(rspec)): - file = rspec - else: - file = os.path.join(self.options.sfi_dir, rspec) - if (os.path.isfile(file)): - return file - else: - self.logger.critical("No such rspec file %s"%rspec) - sys.exit(1) - def get_record_file(self, record): - if (os.path.isabs(record)): - file = record - else: - file = os.path.join(self.options.sfi_dir, record) - if (os.path.isfile(file)): - return file - else: - self.logger.critical("No such registry record file %s"%record) - sys.exit(1) - - # xxx opts undefined - def get_component_proxy_from_hrn(self, hrn): - # direct connection to the nodes component manager interface - user_cred = self.get_user_cred().save_to_string(save_parents=True) - records = self.registry.Resolve(hrn, user_cred) - records = filter_records('node', records) - if not records: - self.logger.warning("No such component:%r"% opts.component) - record = records[0] - - return self.server_proxy(record['hostname'], CM_PORT, self.key_file, self.cert_file) - - def server_proxy(self, host, port, keyfile, certfile): - """ - Return an instance of an xmlrpc server connection - """ - # port is appended onto the domain, before the path. Should look like: - # http://domain:port/path - host_parts = host.split('/') - host_parts[0] = host_parts[0] + ":" + str(port) - url = "http://%s" % "/".join(host_parts) - return sfaprotocol.server_proxy(url, keyfile, certfile, timeout=self.options.timeout, - verbose=self.options.debug) - - # xxx opts could be retrieved in self.options - def server_proxy_from_opts(self, opts): - """ - Return instance of an xmlrpc connection to a slice manager, aggregate - or component server depending on the specified opts - """ - server = self.slicemgr - # direct connection to an aggregate - if hasattr(opts, 'aggregate') and opts.aggregate: - server = self.server_proxy(opts.aggregate, opts.port, self.key_file, self.cert_file) - # direct connection to the nodes component manager interface - if hasattr(opts, 'component') and opts.component: - server = self.get_component_proxy_from_hrn(opts.component) - - return server + if (os.path.isabs(record)): + file = record + else: + file = os.path.join(self.options.sfi_dir, record) + if (os.path.isfile(file)): + return file + else: + logger.critical( + "No such registry record file {}".format(record)) + sys.exit(1) + + # helper function to analyze raw output + # for main : return 0 if everything is fine, something else otherwise + # (mostly 1 for now) + def success(self, raw): + return_value = ReturnValue(raw) + output = ReturnValue.get_output(return_value) + # means everything is fine + if not output: + return 0 + # something went wrong + print('ERROR:', output) + return 1 + #========================================================================== # Following functions implement the commands # # Registry-related commands #========================================================================== - - def create_gid(self, opts, args): - if len(args) < 1: + + @declare_command("", "") + def config(self, options, args): + """ + Display contents of current config + """ + if len(args) != 0: self.print_help() sys.exit(1) - target_hrn = args[0] - user_cred = self.get_user_cred().save_to_string(save_parents=True) - gid = self.registry.CreateGid(user_cred, target_hrn, self.cert.save_to_string()) - if opts.file: - filename = opts.file + + print("# From configuration file {}".format(self.config_file)) + flags = [('sfi', [('registry', 'reg_url'), + ('auth', 'authority'), + ('user', 'user'), + ('sm', 'sm_url'), + ]), + ] + if options.myslice: + flags.append( + ('myslice', ['backend', 'delegate', 'platform', 'username'])) + + for (section, tuples) in flags: + print("[{}]".format(section)) + try: + for external_name, internal_name in tuples: + print("{:<20} = {}".format( + external_name, getattr(self, internal_name))) + except: + for external_name, internal_name in tuples: + varname = "{}_{}".format( + section.upper(), external_name.upper()) + value = getattr(self.config_instance, varname) + print("{:<20} = {}".format(external_name, value)) + # xxx should analyze result + return 0 + + @declare_command("", "") + def version(self, options, args): + """ + display an SFA server version (GetVersion) + or version information about sfi itself + """ + if len(args) != 0: + self.print_help() + sys.exit(1) + + if options.version_local: + version = version_core() else: - filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn]) - self.logger.info("writing %s gid to %s" % (target_hrn, filename)) - GID(string=gid).save_to_file(filename) - - - # list entires in named authority registry - def list(self, opts, args): - if len(args)!= 1: + if options.registry_interface: + server = self.registry() + else: + server = self.sliceapi() + result = server.GetVersion() + version = ReturnValue.get_value(result) + if self.options.raw: + save_raw_to_file(result, self.options.raw, + self.options.rawformat, self.options.rawbanner) + else: + pprinter = PrettyPrinter(indent=4) + pprinter.pprint(version) + # xxx should analyze result + return 0 + + @declare_command("authority", "") + def list(self, options, args): + """ + list entries in named authority registry (List) + """ + if len(args) != 1: self.print_help() sys.exit(1) + hrn = args[0] - user_cred = self.get_user_cred().save_to_string(save_parents=True) + opts = {} + if options.recursive: + opts['recursive'] = options.recursive + + if options.show_credential: + show_credentials(self.my_credential_string) try: - list = self.registry.List(hrn, user_cred) + list = self.registry().List(hrn, self.my_credential_string, options) except IndexError: - raise Exception, "Not enough parameters for the 'list' command" + raise Exception("Not enough parameters for the 'list' command") # filter on person, slice, site, node, etc. - # THis really should be in the self.filter_records funct def comment... - list = filter_records(opts.type, list) - for record in list: - print "%s (%s)" % (record['hrn'], record['type']) - if opts.file: - save_records_to_file(opts.file, list, opts.fileformat) - return - - # show named registry record - def show(self, opts, args): - if len(args)!= 1: + # This really should be in the self.filter_records funct def comment... + list = filter_records(options.type, list) + terminal_render(list, options) + if options.file: + save_records_to_file(options.file, list, options.fileformat) + # xxx should analyze result + return 0 + + @declare_command("name", "") + def show(self, options, args): + """ + show details about named registry record (Resolve) + """ + if len(args) != 1: self.print_help() sys.exit(1) + hrn = args[0] - user_cred = self.get_user_cred().save_to_string(save_parents=True) - records = self.registry.Resolve(hrn, user_cred) - records = filter_records(opts.type, records) - if not records: - self.logger.error("No record of type %s"% opts.type) + # explicitly require Resolve to run in details mode + resolve_options = {} + if not options.no_details: + resolve_options['details'] = True + record_dicts = self.registry().Resolve( + hrn, self.my_credential_string, resolve_options) + record_dicts = filter_records(options.type, record_dicts) + if not record_dicts: + logger.error("No record of type {}".format(options.type)) + return + # user has required to focus on some keys + if options.keys: + def project(record): + projected = {} + for key in options.keys: + try: + projected[key] = record[key] + except: + pass + return projected + record_dicts = [project(record) for record in record_dicts] + records = [Record(dict=record_dict) for record_dict in record_dicts] for record in records: - if record['type'] in ['user']: - record = UserRecord(dict=record) - elif record['type'] in ['slice']: - record = SliceRecord(dict=record) - elif record['type'] in ['node']: - record = NodeRecord(dict=record) - elif record['type'].startswith('authority'): - record = AuthorityRecord(dict=record) + if (options.format == "text"): + record.dump(sort=True) else: - record = SfaRecord(dict=record) - if (opts.format == "text"): - record.dump() - else: - print record.save_to_string() - if opts.file: - save_records_to_file(opts.file, records, opts.fileformat) - return - - def delegate(self, opts, args): - - delegee_hrn = args[0] - if opts.delegate_user: - user_cred = self.get_user_cred() - cred = self.delegate_cred(user_cred, delegee_hrn) - elif opts.delegate_slice: - slice_cred = self.get_slice_cred(opts.delegate_slice) - cred = self.delegate_cred(slice_cred, delegee_hrn) - else: - self.logger.warning("Must specify either --user or --slice ") - return - delegated_cred = Credential(string=cred) - object_hrn = delegated_cred.get_gid_object().get_hrn() - if opts.delegate_user: - dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_" - + get_leaf(object_hrn) + ".cred") - elif opts.delegate_slice: - dest_fn = os.path.join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_" - + get_leaf(object_hrn) + ".cred") - - delegated_cred.save_to_file(dest_fn, save_parents=True) - - self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn)) - - def delegate_cred(self, object_cred, hrn): - # the gid and hrn of the object we are delegating - if isinstance(object_cred, str): - object_cred = Credential(string=object_cred) - object_gid = object_cred.get_gid_object() - object_hrn = object_gid.get_hrn() - - if not object_cred.get_privileges().get_all_delegate(): - self.logger.error("Object credential %s does not have delegate bit set"%object_hrn) - return + print(record.save_as_xml()) + if options.file: + save_records_to_file( + options.file, record_dicts, options.fileformat) + # xxx should analyze result + return 0 + + # this historically was named 'add', it is now 'register' with an alias + # for legacy + @declare_command("[xml-filename]", "", ['add']) + def register(self, options, args): + """ + create new record in registry (Register) + from command line options (recommended) + old-school method involving an xml file still supported + """ + if len(args) > 1: + self.print_help() + sys.exit(1) - # the delegating user's gid - caller_gid = self._get_gid(self.user) - caller_gidfile = os.path.join(self.options.sfi_dir, self.user + ".gid") - - # the gid of the user who will be delegated to - delegee_gid = self._get_gid(hrn) - delegee_hrn = delegee_gid.get_hrn() - delegee_gidfile = os.path.join(self.options.sfi_dir, delegee_hrn + ".gid") - delegee_gid.save_to_file(filename=delegee_gidfile) - dcred = object_cred.delegate(delegee_gidfile, self.get_key_file(), caller_gidfile) - return dcred.save_to_string(save_parents=True) - - # removed named registry record - # - have to first retrieve the record to be removed - def remove(self, opts, args): - auth_cred = self.get_auth_cred().save_to_string(save_parents=True) - if len(args)!=1: + auth_cred = self.my_authority_credential_string() + if options.show_credential: + show_credentials(auth_cred) + record_dict = {} + if len(args) == 1: + try: + record_filepath = args[0] + rec_file = self.get_record_file(record_filepath) + record_dict.update(load_record_from_file( + rec_file).record_to_dict()) + except: + print("Cannot load record file {}".format(record_filepath)) + sys.exit(1) + if options: + record_dict.update(load_record_from_opts(options).record_to_dict()) + # we should have a type by now + if 'type' not in record_dict: self.print_help() sys.exit(1) - hrn = args[0] - type = opts.type - if type in ['all']: - type = '*' - return self.registry.Remove(hrn, auth_cred, type) - - # add named registry record - def add(self, opts, args): - auth_cred = self.get_auth_cred().save_to_string(save_parents=True) - if len(args)!=1: + # this is still planetlab dependent.. as plc will whine without that + # also, it's only for adding + if record_dict['type'] == 'user': + if not 'first_name' in record_dict: + record_dict['first_name'] = record_dict['hrn'] + if 'last_name' not in record_dict: + record_dict['last_name'] = record_dict['hrn'] + register = self.registry().Register(record_dict, auth_cred) + # xxx looks like the result here is not ReturnValue-compatible + # return self.success (register) + # xxx should analyze result + return 0 + + @declare_command("[xml-filename]", "") + def update(self, options, args): + """ + update record into registry (Update) + from command line options (recommended) + old-school method involving an xml file still supported + """ + if len(args) > 1: self.print_help() sys.exit(1) - record_filepath = args[0] - rec_file = self.get_record_file(record_filepath) - record = load_record_from_file(rec_file).as_dict() - return self.registry.Register(record, auth_cred) - - # update named registry entry - def update(self, opts, args): - user_cred = self.get_user_cred() - if len(args)!=1: + + record_dict = {} + if len(args) == 1: + record_filepath = args[0] + rec_file = self.get_record_file(record_filepath) + record_dict.update(load_record_from_file( + rec_file).record_to_dict()) + if options: + record_dict.update(load_record_from_opts(options).record_to_dict()) + # at the very least we need 'type' here + if 'type' not in record_dict or record_dict['type'] is None: self.print_help() sys.exit(1) - rec_file = self.get_record_file(args[0]) - record = load_record_from_file(rec_file) - if record['type'] == "user": - if record.get_name() == user_cred.get_gid_object().get_hrn(): - cred = user_cred.save_to_string(save_parents=True) + + # don't translate into an object, as this would possibly distort + # user-provided data; e.g. add an 'email' field to Users + if record_dict['type'] in ['user']: + if record_dict['hrn'] == self.user: + cred = self.my_credential_string else: - cred = self.get_auth_cred().save_to_string(save_parents=True) - elif record['type'] in ["slice"]: + cred = self.my_authority_credential_string() + elif record_dict['type'] in ['slice']: try: - cred = self.get_slice_cred(record.get_name()).save_to_string(save_parents=True) - except sfaprotocol.ServerException, e: - # XXX smbaker -- once we have better error return codes, update this - # to do something better than a string compare - if "Permission error" in e.args[0]: - cred = self.get_auth_cred().save_to_string(save_parents=True) - else: - raise - elif record.get_type() in ["authority"]: - cred = self.get_auth_cred().save_to_string(save_parents=True) - elif record.get_type() == 'node': - cred = self.get_auth_cred().save_to_string(save_parents=True) + cred = self.slice_credential_string(record_dict['hrn']) + except ServerException as e: + # XXX smbaker -- once we have better error return codes, update this + # to do something better than a string compare + if "Permission error" in e.args[0]: + cred = self.my_authority_credential_string() + else: + raise + elif record_dict['type'] in ['authority']: + cred = self.my_authority_credential_string() + elif record_dict['type'] in ['node']: + cred = self.my_authority_credential_string() else: - raise "unknown record type" + record.get_type() - record = record.as_dict() - return self.registry.Update(record, cred) - - def get_trusted_certs(self, opts, args): + raise Exception( + "unknown record type {}".format(record_dict['type'])) + if options.show_credential: + show_credentials(cred) + update = self.registry().Update(record_dict, cred) + # xxx looks like the result here is not ReturnValue-compatible + # return self.success(update) + # xxx should analyze result + return 0 + + @declare_command("hrn", "") + def remove(self, options, args): """ - return uhe trusted certs at this interface - """ - trusted_certs = self.registry.get_trusted_certs() - for trusted_cert in trusted_certs: - gid = GID(string=trusted_cert) - gid.dump() - cert = Certificate(string=trusted_cert) - self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject()) - return + remove registry record by name (Remove) + """ + auth_cred = self.my_authority_credential_string() + if len(args) != 1: + self.print_help() + sys.exit(1) + + hrn = args[0] + type = options.type + if type in ['all']: + type = '*' + if options.show_credential: + show_credentials(auth_cred) + remove = self.registry().Remove(hrn, auth_cred, type) + # xxx looks like the result here is not ReturnValue-compatible + # return self.success (remove) + # xxx should analyze result + return 0 # ================================================================== # Slice-related commands # ================================================================== - def version(self, opts, args): - if opts.version_local: - version=version_core() - else: - if opts.version_registry: - server=self.registry - else: - server = self.server_proxy_from_opts(opts) - result = server.GetVersion() - version = ReturnValue.get_value(result) - for (k,v) in version.iteritems(): - print "%-20s: %s"%(k,v) - if opts.file: - save_variable_to_file(version, opts.file, opts.fileformat) + # show rspec for named slice + @declare_command("", "", ['discover']) + def resources(self, options, args): + """ + discover available resources (ListResources) + """ + if len(args) != 0: + self.print_help() + sys.exit(1) - # list instantiated slices - def slices(self, opts, args): + server = self.sliceapi() + # set creds + creds = [self.my_credential_string] + if options.delegate: + creds.append(self.delegate_cred( + cred, get_authority(self.authority))) + if options.show_credential: + show_credentials(creds) + + # no need to check if server accepts the options argument since the options has + # been a required argument since v1 API + api_options = {} + # always send call_id to v2 servers + api_options['call_id'] = unique_call_id() + # ask for cached value if available + api_options['cached'] = True + if options.info: + api_options['info'] = options.info + if options.list_leases: + api_options['list_leases'] = options.list_leases + if options.current: + if options.current == True: + api_options['cached'] = False + else: + api_options['cached'] = True + version_manager = VersionManager() + api_options['geni_rspec_version'] = version_manager.get_version( + options.rspec_version).to_dict() + + list_resources = server.ListResources(creds, api_options) + value = ReturnValue.get_value(list_resources) + if self.options.raw: + save_raw_to_file(list_resources, self.options.raw, + self.options.rawformat, self.options.rawbanner) + if options.file is not None: + save_rspec_to_file(value, options.file) + if (self.options.raw is None) and (options.file is None): + display_rspec(value, options.format) + return self.success(list_resources) + + @declare_command("slice_hrn", "") + def describe(self, options, args): """ - list instantiated slices + shows currently allocated/provisioned resources + of the named slice or set of slivers (Describe) """ - user_cred = self.get_user_cred().save_to_string(save_parents=True) - creds = [user_cred] - if opts.delegate: - delegated_cred = self.delegate_cred(user_cred, get_authority(self.authority)) - creds.append(delegated_cred) - server = self.server_proxy_from_opts(opts) - call_args = [creds] - if self.server_supports_options_arg(server): - options = {'call_id': unique_call_id()} - call_args.append(options) - result = server.ListSlices(*call_args) - value = ReturnValue.get_value(result) - display_list(value) - return - - # show rspec for named slice - def resources(self, opts, args): - user_cred = self.get_user_cred().save_to_string(save_parents=True) - server = self.server_proxy_from_opts(opts) - - options = {'call_id': unique_call_id()} - #panos add info options - if opts.info: - options['info'] = opts.info - - if args: - cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True) - hrn = args[0] - options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice') - else: - cred = user_cred - - creds = [cred] - if opts.delegate: - delegated_cred = self.delegate_cred(cred, get_authority(self.authority)) - creds.append(delegated_cred) - if opts.rspec_version: + if len(args) != 1: + self.print_help() + sys.exit(1) + + server = self.sliceapi() + # set creds + creds = [self.slice_credential(args[0])] + if options.delegate: + creds.append(self.delegate_cred( + cred, get_authority(self.authority))) + if options.show_credential: + show_credentials(creds) + + api_options = {'call_id': unique_call_id(), + 'cached': True, + 'info': options.info, + 'list_leases': options.list_leases, + 'geni_rspec_version': {'type': 'geni', 'version': '3'}, + } + if options.info: + api_options['info'] = options.info + + if options.rspec_version: version_manager = VersionManager() server_version = self.get_cached_server_version(server) if 'sfa' in server_version: - # just request the version the client wants - options['geni_rspec_version'] = version_manager.get_version(opts.rspec_version).to_dict() + # just request the version the client wants + api_options['geni_rspec_version'] = version_manager.get_version( + options.rspec_version).to_dict() else: - # this must be a protogeni aggregate. We should request a v2 ad rspec - # regardless of what the client user requested - options['geni_rspec_version'] = version_manager.get_version('ProtoGENI 2').to_dict() + api_options['geni_rspec_version'] = { + 'type': 'geni', 'version': '3'} + urn = Xrn(args[0], type='slice').get_urn() + remove_none_fields(api_options) + describe = server.Describe([urn], creds, api_options) + value = ReturnValue.get_value(describe) + if self.options.raw: + save_raw_to_file(describe, self.options.raw, + self.options.rawformat, self.options.rawbanner) + if options.file is not None: + save_rspec_to_file(value['geni_rspec'], options.file) + if (self.options.raw is None) and (options.file is None): + display_rspec(value['geni_rspec'], options.format) + return self.success(describe) + + @declare_command("slice_hrn [...]", "") + def delete(self, options, args): + """ + de-allocate and de-provision all or named slivers of the named slice (Delete) + """ + if len(args) == 0: + self.print_help() + sys.exit(1) + + server = self.sliceapi() + # slice urn + slice_hrn = args[0] + slice_urn = hrn_to_urn(slice_hrn, 'slice') + + if len(args) > 1: + # we have sliver urns + sliver_urns = args[1:] else: - options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'} - - call_args = [creds, options] - result = server.ListResources(*call_args) - value = ReturnValue.get_value(result) - if opts.file is None: - display_rspec(value, opts.format) + # we provision all the slivers of the slice + sliver_urns = [slice_urn] + + # creds + slice_cred = self.slice_credential(slice_hrn) + creds = [slice_cred] + + # options and call_id when supported + api_options = {} + api_options['call_id'] = unique_call_id() + if options.show_credential: + show_credentials(creds) + delete = server.Delete(sliver_urns, creds, * + self.ois(server, api_options)) + value = ReturnValue.get_value(delete) + if self.options.raw: + save_raw_to_file(delete, self.options.raw, + self.options.rawformat, self.options.rawbanner) else: - save_rspec_to_file(value, opts.file) - return + print(value) + return self.success(delete) + + @declare_command("slice_hrn rspec", "") + def allocate(self, options, args): + """ + allocate resources to the named slice (Allocate) + """ + if len(args) != 2: + self.print_help() + sys.exit(1) - # created named slice with given rspec - def create(self, opts, args): - server = self.server_proxy_from_opts(opts) + server = self.sliceapi() server_version = self.get_cached_server_version(server) slice_hrn = args[0] - slice_urn = hrn_to_urn(slice_hrn, 'slice') - user_cred = self.get_user_cred() - slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True) + rspec_file = self.get_rspec_file(args[1]) + + slice_urn = Xrn(slice_hrn, type='slice').get_urn() + + # credentials + creds = [self.slice_credential(slice_hrn)] + delegated_cred = None if server_version.get('interface') == 'slicemgr': # delegate our cred to the slice manager # do not delegate cred to slicemgr...not working at the moment pass - #if server_version.get('hrn'): + # if server_version.get('hrn'): # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn']) - #elif server_version.get('urn'): + # elif server_version.get('urn'): # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn'])) - - rspec_file = self.get_rspec_file(args[1]) - rspec = open(rspec_file).read() + if options.show_credential: + show_credentials(creds) + + # rspec + api_options = {} + api_options['call_id'] = unique_call_id() + # users + sfa_users = [] + geni_users = [] + slice_records = self.registry().Resolve( + slice_urn, [self.my_credential_string]) + remove_none_fields(slice_records[0]) + if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers'] != []: + slice_record = slice_records[0] + user_hrns = slice_record['reg-researchers'] + user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns] + user_records = self.registry().Resolve( + user_urns, [self.my_credential_string]) + sfa_users = sfa_users_arg(user_records, slice_record) + geni_users = pg_users_arg(user_records) + + api_options['sfa_users'] = sfa_users + api_options['geni_users'] = geni_users + + with open(rspec_file) as rspec: + rspec_xml = rspec.read() + allocate = server.Allocate( + slice_urn, creds, rspec_xml, api_options) + value = ReturnValue.get_value(allocate) + if self.options.raw: + save_raw_to_file(allocate, self.options.raw, + self.options.rawformat, self.options.rawbanner) + if options.file is not None: + save_rspec_to_file(value['geni_rspec'], options.file) + if (self.options.raw is None) and (options.file is None): + print(value) + return self.success(allocate) + + @declare_command("slice_hrn [...]", "") + def provision(self, options, args): + """ + provision all or named already allocated slivers of the named slice (Provision) + """ + if len(args) == 0: + self.print_help() + sys.exit(1) + + server = self.sliceapi() + server_version = self.get_cached_server_version(server) + slice_hrn = args[0] + slice_urn = Xrn(slice_hrn, type='slice').get_urn() + if len(args) > 1: + # we have sliver urns + sliver_urns = args[1:] + else: + # we provision all the slivers of the slice + sliver_urns = [slice_urn] + + # credentials + creds = [self.slice_credential(slice_hrn)] + delegated_cred = None + if server_version.get('interface') == 'slicemgr': + # delegate our cred to the slice manager + # do not delegate cred to slicemgr...not working at the moment + pass + # if server_version.get('hrn'): + # delegated_cred = self.delegate_cred(slice_cred, server_version['hrn']) + # elif server_version.get('urn'): + # delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn'])) + + if options.show_credential: + show_credentials(creds) + + api_options = {} + api_options['call_id'] = unique_call_id() + + # set the requtested rspec version + version_manager = VersionManager() + rspec_version = version_manager._get_version('geni', '3').to_dict() + api_options['geni_rspec_version'] = rspec_version + + # users # need to pass along user keys to the aggregate. # users = [ # { urn: urn:publicid:IDN+emulab.net+user+alice # keys: [, ] # }] users = [] - slice_records = self.registry.Resolve(slice_urn, [user_cred.save_to_string(save_parents=True)]) - if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]: + slice_records = self.registry().Resolve( + slice_urn, [self.my_credential_string]) + if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers'] != []: slice_record = slice_records[0] - user_hrns = slice_record['researcher'] + user_hrns = slice_record['reg-researchers'] user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns] - user_records = self.registry.Resolve(user_urns, [user_cred.save_to_string(save_parents=True)]) - - if 'sfa' not in server_version: - users = pg_users_arg(user_records) - rspec = RSpec(rspec) - rspec.filter({'component_manager_id': server_version['urn']}) - rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request') - creds = [slice_cred] - else: - users = sfa_users_arg(user_records, slice_record) - creds = [slice_cred] - if delegated_cred: - creds.append(delegated_cred) - call_args = [slice_urn, creds, rspec, users] - if self.server_supports_options_arg(server): - options = {'call_id': unique_call_id()} - call_args.append(options) - result = server.CreateSliver(*call_args) - value = ReturnValue.get_value(result) - if opts.file is None: - print value - else: - save_rspec_to_file (value, opts.file) - return value + user_records = self.registry().Resolve( + user_urns, [self.my_credential_string]) + users = pg_users_arg(user_records) + + api_options['geni_users'] = users + provision = server.Provision(sliver_urns, creds, api_options) + value = ReturnValue.get_value(provision) + if self.options.raw: + save_raw_to_file(provision, self.options.raw, + self.options.rawformat, self.options.rawbanner) + if options.file is not None: + save_rspec_to_file(value['geni_rspec'], options.file) + if (self.options.raw is None) and (options.file is None): + print(value) + return self.success(provision) + + @declare_command("slice_hrn", "") + def status(self, options, args): + """ + retrieve the status of the slivers belonging to the named slice (Status) + """ + if len(args) != 1: + self.print_help() + sys.exit(1) - # get a ticket for the specified slice - def get_ticket(self, opts, args): - slice_hrn, rspec_path = args[0], args[1] - slice_urn = hrn_to_urn(slice_hrn, 'slice') - user_cred = self.get_user_cred() - slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True) - creds = [slice_cred] - if opts.delegate: - delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority)) - creds.append(delegated_cred) - rspec_file = self.get_rspec_file(rspec_path) - rspec = open(rspec_file).read() - server = self.server_proxy_from_opts(opts) - ticket_string = server.GetTicket(slice_urn, creds, rspec, []) - file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket") - self.logger.info("writing ticket to %s"%file) - ticket = SfaTicket(string=ticket_string) - ticket.save_to_file(filename=file, save_parents=True) - - def redeem_ticket(self, opts, args): - ticket_file = args[0] - - # get slice hrn from the ticket - # use this to get the right slice credential - ticket = SfaTicket(filename=ticket_file) - ticket.decode() - slice_hrn = ticket.gidObject.get_hrn() - slice_urn = hrn_to_urn(slice_hrn, 'slice') - #slice_hrn = ticket.attributes['slivers'][0]['hrn'] - user_cred = self.get_user_cred() - slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True) - - # get a list of node hostnames from the RSpec - tree = etree.parse(StringIO(ticket.rspec)) - root = tree.getroot() - hostnames = root.xpath("./network/site/node/hostname/text()") - - # create an xmlrpc connection to the component manager at each of these - # components and gall redeem_ticket - connections = {} - for hostname in hostnames: - try: - self.logger.info("Calling redeem_ticket at %(hostname)s " % locals()) - server = self.server_proxy(hostname, CM_PORT, self.key_file, \ - self.cert_file, self.options.debug) - server.RedeemTicket(ticket.save_to_string(save_parents=True), slice_cred) - self.logger.info("Success") - except socket.gaierror: - self.logger.error("redeem_ticket failed: Component Manager not accepting requests") - except Exception, e: - self.logger.log_exc(e.message) - return - - # delete named slice - def delete(self, opts, args): - slice_hrn = args[0] - slice_urn = hrn_to_urn(slice_hrn, 'slice') - slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True) - creds = [slice_cred] - if opts.delegate: - delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority)) - creds.append(delegated_cred) - server = self.server_proxy_from_opts(opts) - call_args = [slice_urn, creds] - if self.server_supports_options_arg(server): - options = {'call_id': unique_call_id()} - call_args.append(options) - return server.DeleteSliver(*call_args) - - # start named slice - def start(self, opts, args): - slice_hrn = args[0] - slice_urn = hrn_to_urn(slice_hrn, 'slice') - slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True) - creds = [slice_cred] - if opts.delegate: - delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority)) - creds.append(delegated_cred) - server = self.server_proxy_from_opts(opts) - return server.Start(slice_urn, creds) - - # stop named slice - def stop(self, opts, args): - slice_hrn = args[0] - slice_urn = hrn_to_urn(slice_hrn, 'slice') - slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True) - creds = [slice_cred] - if opts.delegate: - delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority)) - creds.append(delegated_cred) - server = self.server_proxy_from_opts(opts) - return server.Stop(slice_urn, creds) - - # reset named slice - def reset(self, opts, args): + server = self.sliceapi() + # slice urn slice_hrn = args[0] - slice_urn = hrn_to_urn(slice_hrn, 'slice') - server = self.server_proxy_from_opts(opts) - slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True) + slice_urn = hrn_to_urn(slice_hrn, 'slice') + + # creds + slice_cred = self.slice_credential(slice_hrn) creds = [slice_cred] - if opts.delegate: - delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority)) - creds.append(delegated_cred) - return server.reset_slice(creds, slice_urn) - def renew(self, opts, args): + # options and call_id when supported + api_options = {} + api_options['call_id'] = unique_call_id() + if options.show_credential: + show_credentials(creds) + status = server.Status([slice_urn], creds, * + self.ois(server, api_options)) + value = ReturnValue.get_value(status) + if self.options.raw: + save_raw_to_file(status, self.options.raw, + self.options.rawformat, self.options.rawbanner) + else: + print(value) + return self.success(status) + + @declare_command("slice_hrn [...] action", "") + def action(self, options, args): + """ + Perform the named operational action on all or named slivers of the named slice + """ + if len(args) == 0: + self.print_help() + sys.exit(1) + + server = self.sliceapi() + api_options = {} + # slice urn slice_hrn = args[0] - slice_urn = hrn_to_urn(slice_hrn, 'slice') - server = self.server_proxy_from_opts(opts) - slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True) + slice_urn = Xrn(slice_hrn, type='slice').get_urn() + if len(args) > 2: + # we have sliver urns + sliver_urns = args[1:-1] + else: + # we provision all the slivers of the slice + sliver_urns = [slice_urn] + action = args[-1] + # cred + slice_cred = self.slice_credential(args[0]) creds = [slice_cred] - if opts.delegate: - delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority)) + if options.delegate: + delegated_cred = self.delegate_cred( + slice_cred, get_authority(self.authority)) creds.append(delegated_cred) - time = args[1] - - call_args = [slice_urn, creds, time] - if self.server_supports_options_arg(server): - options = {'call_id': unique_call_id()} - call_args.append(options) - result = server.RenewSliver(*call_args) - value = ReturnValue.get_value(result) - return value + perform_action = server.PerformOperationalAction( + sliver_urns, creds, action, api_options) + value = ReturnValue.get_value(perform_action) + if self.options.raw: + save_raw_to_file(perform_action, self.options.raw, + self.options.rawformat, self.options.rawbanner) + else: + print(value) + return self.success(perform_action) + + @declare_command("slice_hrn [...] time", + "\n".join(["sfi renew onelab.ple.heartbeat 2015-04-31", + "sfi renew onelab.ple.heartbeat 2015-04-31T14:00:00Z", + "sfi renew onelab.ple.heartbeat +5d", + "sfi renew onelab.ple.heartbeat +3w", + "sfi renew onelab.ple.heartbeat +2m", ])) + def renew(self, options, args): + """ + renew slice(Renew) + """ + if len(args) < 2: + self.print_help() + sys.exit(1) - def status(self, opts, args): + server = self.sliceapi() slice_hrn = args[0] - slice_urn = hrn_to_urn(slice_hrn, 'slice') - slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True) + slice_urn = Xrn(slice_hrn, type='slice').get_urn() + + if len(args) > 2: + # we have sliver urns + sliver_urns = args[1:-1] + else: + # we provision all the slivers of the slice + sliver_urns = [slice_urn] + input_time = args[-1] + + # time: don't try to be smart on the time format, server-side will + # creds + slice_cred = self.slice_credential(args[0]) creds = [slice_cred] - if opts.delegate: - delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority)) - creds.append(delegated_cred) - server = self.server_proxy_from_opts(opts) - call_args = [slice_urn, creds] - if self.server_supports_options_arg(server): - options = {'call_id': unique_call_id()} - call_args.append(options) - result = server.SliverStatus(*call_args) - value = ReturnValue.get_value(result) - print value - if opts.file: - save_variable_to_file(value, opts.file, opts.fileformat) + # options and call_id when supported + api_options = {} + api_options['call_id'] = unique_call_id() + if options.alap: + api_options['geni_extend_alap'] = True + if options.show_credential: + show_credentials(creds) + renew = server.Renew(sliver_urns, creds, input_time, + *self.ois(server, api_options)) + value = ReturnValue.get_value(renew) + if self.options.raw: + save_raw_to_file(renew, self.options.raw, + self.options.rawformat, self.options.rawbanner) + else: + print(value) + return self.success(renew) + @declare_command("slice_hrn", "") + def shutdown(self, options, args): + """ + shutdown named slice (Shutdown) + """ + if len(args) != 1: + self.print_help() + sys.exit(1) - def shutdown(self, opts, args): + server = self.sliceapi() + # slice urn slice_hrn = args[0] - slice_urn = hrn_to_urn(slice_hrn, 'slice') - slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True) + slice_urn = hrn_to_urn(slice_hrn, 'slice') + # creds + slice_cred = self.slice_credential(slice_hrn) creds = [slice_cred] - if opts.delegate: - delegated_cred = self.delegate_cred(slice_cred, get_authority(self.authority)) - creds.append(delegated_cred) - server = self.server_proxy_from_opts(opts) - return server.Shutdown(slice_urn, creds) - - def print_help (self): - self.sfi_parser.print_help() - self.cmd_parser.print_help() + shutdown = server.Shutdown(slice_urn, creds) + value = ReturnValue.get_value(shutdown) + if self.options.raw: + save_raw_to_file(shutdown, self.options.raw, + self.options.rawformat, self.options.rawbanner) + else: + print(value) + return self.success(shutdown) - # - # Main: parse arguments and dispatch to command - # - def dispatch(self, command, cmd_opts, cmd_args): - return getattr(self, command)(cmd_opts, cmd_args) + @declare_command("[name]", "") + def gid(self, options, args): + """ + Create a GID (CreateGid) + """ + if len(args) < 1: + self.print_help() + sys.exit(1) - def main(self): - self.sfi_parser = self.create_parser() - (options, args) = self.sfi_parser.parse_args() - self.options = options + target_hrn = args[0] + my_gid_string = open(self.client_bootstrap.my_gid()).read() + gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string) + if options.file: + filename = options.file + else: + filename = os.sep.join( + [self.options.sfi_dir, '{}.gid'.format(target_hrn)]) + logger.info("writing {} gid to {}".format(target_hrn, filename)) + GID(string=gid).save_to_file(filename) + # xxx should analyze result + return 0 + + #################### + @declare_command("to_hrn", """$ sfi delegate -u -p -s ple.inria.heartbeat -s ple.inria.omftest ple.upmc.slicebrowser + + will locally create a set of delegated credentials for the benefit of ple.upmc.slicebrowser + the set of credentials in the scope for this call would be + (*) ple.inria.thierry_parmentelat.user_for_ple.upmc.slicebrowser.user.cred + as per -u/--user + (*) ple.inria.pi_for_ple.upmc.slicebrowser.user.cred + as per -p/--pi + (*) ple.inria.heartbeat.slice_for_ple.upmc.slicebrowser.user.cred + (*) ple.inria.omftest.slice_for_ple.upmc.slicebrowser.user.cred + because of the two -s options + +""") + def delegate(self, options, args): + """ + (locally) create delegate credential for use by given hrn + make sure to check for 'sfi myslice' instead if you plan + on using MySlice + """ + if len(args) != 1: + self.print_help() + sys.exit(1) - self.logger.setLevelFromOptVerbose(self.options.verbose) - if options.hashrequest: - self.hashrequest = True - - if len(args) <= 0: - self.logger.critical("No command given. Use -h for help.") - return -1 - - command = args[0] - self.cmd_parser = self.create_cmd_parser(command) - (cmd_opts, cmd_args) = self.cmd_parser.parse_args(args[1:]) - - self.set_servers() - self.logger.info("Command=%s" % command) - if command in ("resources"): - self.logger.debug("resources cmd_opts %s" % cmd_opts.format) - elif command in ("list", "show", "remove"): - self.logger.debug("cmd_opts.type %s" % cmd_opts.type) - self.logger.debug('cmd_args %s' % cmd_args) + to_hrn = args[0] + # support for several delegations in the same call + # so first we gather the things to do + tuples = [] + for slice_hrn in options.delegate_slices: + message = "{}.slice".format(slice_hrn) + original = self.slice_credential_string(slice_hrn) + tuples.append((message, original,)) + if options.delegate_pi: + my_authority = self.authority + message = "{}.pi".format(my_authority) + original = self.my_authority_credential_string() + tuples.append((message, original,)) + for auth_hrn in options.delegate_auths: + message = "{}.auth".format(auth_hrn) + original = self.authority_credential_string(auth_hrn) + tuples.append((message, original, )) + # if nothing was specified at all at this point, let's assume -u + if not tuples: + options.delegate_user = True + # this user cred + if options.delegate_user: + message = "{}.user".format(self.user) + original = self.my_credential_string + tuples.append((message, original, )) + + # default type for beneficial is user unless -A + to_type = 'authority' if options.delegate_to_authority else 'user' + + # let's now handle all this + # it's all in the filenaming scheme + for (message, original) in tuples: + delegated_string = self.client_bootstrap.delegate_credential_string( + original, to_hrn, to_type) + delegated_credential = Credential(string=delegated_string) + filename = os.path.join(self.options.sfi_dir, + "{}_for_{}.{}.cred".format(message, to_hrn, to_type)) + delegated_credential.save_to_file(filename, save_parents=True) + logger.info("delegated credential for {} to {} and wrote to {}" + .format(message, to_hrn, filename)) + + #################### + @declare_command("", """$ less +/myslice sfi_config +[myslice] +backend = http://manifold.pl.sophia.inria.fr:7080 +# the HRN that myslice uses, so that we are delegating to +delegate = ple.upmc.slicebrowser +# platform - this is a myslice concept +platform = ple +# username - as of this writing (May 2013) a simple login name +username = thierry + +$ sfi myslice + will first collect the slices that you are part of, then make sure + all your credentials are up-to-date (read: refresh expired ones) + then compute delegated credentials for user 'ple.upmc.slicebrowser' + and upload them all on myslice backend, using 'platform' and 'user'. + A password will be prompted for the upload part. + +$ sfi -v myslice -- or sfi -vv myslice + same but with more and more verbosity + +$ sfi m -b http://mymanifold.foo.com:7080/ + is synonym to sfi myslice as no other command starts with an 'm' + and uses a custom backend for this one call +""" + ) # declare_command + def myslice(self, options, args): + """ This helper is for refreshing your credentials at myslice; it will + * compute all the slices that you currently have credentials on + * refresh all your credentials (you as a user and pi, your slices) + * upload them to the manifold backend server + for last phase, sfi_config is read to look for the [myslice] section, + and namely the 'backend', 'delegate' and 'user' settings + """ - try: - self.dispatch(command, cmd_opts, cmd_args) - except KeyError: - self.logger.critical ("Unknown command %s"%command) - raise + ########## + if len(args) > 0: + self.print_help() + sys.exit(1) + # enable info by default + logger.setLevelFromOptVerbose(self.options.verbose + 1) + # the rough sketch goes like this + # (0) produce a p12 file + self.client_bootstrap.my_pkcs12() + + # (a) rain check for sufficient config in sfi_config + myslice_dict = {} + myslice_keys = ['backend', 'delegate', 'platform', 'username'] + for key in myslice_keys: + value = None + # oct 2013 - I'm finding myself juggling with config files + # so a couple of command-line options can now override config + if hasattr(options, key) and getattr(options, key) is not None: + value = getattr(options, key) + else: + full_key = "MYSLICE_" + key.upper() + value = getattr(self.config_instance, full_key, None) + if value: + myslice_dict[key] = value + else: + print("Unsufficient config, missing key {} in [myslice] section of sfi_config" + .format(key)) + if len(myslice_dict) != len(myslice_keys): sys.exit(1) - - return - -if __name__ == "__main__": - Sfi().main() + + # (b) figure whether we are PI for the authority where we belong + logger.info("Resolving our own id {}".format(self.user)) + my_records = self.registry().Resolve(self.user, self.my_credential_string) + if len(my_records) != 1: + print("Cannot Resolve {} -- exiting".format(self.user)) + sys.exit(1) + my_record = my_records[0] + my_auths_all = my_record['reg-pi-authorities'] + logger.info( + "Found {} authorities that we are PI for".format(len(my_auths_all))) + logger.debug("They are {}".format(my_auths_all)) + + my_auths = my_auths_all + if options.delegate_auths: + my_auths = list(set(my_auths_all).intersection( + set(options.delegate_auths))) + logger.debug( + "Restricted to user-provided auths {}".format(my_auths)) + + # (c) get the set of slices that we are in + my_slices_all = my_record['reg-slices'] + logger.info( + "Found {} slices that we are member of".format(len(my_slices_all))) + logger.debug("They are: {}".format(my_slices_all)) + + my_slices = my_slices_all + # if user provided slices, deal only with these - if they are found + if options.delegate_slices: + my_slices = list(set(my_slices_all).intersection( + set(options.delegate_slices))) + logger.debug( + "Restricted to user-provided slices: {}".format(my_slices)) + + # (d) make sure we have *valid* credentials for all these + hrn_credentials = [] + hrn_credentials.append((self.user, 'user', self.my_credential_string,)) + for auth_hrn in my_auths: + hrn_credentials.append( + (auth_hrn, 'auth', self.authority_credential_string(auth_hrn),)) + for slice_hrn in my_slices: + try: + hrn_credentials.append( + (slice_hrn, 'slice', self.slice_credential_string(slice_hrn),)) + except: + print("WARNING: could not get slice credential for slice {}" + .format(slice_hrn)) + + # (e) check for the delegated version of these + # xxx todo add an option -a/-A? like for 'sfi delegate' for when we ever + # switch to myslice using an authority instead of a user + delegatee_type = 'user' + delegatee_hrn = myslice_dict['delegate'] + hrn_delegated_credentials = [] + for (hrn, htype, credential) in hrn_credentials: + delegated_credential = self.client_bootstrap.delegate_credential_string( + credential, delegatee_hrn, delegatee_type) + # save these so user can monitor what she's uploaded + filename = os.path.join(self.options.sfi_dir, + "{}.{}_for_{}.{}.cred" + .format(hrn, htype, delegatee_hrn, delegatee_type)) + with open(filename, 'w') as f: + f.write(delegated_credential) + logger.debug("(Over)wrote {}".format(filename)) + hrn_delegated_credentials.append( + (hrn, htype, delegated_credential, filename, )) + + # (f) and finally upload them to manifold server + # xxx todo add an option so the password can be set on the command line + # (but *NOT* in the config file) so other apps can leverage this + logger.info("Uploading on backend at {}".format( + myslice_dict['backend'])) + uploader = ManifoldUploader(logger=logger, + url=myslice_dict['backend'], + platform=myslice_dict['platform'], + username=myslice_dict['username'], + password=options.password) + uploader.prompt_all() + (count_all, count_success) = (0, 0) + for (hrn, htype, delegated_credential, filename) in hrn_delegated_credentials: + # inspect + inspect = Credential(string=delegated_credential) + expire_datetime = inspect.get_expiration() + message = "{} ({}) [exp:{}]".format(hrn, htype, expire_datetime) + if uploader.upload(delegated_credential, message=message): + count_success += 1 + count_all += 1 + logger.info("Successfully uploaded {}/{} credentials" + .format(count_success, count_all)) + + # at first I thought we would want to save these, + # like 'sfi delegate does' but on second thought + # it is probably not helpful as people would not + # need to run 'sfi delegate' at all anymore + if count_success != count_all: + sys.exit(1) + # xxx should analyze result + return 0 + + @declare_command("cred", "") + def trusted(self, options, args): + """ + return the trusted certs at this interface (get_trusted_certs) + """ + if options.registry_interface: + server = self.registry() + else: + server = self.sliceapi() + cred = self.my_authority_credential_string() + trusted_certs = server.get_trusted_certs(cred) + if not options.registry_interface: + trusted_certs = ReturnValue.get_value(trusted_certs) + + for trusted_cert in trusted_certs: + print("\n===========================================================\n") + gid = GID(string=trusted_cert) + gid.dump() + cert = Certificate(string=trusted_cert) + logger.debug('Sfi.trusted -> {}'.format(cert.get_subject())) + print("Certificate:\n{}\n\n".format(trusted_cert)) + # xxx should analyze result + return 0 + + @declare_command("", "") + def introspect(self, options, args): + """ + If remote server supports XML-RPC instrospection API, allows + to list supported methods + """ + if options.registry_interface: + server = self.registry() + else: + server = self.sliceapi() + results = server.serverproxy.system.listMethods() + # at first sight a list here means it's fine, + # and a dict suggests an error (no support for introspection?) + if isinstance(results, list): + results = [name for name in results if 'system.' not in name] + results.sort() + print("== methods supported at {}".format(server.url)) + if 'Discover' in results: + print("== has support for 'Discover' - most likely a v3") + else: + print("== has no support for 'Discover' - most likely a v2") + for name in results: + print(name) + else: + print("Got return of type {}, expected a list".format(type(results))) + print("This suggests the remote end does not support introspection") + print(results)