Skip to content

Commit

Permalink
Retrieve application and pod statuses (cloud-ark#1332)
Browse files Browse the repository at this point in the history
  • Loading branch information
omgoswami authored Aug 6, 2024
1 parent 4e2d9e6 commit effd6a9
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 13 deletions.
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

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

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)

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])
self.assertTrue('Running' in lines[2])

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

0 comments on commit effd6a9

Please sign in to comment.