diff --git a/examples/multitenancy/hello-world/hs1.yaml b/examples/multitenancy/hello-world/hs1.yaml index f9c8e57b..77ebb91a 100644 --- a/examples/multitenancy/hello-world/hs1.yaml +++ b/examples/multitenancy/hello-world/hs1.yaml @@ -3,6 +3,5 @@ kind: HelloWorldService metadata: name: hs1 spec: - greeting: Hello hello hello - replicas: 1 - + greeting: Hello hello hello + replicas: 1 \ No newline at end of file diff --git a/plugins/appresources.py b/plugins/appresources.py index a6831b5a..e53fafad 100644 --- a/plugins/appresources.py +++ b/plugins/appresources.py @@ -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') @@ -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) @@ -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 @@ -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: @@ -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) @@ -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)) diff --git a/plugins/appstatus.py b/plugins/appstatus.py new file mode 100644 index 00000000..a853a2ca --- /dev/null +++ b/plugins/appstatus.py @@ -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)) \ No newline at end of file diff --git a/plugins/crmetrics.py b/plugins/crmetrics.py index a8dc7b49..3779d541 100644 --- a/plugins/crmetrics.py +++ b/plugins/crmetrics.py @@ -6,7 +6,7 @@ import platform import pprint import time - +import yaml import utils class CRBase(object): @@ -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): diff --git a/plugins/kubectl-appstatus b/plugins/kubectl-appstatus new file mode 100755 index 00000000..c1f0650b --- /dev/null +++ b/plugins/kubectl-appstatus @@ -0,0 +1,53 @@ +#!/bin/bash + +source utils.sh + +print_help () { + echo "NAME" + echo " kubectl appstatus" + echo "" + echo "SYNOPSIS" + echo " kubectl appstatus -k " + 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 diff --git a/tests/tests.py b/tests/tests.py index cabcf818..6c94749e 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -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