ONAP audit script detailing pods, docker images, tags and digests 63/89663/1
authorAndyWalshe <andy.walshe@est.tech>
Mon, 10 Jun 2019 23:20:41 +0000 (23:20 +0000)
committerAndyWalshe <andy.walshe@est.tech>
Mon, 10 Jun 2019 23:20:41 +0000 (23:20 +0000)
Change-Id: Ia8d369c978f3d1da0e98af91415cc50cd36b03a2
Issue-ID: INT-1101
Signed-off-by: AndyWalshe <andy.walshe@est.tech>
test/ete/scripts/probe-onap.py [new file with mode: 0644]

diff --git a/test/ete/scripts/probe-onap.py b/test/ete/scripts/probe-onap.py
new file mode 100644 (file)
index 0000000..a1dc235
--- /dev/null
@@ -0,0 +1,236 @@
+#!/usr/bin/env python
+"""
+This script attempts to probe an ONAP deployment in a Kubernetes cluster on an OpenStack stack
+and extract all pods and corresponding docker image versions along with some of the tool versions utilised.
+"""
+
+import os
+import sys
+import subprocess
+import argparse
+import logging
+
+OPENSTACK_GET_SERVER_IPS = 'openstack server list --name "^(%s-).*" -c Name -c Networks -f value --sort-column Name'
+
+SSH_CMD_TEMPLATE = 'ssh -o StrictHostKeychecking=no -i %s ubuntu@%s "sudo su -c \\"%s\\""'
+
+KUBECTL_GET_ALL_POD_IMAGES_AND_SHAS = 'kubectl get pods --all-namespaces -o=jsonpath=\'{range .items[*]}' \
+                                      '{\\\\\\"\\n\\\\\\"}{.metadata.name}{\\\\\\":::\\\\\\"}' \
+                                      '{range .status.containerStatuses[*]}{.image}{\\\\\\"___\\\\\\"}' \
+                                      '{.imageID}{\\\\\\" \\\\\\"}{end}{end}{\\\\\\"\\n\\\\\\"}\''
+
+DOCKER_INSPECT = 'docker inspect --format=\'{{index .RepoTags 0}}{{\\\\\\" \\\\\\"}}{{index .RepoDigests 0}}\' ' \
+                 '\$(docker images -q | uniq| tr \'\n\' \' \')'
+
+KUBECTL_VERSION = 'kubectl version'
+DOCKER_VERSION = 'docker --version'
+
+logging.basicConfig(level=logging.DEBUG, format='%(message)s')
+file_log = logging.FileHandler(filename='onap-probe-report.txt', mode='w')
+file_log.setLevel(logging.INFO)
+formatter = logging.Formatter('%(message)s')
+file_log.setFormatter(formatter)
+logging.getLogger('').addHandler(file_log)
+
+
+class CommandResult(object):
+    def __init__(self, exit_code, stdout, stderr):
+        self.exit_code = exit_code
+        self.stdout = stdout
+        self.stderr = stderr
+
+
+def run_command_or_exit(command, message=""):
+    if message:
+        logging.debug(message)
+    logging.debug('cmd> ' + command)
+
+    child = subprocess.Popen(command, stdout=subprocess.PIPE,
+                             stderr=subprocess.PIPE, shell=True)
+    result = child.communicate()
+
+    cmd_result = CommandResult(child.returncode,
+                               result[0].strip(), result[1].strip())
+
+    if cmd_result.exit_code:
+        logging.error("exit_code: '%d', stdout: '%s', stderr: '%s'" %
+                      (cmd_result.exit_code, cmd_result.stdout, cmd_result.stderr))
+        sys.exit(1)
+
+    return cmd_result
+
+
+class OpenStackK8sCluster(object):
+    def __init__(self, stack_name, identity_file):
+        self.stack_name = stack_name
+        self.identity_file = identity_file
+        self.servers = {}
+        self.vm_docker_images = set()
+        self.kubectl_version = 'unknown'
+        self.docker_version = 'unknown'
+
+        response = run_command_or_exit(OPENSTACK_GET_SERVER_IPS % stack_name,
+                                       "Get all stack servers and ip addressed using stack name").stdout
+        for line in response.split('\n'):
+            parts = line.split()
+            self.servers[parts[0].replace(stack_name+'-', "")] = parts[2]
+
+    def __str__(self):
+        desc = "Stack name: " + self.stack_name + '\n'
+        for key, value in sorted(self.servers.items()):
+            desc += "  " + key + " : " + value + "\n"
+        return desc.strip()
+
+    def get_stack_name(self):
+        return self.stack_name
+
+    def get_identity_file(self):
+        return self.identity_file
+
+    def get_rancher_ip_address(self):
+        return self.servers['rancher']
+
+    def get_worker_nodes(self):
+        return [value for key, value in self.servers.items() if 'k8s-' in key.lower()]
+
+    def get_orchestrators(self):
+        return [value for key, value in self.servers.items() if 'orch-' in key.lower()]
+
+    def determine_docker_images_on_vms(self):
+        for node_ip in self.get_worker_nodes() + self.get_orchestrators():
+            command = SSH_CMD_TEMPLATE % (self.get_identity_file(), node_ip, DOCKER_INSPECT)
+            vm_inspect_results = run_command_or_exit(command, "Examine server and list docker images").stdout
+
+            for inspect_line in vm_inspect_results.split('\n'):
+                name_tag, name_digest = inspect_line.split(' ')
+                name, tag = name_tag.rsplit(':', 1)
+                digest = name_digest.split('sha256:')[1]
+                self.vm_docker_images.add((name, tag, digest))
+
+    def get_docker_images_on_vms(self):
+        return self.vm_docker_images
+
+    def get_number_of_vm_docker_images(self):
+        return len(self.vm_docker_images)
+
+    def determine_kubectl_version(self):
+        command = SSH_CMD_TEMPLATE % (self.get_identity_file(), self.get_rancher_ip_address(), KUBECTL_VERSION)
+        self.kubectl_version = run_command_or_exit(command, "Examine rancher vm to determine kubectl version").stdout
+
+    def get_kubectl_version(self):
+        return self.kubectl_version
+
+    def determine_docker_version(self):
+        command = SSH_CMD_TEMPLATE % (self.get_identity_file(), self.get_worker_nodes()[0], DOCKER_VERSION)
+        self.docker_version = run_command_or_exit(command, "Examine worker node to determine docker version").stdout
+
+    def get_docker_version(self):
+        return self.docker_version
+
+
+class OnapDeployment(object):
+    def __init__(self, openstack_stack):
+        self.stack = openstack_stack
+        self.raw = ""
+        self.pods = []
+        self.unique_images = set()
+
+    def dig(self):
+        command = SSH_CMD_TEMPLATE % (self.stack.get_identity_file(), self.stack.get_rancher_ip_address(),
+                                      KUBECTL_GET_ALL_POD_IMAGES_AND_SHAS)
+        self.raw = run_command_or_exit(command, "Use kubectl to retrieve all pods and pod images in K8S cluster").stdout
+
+        for row in self.raw.strip().split("\n"):
+            self.pods.append(Pod(row))
+
+        for pod in self.pods:
+            for image in pod.get_images():
+                self.unique_images.add(image)
+
+    def __str__(self):
+        desc = "Pods and docker images:\n"
+        for pod in sorted(self.pods):
+            desc += str(pod)
+        return desc.strip()
+
+    def get_number_of_pods(self):
+        return len(self.pods)
+
+    def get_docker_images(self):
+        return sorted(self.unique_images)
+
+    def get_number_of_unique_docker_images(self):
+        return len(self.unique_images)
+
+    def get_number_of_docker_images(self):
+        images = []
+        for pod in self.pods:
+            for image in pod.get_images():
+                images.append(image)
+        return len(images)
+
+
+class Pod(object):
+    def __init__(self, data):
+        self.name, images = data.strip().split(":::")
+        self.shas_images = {}
+        for item in images.split(" "):
+            image_raw, sha_raw = item.split("___")
+            if "sha256:" in images:
+                self.shas_images[sha_raw.split("sha256:")[1]] = image_raw
+
+    def get_images(self):
+        return self.shas_images.values()
+
+    def __cmp__(self, other):
+        return cmp(self.name, other.name)
+
+    def __str__(self):
+        desc = self.name + "\n"
+        for key, value in sorted(self.shas_images.items(), key=lambda x: x[1]):
+            desc += "        " + value + ", " + key + "\n"
+        return desc
+
+
+def main():
+    # Disable output buffering to receive the output instantly
+    sys.stdout = os.fdopen(sys.stdout.fileno(), "w", 0)
+    sys.stderr = os.fdopen(sys.stderr.fileno(), "w", 0)
+
+    help_desc = "Script to probe an ONAP k8s cluster on an OpenStack stack and report back information on the " \
+                "stack vms, all pods and corresponding docker image versions/digests and some of the tool versions " \
+                "utilised."
+    parser = argparse.ArgumentParser(description=help_desc)
+    parser.add_argument("-s", "--stack-name", dest="stack_name",
+                        help="OpenStack stack name used by this ONAP deployment",
+                        metavar="STACKNAME", required=True)
+    parser.add_argument("-i", "--identity_file", dest="identity_file",
+                        help="OpenStack identity file to be used by ssh to access servers",
+                        metavar="IDENTITY-FILE", required=True)
+    args = parser.parse_args()
+
+    openstack_k8s = OpenStackK8sCluster(args.stack_name, args.identity_file)
+    openstack_k8s.determine_kubectl_version()
+    openstack_k8s.determine_docker_version()
+    openstack_k8s.determine_docker_images_on_vms()
+
+    onap_dep = OnapDeployment(openstack_k8s)
+    onap_dep.dig()
+
+    logging.info('\n%s\n' % openstack_k8s)
+    logging.info("number of pods: %d" % onap_dep.get_number_of_pods())
+    logging.info("number of docker images in pods: %d" % onap_dep.get_number_of_docker_images())
+    logging.info("number of unique docker images in pods: %d" % onap_dep.get_number_of_unique_docker_images())
+    logging.info("number of unique docker images on vms: %d" % openstack_k8s.get_number_of_vm_docker_images())
+    logging.info("docker version:\n%s" % openstack_k8s.get_docker_version())
+    logging.info("kubectl version:\n%s" % openstack_k8s.get_kubectl_version())
+
+    logging.info("\n%s\n" % onap_dep)
+
+    logging.info("<image-name>,<image-version>,<image-digest>")
+    for entry in sorted(openstack_k8s.get_docker_images_on_vms()):
+        logging.info('%s,%s,%s' % (entry[0], entry[1], entry[2]))
+
+
+if __name__ == "__main__":
+    main()