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