Repository: cloudstack-cloudmonkey Updated Branches: refs/heads/master a711367e4 -> fc5d85da2
Add support for using username and password to cloudmonkey. The interactive CLI user can call 'login' and 'logout' to start and terminate a session. If the apikey and secretkey are not supplied in the config, then the username and password are used. If the session has expired, the CLI will automatically login and obtain a session Project: http://git-wip-us.apache.org/repos/asf/cloudstack-cloudmonkey/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack-cloudmonkey/commit/61901f20 Tree: http://git-wip-us.apache.org/repos/asf/cloudstack-cloudmonkey/tree/61901f20 Diff: http://git-wip-us.apache.org/repos/asf/cloudstack-cloudmonkey/diff/61901f20 Branch: refs/heads/master Commit: 61901f20d23383589582620779ad55034b08c0a0 Parents: 34665b3 Author: Chiradeep Vittal <[email protected]> Authored: Tue Oct 15 19:21:26 2013 -0700 Committer: Chiradeep Vittal <[email protected]> Committed: Tue Oct 15 19:21:26 2013 -0700 ---------------------------------------------------------------------- cloudmonkey/cloudmonkey.py | 28 ++++++++++- cloudmonkey/config.py | 2 + cloudmonkey/requester.py | 108 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 131 insertions(+), 7 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack-cloudmonkey/blob/61901f20/cloudmonkey/cloudmonkey.py ---------------------------------------------------------------------- diff --git a/cloudmonkey/cloudmonkey.py b/cloudmonkey/cloudmonkey.py index b465bec..53ca066 100644 --- a/cloudmonkey/cloudmonkey.py +++ b/cloudmonkey/cloudmonkey.py @@ -36,6 +36,8 @@ try: from prettytable import PrettyTable from printer import monkeyprint from requester import monkeyrequest + from requester import login + from requester import logout except ImportError, e: print("Import error in %s : %s" % (__name__, e)) import sys @@ -73,6 +75,8 @@ class CloudMonkeyShell(cmd.Cmd, object): self.config_file = cfile self.config_options = read_config(self.get_attr, self.set_attr, self.config_file) + self.credentials = {'apikey':self.apikey, 'secretkey': self.secretkey, + 'username': self.username, 'password': self.password} self.loadcache() self.prompt = self.prompt.strip() + " " # Cosmetic fix for prompt @@ -90,6 +94,7 @@ class CloudMonkeyShell(cmd.Cmd, object): logger.debug("Error: Unable to read history. " + str(e)) atexit.register(readline.write_history_file, self.history_file) + def get_attr(self, field): return getattr(self, field) @@ -253,7 +258,7 @@ class CloudMonkeyShell(cmd.Cmd, object): response, error = monkeyrequest(command, args, isasync, self.asyncblock, logger, self.host, self.port, - self.apikey, self.secretkey, + self.credentials, self.timeout, self.protocol, self.path) if error is not None: self.monkeyprint(error) @@ -414,6 +419,25 @@ class CloudMonkeyShell(cmd.Cmd, object): return [s[offs:] for s in self.config_options if s.startswith(mline)] + def do_login(self, args): + """ + Login using stored credentials. Starts a session to be reused for subsequent api calls + """ + url = "%s://%s:%s%s" % (self.protocol, self.host, self.port, self.path) + session, sessionkey = login(url, self.username, self.password) + self.credentials['session'] = session + self.credentials['sessionkey'] = sessionkey + + def do_logout(self, args): + """ + Logout of session started with login with username and password + """ + url = "%s://%s:%s%s" % (self.protocol, self.host, self.port, self.path) + logout(url, self.credentials['session']) + self.credentials['session'] = None + self.credentials['sessionkey'] = None + + def pipe_runner(self, args): if args.find(' |') > -1: pname = self.program_name @@ -529,7 +553,9 @@ def main(): print __description__, "(%s)" % __projecturl__ sys.exit(0) + shell = CloudMonkeyShell(sys.argv[0], options.cfile) + if len(args) > 0: shell.onecmd(' '.join(args)) else: http://git-wip-us.apache.org/repos/asf/cloudstack-cloudmonkey/blob/61901f20/cloudmonkey/config.py ---------------------------------------------------------------------- diff --git a/cloudmonkey/config.py b/cloudmonkey/config.py index 2f91608..ae56bfa 100644 --- a/cloudmonkey/config.py +++ b/cloudmonkey/config.py @@ -66,6 +66,8 @@ config_fields['server']['timeout'] = '3600' # user config_fields['user']['apikey'] = '' config_fields['user']['secretkey'] = '' +config_fields['user']['username'] = '' +config_fields['user']['password'] = '' def write_config(get_attr, config_file, first_time=False): http://git-wip-us.apache.org/repos/asf/cloudstack-cloudmonkey/blob/61901f20/cloudmonkey/requester.py ---------------------------------------------------------------------- diff --git a/cloudmonkey/requester.py b/cloudmonkey/requester.py index b06e1fc..93400e1 100644 --- a/cloudmonkey/requester.py +++ b/cloudmonkey/requester.py @@ -26,6 +26,7 @@ try: import os import pdb import re + import requests import shlex import sys import time @@ -45,8 +46,95 @@ def logger_debug(logger, message): logger.debug(message) +def login(url, username, password): + """ + Login and obtain a session to be used for subsequent API calls + Wrong username/password leads to HTTP error code 531 + """ + args = {} + + args["command"] = 'login' + args["username"] = username + args["password"] = password + args["domain"] = "/" + args["response"] = "json" + + sessionkey = '' + session = requests.Session() + + resp = session.post(url, params=args) + if resp.status_code == 200: + sessionkey = resp.json()['loginresponse']['sessionkey'] + userid = resp.json()['loginresponse']['userid'] + elif resp.status_code == 531: + print "Error authenticating at %s, with username: %s, and password: %s" % (url, username, password) + session = None + sessionkey = None + else: + resp.raise_for_status() + + + return session, sessionkey + + +def logout(url, session): + if session is None: + return + session.get(url, params={'command': 'logout'}) + +def make_request_with_password(command, args, logger, url, credentials): + + error = None + username = credentials['username'] + password = credentials['password'] + + if not (username and password): + error = "Username and password cannot be empty" + result = None + return result, error + + tries = 0 + retry = True + + while tries < 2 and retry: + sessionkey = credentials.get('sessionkey') + session = credentials.get('session') + tries += 1 + + #obtain a valid session if not supplied + if not (session and sessionkey): + session, sessionkey = login(url, username, password) + if not (session and sessionkey): + return None, 'Error authenticating' + credentials['session'] = session + credentials['sessionkey'] = sessionkey + + args['sessionkey'] = sessionkey + + #make the api call + resp = session.get(url, params=args) + result = resp.text + logger_debug(logger, "Response received: %s" % resp.text) + + if resp.status_code == 200: #success + retry = False + break + if resp.status_code == 401: #sessionkey is wrong + credentials['session'] = None + credentials['sessionkey'] = None + continue + + if resp.status_code != 200 and resp.status_code != 401: + error = "%s: %s" % (str(resp.status_code), resp.headers.get('X-Description')) + result = None + retry = False + + + return result, error + + def make_request(command, args, logger, host, port, - apikey, secretkey, protocol, path): + credentials, protocol, path): response = None error = None @@ -58,8 +146,16 @@ def make_request(command, args, logger, host, port, args = {} args["command"] = command - args["apiKey"] = apikey args["response"] = "json" + + #try to use the apikey/secretkey method by default + #if not present, use the username/password method + if not credentials['apikey']: + url = "%s://%s:%s%s" % (protocol, host, port, path) + return make_request_with_password(command, args, logger, url, credentials) + + args['apikey'] = credentials['apikey'] + secretkey = credentials['secretkey'] request = zip(args.keys(), args.values()) request.sort(key=lambda x: x[0].lower()) @@ -92,13 +188,13 @@ def make_request(command, args, logger, host, port, def monkeyrequest(command, args, isasync, asyncblock, logger, host, port, - apikey, secretkey, timeout, protocol, path): + credentials, timeout, protocol, path): response = None error = None logger_debug(logger, "======== START Request ========") logger_debug(logger, "Requesting command=%s, args=%s" % (command, args)) - response, error = make_request(command, args, logger, host, port, - apikey, secretkey, protocol, path) + response, error = make_request(command, args, logger, host, port, credentials, protocol, path) + logger_debug(logger, "======== END Request ========\n") if error is not None: @@ -135,7 +231,7 @@ def monkeyrequest(command, args, isasync, asyncblock, logger, host, port, progress += 1 logger_debug(logger, "Job %s to timeout in %ds" % (jobid, timeout)) response, error = make_request(command, request, logger, - host, port, apikey, secretkey, + host, port, credentials, protocol, path) if error is not None: return response, error
