Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Retrieve application and pod statuses #1332

Merged
merged 15 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions examples/multitenancy/hello-world/hs1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@ kind: HelloWorldService
metadata:
name: hs1
spec:
greeting: Hello hello hello
replicas: 1
omgoswami marked this conversation as resolved.
Show resolved Hide resolved

greeting: Hello hello hello
replicas: 1
18 changes: 9 additions & 9 deletions plugins/appresources.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import os
from crmetrics import CRBase

class AppResourcesFinder(CRBase):

class AppResourcesFinder(CRBase):
def run_command(self, cmd):
cmdOut = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()
out = cmdOut[0].decode('utf-8')
Expand Down Expand Up @@ -56,10 +56,10 @@ def get_target_ns(self, kubeplus_ns, kind, instance, kubeconfig):
targetNS = parts[0].strip()
releaseName = parts[1].strip().split("\n")[0]
return targetNS, releaseName
return targetNS, releaseName
return targetNS, releaseName

def get_helm_resources(self, targetNS, helmrelease, kubeconfig):
#print("Inside helm_resources")
# print("Inside helm_resources")
cmd = "helm get all " + helmrelease + " -n " + targetNS + ' ' + kubeconfig
out, err = self.run_command(cmd)

Expand All @@ -74,7 +74,7 @@ def get_helm_resources(self, targetNS, helmrelease, kubeconfig):
res_details = {}
res_details['name'] = res_name
res_details['namespace'] = targetNS
res_details['kind'] = kind
res_details['kind'] = kind
resources.append(res_details)

new_resource = False
Expand Down Expand Up @@ -129,7 +129,7 @@ def verify_kind_is_consumerapi(self, kind, kubeconfig):
instance = sys.argv[2]
kubeconfig = sys.argv[3]

#print("kind:" + kind + " instance:" + instance + " kubeconfig:" + kubeconfig)
# print("kind:" + kind + " instance:" + instance + " kubeconfig:" + kubeconfig)

valid_consumer_api = appResourcesFinder.verify_kind_is_consumerapi(kind, kubeconfig)
if not valid_consumer_api:
Expand All @@ -148,7 +148,7 @@ def verify_kind_is_consumerapi(self, kind, kubeconfig):
targetNS, helmrelease = appResourcesFinder.get_target_ns(res_ns, kind, instance, kubeconfig)
if targetNS == '' and helmrelease == '':
print("No Helm release found for {} resource {}".format(kind, instance))
#print(targetNS + " " + helmrelease)
# print(targetNS + " " + helmrelease)
pods = appResourcesFinder.get_pods(targetNS, kind, instance, kubeconfig)
networkpolicies = appResourcesFinder.get_networkpolicies(targetNS, kind, instance, kubeconfig)
resourcequotas = appResourcesFinder.get_resourcequotas(targetNS, kind, instance, kubeconfig)
Expand All @@ -162,10 +162,10 @@ def verify_kind_is_consumerapi(self, kind, kubeconfig):

# Ref: https://www.educba.com/python-print-table/
# https://stackoverflow.com/questions/20309255/how-to-pad-a-string-to-a-fixed-length-with-spaces-in-python
print ("{:<25} {:<25} {:<25} ".format("NAMESPACE", "KIND", "NAME"))
print ("{:<25} {:<25} {:<25} ".format(kubeplus_ns, kind, instance))
print("{:<25} {:<25} {:<25} ".format("NAMESPACE", "KIND", "NAME"))
print("{:<25} {:<25} {:<25} ".format(kubeplus_ns, kind, instance))
for res in allresources:
ns = res['namespace']
kind = res['kind']
name = res['name']
print ("{:<25} {:<25} {:<25} ".format(ns, kind, name))
print("{:<25} {:<25} {:<25} ".format(ns, kind, name))
84 changes: 84 additions & 0 deletions plugins/appstatus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import subprocess
import sys
import json
from crmetrics import CRBase


class AppStatusFinder(CRBase):

def get_app_instance_status(self, kind, instance, kubeconfig):
cmd = 'kubectl get %s %s -o json %s' % (kind, instance, kubeconfig)
out, err = self.run_command(cmd)
if err != "":
print("Something went wrong while getting app instance status.")
print(err)
exit(1)

deployed = False
ns = None
response = json.loads(out)
if 'status' in response:
if 'helmrelease' in response['status']:
helm_release = response['status']['helmrelease'].strip('\n')
ns, name = helm_release.split(':')
deployed = True
return name, ns, deployed
else:
# an error has occurred
status = response['status']
return status, ns, deployed

else:
return 'Application not deployed properly', ns, deployed


def get_app_pods(self, namespace, kubeconfig):
cmd = 'kubectl get pods -n %s %s -o json' % (namespace, kubeconfig)
out, err = self.run_command(cmd)
# format?
response = json.loads(out)
pods = []
for pod in response['items']:
name = pod['metadata']['name']
typ = pod['kind']
ns = pod['metadata']['namespace']
phase = pod['status']['phase']
pods.append((name, typ, ns, phase))
return pods


if __name__ == '__main__':
appStatusFinder = AppStatusFinder()
kind = sys.argv[1]
instance = sys.argv[2]
kubeconfig = sys.argv[3]

valid_consumer_api = appStatusFinder.verify_kind_is_consumerapi(kind, kubeconfig)
if not valid_consumer_api:
print(("{} is not a valid Consumer API.").format(kind))
exit(0)

res_exists, ns, err = appStatusFinder.check_res_exists(kind, instance, kubeconfig)
if not res_exists:
print(err)
exit(0)

working, error = appStatusFinder.validate_kind_and_instance(kind, instance, ns)
if working == False:
print(err)
exit(1)

release_name_or_status, release_ns, deployed = appStatusFinder.get_app_instance_status(kind, instance, kubeconfig)

if deployed:
deploy_str = 'Deployed'
else:
print(release_name_or_status)
exit(1)

pods = appStatusFinder.get_app_pods(instance, kubeconfig)

print("{:<55} {:<55} {:<55} {:<55}".format("NAME", "TYPE", "NAMESPACE", "STATUS"))
print("{:<55} {:<55} {:<55} {:<55}".format(release_name_or_status, 'helmrelease', release_ns, deploy_str))
for pod_name, typ, pod_ns, phase in pods:
print("{:<55} {:<55} {:<55} {:<55}".format(pod_name, typ, pod_ns, phase))
54 changes: 53 additions & 1 deletion plugins/crmetrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import platform
import pprint
import time

import yaml
import utils

class CRBase(object):
Expand Down Expand Up @@ -137,6 +137,58 @@ def get_resources_connections(self, kind, instance, namespace, kubeconfig):
print(e)
return json_output

def _get_resources(self, kind, plural, targetNS, kubeconfig):
cmd = "kubectl get " + plural + " -n " + targetNS + " " + kubeconfig
out, err = self._run_command(cmd)
resources = []
for line in out.split("\n"):
res_details = {}
line = line.strip()
if 'NAME' not in line and line != '' and line != '\n':
line1 = ' '.join(line.split())
parts = line1.split(" ")
res_name = parts[0].strip()
res_details['name'] = res_name
res_details['namespace'] = targetNS
res_details['kind'] = kind
resources.append(res_details)
return resources

def check_res_exists(self, kind, instance, kubeconfig):
cmd = 'kubectl get ' + kind + ' -A ' + kubeconfig
out, err = self.run_command(cmd)
for line in out.split("\n"):
if instance in line:
parts = line.split(" ")
ns = parts[0].strip()
return True, ns, ''
return False, '', kind + ' ' + instance + ' not found.'

def verify_kind_is_consumerapi(self, kind, kubeconfig):

if kind.lower() in 'resourcecompositions':
return False

cmd = 'kubectl get crds ' + kubeconfig
out, err = self.run_command(cmd)
for line in out.split("\n"):
parts = line.split(" ")
fqn = parts[0].strip()
parts1 = fqn.split(".")
plural = parts1[0]
singular = plural[0:len(plural) - 1]
if kind.lower() == singular:
return True
return False


def validate_kind_and_instance(self, kind, instance, namespace):
cmd = 'kubectl get %s %s -n %s' % (kind, instance, namespace)
_, err = self.run_command(cmd)
if err == '': # or None?
return True, None
return False, err


class CRMetrics(CRBase):

Expand Down
53 changes: 53 additions & 0 deletions plugins/kubectl-appstatus
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/bin/bash
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You have made this file executable, right? (chmod +x kubectl-appstatus)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.


source utils.sh

print_help () {
echo "NAME"
echo " kubectl appstatus"
echo ""
echo "SYNOPSIS"
echo " kubectl appstatus <Kind> <Instance> -k <Absolute path to kubeconfig>"
echo ""
echo "DESCRIPTION"
echo " kubectl appstatus shows the status of the application instance and its pods"
exit 0
}

if (( $# < 2 )); then
print_help
fi

kind=$1
instance=$2
kubeconfig=""

shift;
shift;

while getopts ":k:" opt; do
case ${opt} in
k )
kubeconfig1=$OPTARG
if [ ! -f $kubeconfig1 ]; then
echo "Kubeconfig $kubeconfig1 does not exist."
exit 0
fi;;
? )
echo "Invalid option: ${1} " 1>&2
print_help
exit 0
;;
esac
done

kubeconfig="--kubeconfig="$kubeconfig1

canonicalKind=$(get_canonical_kind $kind)

if [[ $canonicalKind == *"Unknown"* ]]; then
echo "$canonicalKind"
exit 0
fi

python3 /$KUBEPLUS_HOME/plugins/appstatus.py $canonicalKind $instance $kubeconfig
50 changes: 50 additions & 0 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,57 @@ def test_res_comp_with_no_podpolicies(self):
removed = True
else:
time.sleep(1)

def test_appstatus_plugin(self):
kubeplus_home = os.getenv("KUBEPLUS_HOME")
provider = kubeplus_home + '/kubeplus-saas-provider.json'

def cleanup():
cmd = "kubectl delete -f ../examples/multitenancy/hello-world/hs1.yaml --kubeconfig=%s" % provider
TestKubePlus.run_command(cmd)
cmd = "kubectl delete -f ../examples/multitenancy/hello-world/hello-world-service-composition-localchart.yaml --kubeconfig=%s" % provider
TestKubePlus.run_command(cmd)

if not TestKubePlus._is_kubeplus_running():
print("KubePlus is not running. Deploy KubePlus and then run tests")
sys.exit(0)

if os.getenv("KUBEPLUS_HOME") == '':
print("Skipping test as KUBEPLUS_HOME is not set.")
return

# register HelloWorldService API
cmd = "kubectl create -f ../examples/multitenancy/hello-world/hello-world-service-composition-localchart.yaml --kubeconfig=%s" % provider
TestKubePlus.run_command(cmd)

# check CRD installation
crd = "helloworldservices.platformapi.kubeplus"
crd_installed = self._check_crd_installed(crd)
if not crd_installed:
print("CRD " + crd + " not installed. Exiting this test.")
return

# create app instance
cmd = "kubectl create -f ../examples/multitenancy/hello-world/hs1.yaml --kubeconfig=%s" % provider
out, err = TestKubePlus.run_command(cmd)

time.sleep(10)
# test plugin
cmd = "kubectl appstatus HelloWorldService hs1 -k %s" % provider
out, err = TestKubePlus.run_command(cmd)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we write some assertions on the output?
Also, it will be good to print the output. That way, in the CI run we will be able to see how the output looks like.


if err != '':
print("Something went wrong with the plugin.")
print(err)
cleanup()
sys.exit(1)

# asserts
lines = out.split('\n')
self.assertTrue('Deployed' in lines[1])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the order of the lines always guaranteed to be such that the first line will be helm release and the second line will be Pods?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking this now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! In the driver code for the plugin in plugins/appstatus.py, I am printing the helmrelease first and then iterating through all of the pods in the namespace afterwards.

self.assertTrue('Running' in lines[2])

cleanup()
# TODO: Add tests for
# kubectl connections
# kubectl appresources
Expand Down
Loading