From c187f944613ba5346cc70c354c29804ccf14821a Mon Sep 17 00:00:00 2001 From: ormancey <emmanuel.ormancey@cern.ch> Date: Tue, 5 Oct 2021 15:09:55 +0200 Subject: [PATCH] stress test scripts --- Python/stress-testing/README.md | 9 +++ Python/stress-testing/channel.py | 95 +++++++++++++++++++++++++ Python/stress-testing/config.py | 87 ++++++++++++++++++++++ Python/stress-testing/get_api_token.py | 70 ++++++++++++++++++ Python/stress-testing/notification.py | 25 +++++++ Python/stress-testing/stress_testing.py | 80 +++++++++++++++++++++ 6 files changed, 366 insertions(+) create mode 100644 Python/stress-testing/README.md create mode 100644 Python/stress-testing/channel.py create mode 100644 Python/stress-testing/config.py create mode 100644 Python/stress-testing/get_api_token.py create mode 100644 Python/stress-testing/notification.py create mode 100644 Python/stress-testing/stress_testing.py diff --git a/Python/stress-testing/README.md b/Python/stress-testing/README.md new file mode 100644 index 0000000..73edadc --- /dev/null +++ b/Python/stress-testing/README.md @@ -0,0 +1,9 @@ +# stress-testing Python script + +Tool to stress test Notification service. + +Steps: +- Needs https://gitlab.cern.ch/authzsvc/tools/auth-get-sso-cookie/ + - Works on LXPLUS for example +- Edit ```clientapp_name``` and ```audience``` in ```get_api_token.py``` depending on your target (dev, qa, prod) + diff --git a/Python/stress-testing/channel.py b/Python/stress-testing/channel.py new file mode 100644 index 0000000..bdfaf8a --- /dev/null +++ b/Python/stress-testing/channel.py @@ -0,0 +1,95 @@ +import requests +import json, os, re + +from requests.api import delete +from config import Config +import sys + +# Create new Channel +def create_channel(name, admingroup, description): + print('Creating Channel:', name) + data = {'channel': { + 'name': name, + 'slug': re.sub('[^0-9a-z-_]', '-', name.lower()), + 'description': description, + 'adminGroup': { 'groupIdentifier': admingroup }, + 'visibility': 'RESTRICTED', + 'submissionByForm': [ 'ADMINISTRATORS' ], + #'submissionByEmail': [ 'EGROUP' ], + #'incomingEgroup': egroup + '@cern.ch', + }} + #print(data) + r = requests.post(Config.BACKEND_URL + '/channels/', json=data, headers=Config.HEADER, verify=Config.VERIFY) + if r.status_code != requests.codes.ok: + print('error creating channel', r.json()) + sys.exit(2) + new_channel = r.json() + #print(new_channel) + + return new_channel['id'] + +# Delete Channel +def delete_channel(channel_id): + print('Deleting Channel', channel_id) + r = requests.delete(Config.BACKEND_URL + '/channels/' + channel_id, headers=Config.HEADER, verify=Config.VERIFY) + if r.status_code != requests.codes.ok: + print('error deleting channel', r.json()) + sys.exit(2) + return + +# Add egroup as Channel Member +def add_user_to_channel(channel_id, username): + print('Adding user to Channel members', username) + data = { 'username': username } + r = requests.put(Config.BACKEND_URL + '/channels/' + channel_id + '/members', json=data, headers=Config.HEADER, verify=Config.VERIFY) + if r.status_code != requests.codes.ok: + print('error updating channel', r.json()) + sys.exit(2) + updated_channel = r.json() + + return updated_channel['id'] + +# Add group as Channel Member +def add_group_to_channel(channel_id, group): + print('Adding group to Channel members', group) + data = { 'group': { 'groupIdentifier': group } } + r = requests.put(Config.BACKEND_URL + '/channels/' + channel_id + '/groups', json=data, headers=Config.HEADER, verify=Config.VERIFY) + if r.status_code != requests.codes.ok: + print('error updating channel', r.json()) + sys.exit(2) + updated_channel = r.json() + + return updated_channel['id'] + +# Remove ME from Members +def remove_me_from_channel(channel_id): + print('Removing ME from Channel members') + r = requests.get(Config.BACKEND_URL + '/me', headers=Config.HEADER, verify=Config.VERIFY) + if r.status_code != requests.codes.ok: + print('error removing ME from channel', r.json()) + sys.exit(2) + me = r.json() + if not me['userId']: + print('error retrieving ME', me) + sys.exit(2) + + data = { 'userId': me['userId'] } + r = requests.delete(Config.BACKEND_URL + '/channels/' + channel_id + '/members', json=data, headers=Config.HEADER, verify=Config.VERIFY) + if r.status_code != requests.codes.ok: + print('error removing ME from channel members', r.json()) + sys.exit(2) + updated_channel = r.json() + + return updated_channel['id'] + +# Change Channel owner +def set_channel_owner(channel_id, username): + print('Setting Channel owner to', username) + data = { 'username': username } + r = requests.put(Config.BACKEND_URL + '/channels/' + channel_id + '/owner', json=data, headers=Config.HEADER, verify=Config.VERIFY) + if r.status_code != requests.codes.ok: + print('error setting channel owner', r.json()) + sys.exit(2) + updated_channel = r.json() + + return updated_channel['id'] \ No newline at end of file diff --git a/Python/stress-testing/config.py b/Python/stress-testing/config.py new file mode 100644 index 0000000..2c25724 --- /dev/null +++ b/Python/stress-testing/config.py @@ -0,0 +1,87 @@ +from get_api_token import get_api_token + +_ACCESS_TOKEN=get_api_token() + +class Config: + """App configuration.""" + + # BACKEND_URL='https://api-notifications-dev.app.cern.ch' + BACKEND_URL = "https://localhost:8080" + ACCESS_TOKEN = _ACCESS_TOKEN + HEADER = {"Authorization": "Bearer " + ACCESS_TOKEN} + VERIFY = False # Verify SSL certificate for requests + + CHANNEL_NAME = "Stress Test Channel " + ADMIN_GROUP = "notifications-service-admins" + NOTIFICATION_SUMMARY = "Stress Test Notification " + NOTIFICATION_BODY = ( + "<p><h3>Stress Test Notification</h3>This is a stress test notification</p>" + ) + + NOTIFTEST_USERS = ['notiftest' + f"{i:02d}" for i in range(1, 51)] + + PROBE_USERS = [ + "probe000@cern.ch", + "probe001@cern.ch", + "probe002@cern.ch", + "probe003@cern.ch", + "probe004@cern.ch", + "probe005@cern.ch", + "probe006@cern.ch", + "probe007@cern.ch", + "probe008@cern.ch", + "probe009@cern.ch", + "probe010@cern.ch", + "probe011@cern.ch", + "probe012@cern.ch", + "probe013@cern.ch", + "probe014@cern.ch", + "probe100@cern.ch", + "probe101@cern.ch", + "probe102@cern.ch", + "probe103@cern.ch", + "probe104@cern.ch", + "probe105@cern.ch", + "probe106@cern.ch", + "probe107@cern.ch", + "probe108@cern.ch", + "probe109@cern.ch", + "probe110@cern.ch", + "probe111@cern.ch", + "probe112@cern.ch", + "probe113@cern.ch", + "probe114@cern.ch", + "probe200@cern.ch", + "probe201@cern.ch", + "probe202@cern.ch", + "probe203@cern.ch", + "probe204@cern.ch", + "probe205@cern.ch", + "probe206@cern.ch", + "probe207@cern.ch", + "probe208@cern.ch", + "probe209@cern.ch", + "probe210@cern.ch", + "probe211@cern.ch", + "probe212@cern.ch", + "probe213@cern.ch", + "probe214@cern.ch", + "probe300@cern.ch", + "probe301@cern.ch", + "probe302@cern.ch", + "probe303@cern.ch", + "probe304@cern.ch", + "probe305@cern.ch", + "probe306@cern.ch", + "probe307@cern.ch", + "probe308@cern.ch", + "probe309@cern.ch", + "probe310@cern.ch", + "probe311@cern.ch", + "probe312@cern.ch", + "probe313@cern.ch", + "probe314@cern.ch", + "probe900@cern.ch", + "probe901@cern.ch", + ] + diff --git a/Python/stress-testing/get_api_token.py b/Python/stress-testing/get_api_token.py new file mode 100644 index 0000000..5e43cb5 --- /dev/null +++ b/Python/stress-testing/get_api_token.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +from auth_get_sso_cookie import cern_sso +import subprocess +import requests + +AUTH_HOSTNAME = "auth.cern.ch" +AUTH_REALM = "cern" + +################# CONFIGURATION ################## + +# The Client application (application portal, option my app cannot keep a secret) +#clientapp_name = "tmp-push-notifications-clientscript" +clientapp_name = "notifications-dev-clientapi" +# clientapp_name = "notifications-qa-clientapi" +# clientapp_name = "notifications-clientapi" + +# Standard localhost uri for this virtual app +clientapp_uri = "https://localhost" + +# The target application (the backend API), with granted permissions to client application for token exchange +#audience = "tmp-push-notifications" +audience = "notifications-dev" +# audience = "notifications-qa" +# audience = "notifications" + +################################################## + +#if __name__ == "__main__": +def get_api_token(): + # Get Token for the clientscript application + # Using https://gitlab.cern.ch/authzsvc/tools/auth-get-sso-cookie/ + # Run with parameters for the clientscript application + # clientapi.py -u https://localhost -c tmp-push-notifications-clientscript + #token = command_line_tools.auth_get_sso_token() + # proc = subprocess.Popen( + # ["auth-get-sso-token", "-u", clientapp_uri, "-c", clientapp_name], + # stdout=subprocess.PIPE, + # stderr=subprocess.STDOUT) + # token = proc.communicate()[0].rstrip() + token = cern_sso.get_sso_token(clientapp_uri, clientapp_name, True, AUTH_HOSTNAME, AUTH_REALM) + #print("TOKEN to exchange retrieved") + #print(token) + + # Do Token Exchange for the Backend API application + # https://auth.docs.cern.ch/user-documentation/oidc/exchange-for-api/ + r = requests.post( + "https://auth.cern.ch/auth/realms/cern/protocol/openid-connect/token", + data={ + "client_id": clientapp_name, + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "subject_token": token, + "requested_token_type": "urn:ietf:params:oauth:token-type:refresh_token", + "audience": audience, + }, + ) + if not r.ok: + print( + "The token response was not successful: {}".format(r.json())) + r.raise_for_status() + + token_response = r.json() + access_token = token_response["access_token"] + #print("access_token retrieved") + #print(access_token) + return access_token + + # Then calls to the backend can be performed with this access token + # ACCESS_TOKEN=$(python get-api-token.py) + # curl -X GET "https://api-notifications-dev.app.cern.ch/channels/" -H "authorization: Bearer $ACCESS_TOKEN" + diff --git a/Python/stress-testing/notification.py b/Python/stress-testing/notification.py new file mode 100644 index 0000000..c3d2342 --- /dev/null +++ b/Python/stress-testing/notification.py @@ -0,0 +1,25 @@ +import requests +import json, os, re + +from requests.api import delete +from config import Config +import sys + + +# Send Notification +def send_notification(channel_id, summary): + print('Sending notification to:', channel_id, summary) + data = {'notification': { + 'summary': summary, + 'body': Config.NOTIFICATION_BODY, + 'target': channel_id, + }} + #print(data) + r = requests.post(Config.BACKEND_URL + '/notifications', json=data, headers=Config.HEADER, verify=Config.VERIFY) + if r.status_code != requests.codes.ok: + print('error sending notification', r.json()) + sys.exit(2) + new_notification = r.json() + #print(new_notification) + + return new_notification['id'] diff --git a/Python/stress-testing/stress_testing.py b/Python/stress-testing/stress_testing.py new file mode 100644 index 0000000..f4e556c --- /dev/null +++ b/Python/stress-testing/stress_testing.py @@ -0,0 +1,80 @@ +#!/usr/bin/python + +#import json, os, re +import sys, getopt +from config import Config +from channel import ( + create_channel, + add_group_to_channel, + remove_me_from_channel, + set_channel_owner, + add_user_to_channel, + delete_channel, +) +from notification import send_notification + +def usage(): + print("stress_testing.py -c <countchannels> -n <count notifications>") + print("\t-c <countchannels> : number of test channels to create") + print("\t-n <countnotifications> : number of test notifications to send per channel") + + +# Main +def main(argv): + countchannels = 0 + countnotifications = 0 + adminGroup = Config.ADMIN_GROUP + try: + opts, args = getopt.getopt(argv, "hc:n:", ["channels=", "notifications="]) + except getopt.GetoptError: + usage() + sys.exit(2) + for opt, arg in opts: + if opt == "-h": + usage() + sys.exit() + elif opt in ("-c", "--channels"): + countchannels = int(arg) + elif opt in ("-n", "--notifications"): + countnotifications = int(arg) + + print("Stress testing Notifications", Config.BACKEND_URL) + + # Create Channels + print("Creating ", countchannels, " test channels") + + channel_ids = [] + for i in range(0, countchannels): + channelname = description = Config.CHANNEL_NAME + str(i) + channel_id = create_channel(channelname, adminGroup, description) + if channel_id: + channel_ids.append(channel_id) + # Add notiftest users as member + for username in Config.NOTIFTEST_USERS: + add_user_to_channel(channel_id, username) + # Add probeXXX users as member + #for username in Config.PROBE_USERS: + # add_user_to_channel(channel_id, username) + # Remove ME as member + #remove_me_from_channel(channel_id) + # Set new owner to one notiftestXX + #set_channel_owner(channel_id, Config.NOTIFTEST_USERS[i % 50]) + else: + print("Error creating channel ", channelname) + + # Send Notifications + for i in range(0, countnotifications): + for channel_id in channel_ids: + send_notification(channel_id, Config.NOTIFICATION_SUMMARY + str(i)) + + # Wait for checks to be done, and notifications to be delivered before exit and cleanup + print('Please wait for notifications to be delivered, and check the system is stable before continuing.') + text = input("Press Enter to complete and remove all test channels...") + + # Cleanup by deleting channels + for channel_id in channel_ids: + delete_channel(channel_id) + + +if __name__ == "__main__": + main(sys.argv[1:]) -- GitLab