Testing in a CI/CD Pipeline Part 3: Deployment testing
Why, how, when, where to perform testing in a CI/CD pipeline.

Search for a command to run...
Why, how, when, where to perform testing in a CI/CD pipeline.

No comments yet. Be the first to comment.
Tools are an essential part of developing good and consistent software. This is my attempt to help you out & shed some light on the tools that helped me because it's just an idea that all you need ๐ก.
Why, how, when, where to perform testing in a CI/CD pipeline.
Bringing in a fresh perspective on how you can turn your traditional streaming pipeline to perform like batch processing with endless possibilities

Uncover why SQL may not be the best choice for ETL pipelines in data applications and learn about common hurdles.

Turning Data Pipeline Failures into Success Stories: A Memory Optimization Case Study with Polars Library & Data Engineering best practices.

Practical techniques backed by benchmarks for working with Databases effectively in Python

Guide to Efficiently Streamlining Your Databricks Environment Setup

This is part 3 of the Testing in a CI/CD Pipeline series. It is advised first to go through part 1, part 2 ๐ค.
Deployment testing is different from system or unit testing. Let's see in brief (as the original intent of this guide is how to integrate it CI/CD pipeline)
Why
How
When
Where
Note: I believe the execution part is more important than the logic (as core logic is the same as integration testing). So I will be focussing more on the execution part.
You may not be using the Kubernetes at all but the idea behind this is platform agnostic and understanding the flow is key, having said that let's move on
kubectl exec task at the end because all we need to do is run the python script sitting inside the deployment pod.kubectl -n <your namespace> exec po/<deployment test pod name> -- python3 deployment_test.py
Note: In the later section will see how all this can be automated using reference/variables.
Here comes the magic of kubernets as all the networking is handled by itself. There are a lot of options, but we will be using DNS for Services and Pods as both the service as internal.
All you need to know:
The IP address for URL will be http://<k8s deployment name>.<namespace>.svc.cluster.local/<your remaining endpoint or sub-page>. Let's see an example. Let's say if k8s deployment name is my-deployment & namespace is dev then it will be http://my-deployment.dev.svc.cluster.local/<some-api>
Directly using IP address is bad practice as it will keep on changing after every new/restart pod. But the above method, kubernetes will handle this for us.
I will be using the Release pipeline of the Azure DevOps pipeline for CD Job and focus just on the deployment testing task.
kubectl task
kubectl exec command. kubectl get command to extract the current pod name of the given deployment/app. See the Arguments section carefully. This is where we are extracting the name. The argument will be,pods -l app=crs-ai-deployment-test -o jsonpath={.items[*].metadata.name}


As you can see from the above screenshot, I am using test as a reference which makes the variable name as a test.KubectlOutput.
kubectl exec command to run the python script. See the argument section carefully. The test.KubectlOutput which was produced in the previous stage is used now. 
kubectl log command with --since=10m flag
$(pod.KubectlOutput) is the name of the microservice. Its current pod name can be extracted similarly to how we did for the deployment test pod. Only when a previous task failed as this should be only run when the previous task of `kubectl exec' task performing deployment test failed. So this is how deployment testing can be automated and integrated into the CD job. Let's see some action.

This may change based on your requirement. But you can still refer to this for the idea, as always an idea is platform agnostic.
import sys
import json
import logging
import argparse
import requests
from typing import Dict, Tuple
sys.tracebacklimit = 0
logging.basicConfig(level=logging.INFO, format="[%(levelname)s]: %(message)s")
logger = logging.getLogger(__name__)
parser = argparse.ArgumentParser()
parser.add_argument("--namespace", type=str) # Targeted namespace
parser.add_argument("--deployment_name", type=str) # Targeted test as this is compilation of all individual test
def payload_data(deployment_name: str) -> Tuple[str, Dict]:
"""Prepare payload data for Deployment specifics
Parameters
----------
deployment_name : str
Name of deployment to perform testing.
Returns
-------
str
Api name
Dict
Sample data to check for deployment testing
Raises
------
ValueError
Must be from supported deployment testing: <here you can add all your test name><eg> research-clarity-id-applicability,research-clarity-id-adv-nonadv
"""
# TODO: Add new deployment name to the list
supported_deployment = [
"research-clarity-id-applicability",
"research-clarity-id-adv-nonadv",
]
if deployment_name not in supported_deployment:
raise ValueError(
f"Given deployment is either wrong or not supported.\nIt must be from {', '.join(supported_deployment)} "
)
# TODO: Add all new sample data and API here
elif deployment_name == "research-clarity-id-applicability":
api_name = "IDA"
svc = f"crs-id-applicability-api.{args.namespace}.svc.cluster.local"
path = "./data/sample_data_research-clarity-id-applicability.json"
elif deployment_name == "research-clarity-id-adv-nonadv":
api_name = "adverse_nonadverse"
svc = f"crs-id-adverse.{args.namespace}.svc.cluster.local"
path = "./data/sample_data_research-clarity-id-adv-nonadv.json"
with open(path, "r") as file:
payload = json.load(file)
return (api_name, svc, payload)
api_name, svc, payload = payload_data(args.deployment_name)
url = f"http://{svc}/api/{api_name}"
logger.info(f"Testing api @ {url}")
header = {"Content-Type": "application/json"}
logger.info(f"Send API request for {args.deployment_name} deployment testing")
response = requests.request("POST", url, headers=header, json=payload)
# TODO: Add all new assert condition here.
if args.deployment_name == "research-clarity-id-applicability":
response_data = response.json()
assert (
response_data["ida_output_path"].split("/")[-1] == "IDA.ndjson"
), "Not Received expected output, test is failed"
elif args.deployment_name == "research-clarity-id-adv-nonadv":
response_data = str(response.content).replace("'", "")
assert (
response_data.split("/")[-1] == "classifiation_output.ndjson"
), "Not Received expected output, test is failed"
logger.info("Deployment test passed successfully !!!")