first commit!
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..a099036
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+TBD
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..908381e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,5 @@
+init:
+ pip install -r requirements.txt
+
+# test:
+# nosetests tests
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..757c4ff
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+MCP sanity checks
+========================
+
+This is salt-based set of tests for basic verification of MCP deployments
diff --git a/cvp_checks/__init__.py b/cvp_checks/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cvp_checks/__init__.py
diff --git a/cvp_checks/__init__.pyc b/cvp_checks/__init__.pyc
new file mode 100644
index 0000000..3e354f3
--- /dev/null
+++ b/cvp_checks/__init__.pyc
Binary files differ
diff --git a/cvp_checks/fixtures/__init__.py b/cvp_checks/fixtures/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cvp_checks/fixtures/__init__.py
diff --git a/cvp_checks/fixtures/__init__.pyc b/cvp_checks/fixtures/__init__.pyc
new file mode 100644
index 0000000..cc3d431
--- /dev/null
+++ b/cvp_checks/fixtures/__init__.pyc
Binary files differ
diff --git a/cvp_checks/fixtures/base.py b/cvp_checks/fixtures/base.py
new file mode 100644
index 0000000..f1893fa
--- /dev/null
+++ b/cvp_checks/fixtures/base.py
@@ -0,0 +1,7 @@
+import pytest
+import cvp_checks.utils as utils
+
+
+@pytest.fixture
+def local_salt_client():
+ return utils.init_salt_client()
diff --git a/cvp_checks/fixtures/base.pyc b/cvp_checks/fixtures/base.pyc
new file mode 100644
index 0000000..f36eff1
--- /dev/null
+++ b/cvp_checks/fixtures/base.pyc
Binary files differ
diff --git a/cvp_checks/global_config.yaml b/cvp_checks/global_config.yaml
new file mode 100644
index 0000000..9edfc0d
--- /dev/null
+++ b/cvp_checks/global_config.yaml
@@ -0,0 +1,15 @@
+---
+# MANDATORY: Credentials for Salt master
+#SALT_URL: <salt_url>
+#SALT_USERNAME: <salt_usr>
+#SALT_PASSWORD: <salt_pwd>
+
+# List of nodes to skip in tests
+skipped_nodes: []
+
+# ntp test setting
+time_deviation: 30
+
+# mtu test setting
+# mask for interfaces to skip
+skipped_ifaces: ["bonding_masters", "lo", "veth", "tap", "cali"]
diff --git a/cvp_checks/tests/__init__.py b/cvp_checks/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/cvp_checks/tests/__init__.py
diff --git a/cvp_checks/tests/__init__.pyc b/cvp_checks/tests/__init__.pyc
new file mode 100644
index 0000000..c5b5b46
--- /dev/null
+++ b/cvp_checks/tests/__init__.pyc
Binary files differ
diff --git a/cvp_checks/tests/__pycache__/conftest.cpython-27-PYTEST.pyc b/cvp_checks/tests/__pycache__/conftest.cpython-27-PYTEST.pyc
new file mode 100644
index 0000000..9529827
--- /dev/null
+++ b/cvp_checks/tests/__pycache__/conftest.cpython-27-PYTEST.pyc
Binary files differ
diff --git a/cvp_checks/tests/__pycache__/test_contrail.cpython-27-PYTEST.pyc b/cvp_checks/tests/__pycache__/test_contrail.cpython-27-PYTEST.pyc
new file mode 100644
index 0000000..12c3420
--- /dev/null
+++ b/cvp_checks/tests/__pycache__/test_contrail.cpython-27-PYTEST.pyc
Binary files differ
diff --git a/cvp_checks/tests/__pycache__/test_default_gateway.cpython-27-PYTEST.pyc b/cvp_checks/tests/__pycache__/test_default_gateway.cpython-27-PYTEST.pyc
new file mode 100644
index 0000000..d449fb7
--- /dev/null
+++ b/cvp_checks/tests/__pycache__/test_default_gateway.cpython-27-PYTEST.pyc
Binary files differ
diff --git a/cvp_checks/tests/__pycache__/test_galera_cluster.cpython-27-PYTEST.pyc b/cvp_checks/tests/__pycache__/test_galera_cluster.cpython-27-PYTEST.pyc
new file mode 100644
index 0000000..2dd98e0
--- /dev/null
+++ b/cvp_checks/tests/__pycache__/test_galera_cluster.cpython-27-PYTEST.pyc
Binary files differ
diff --git a/cvp_checks/tests/__pycache__/test_mtu.cpython-27-PYTEST.pyc b/cvp_checks/tests/__pycache__/test_mtu.cpython-27-PYTEST.pyc
new file mode 100644
index 0000000..4fab58c
--- /dev/null
+++ b/cvp_checks/tests/__pycache__/test_mtu.cpython-27-PYTEST.pyc
Binary files differ
diff --git a/cvp_checks/tests/__pycache__/test_ntp_sync.cpython-27-PYTEST.pyc b/cvp_checks/tests/__pycache__/test_ntp_sync.cpython-27-PYTEST.pyc
new file mode 100644
index 0000000..0c139d4
--- /dev/null
+++ b/cvp_checks/tests/__pycache__/test_ntp_sync.cpython-27-PYTEST.pyc
Binary files differ
diff --git a/cvp_checks/tests/__pycache__/test_packet_checker.cpython-27-PYTEST.pyc b/cvp_checks/tests/__pycache__/test_packet_checker.cpython-27-PYTEST.pyc
new file mode 100644
index 0000000..816e78f
--- /dev/null
+++ b/cvp_checks/tests/__pycache__/test_packet_checker.cpython-27-PYTEST.pyc
Binary files differ
diff --git a/cvp_checks/tests/__pycache__/test_rabbit_cluster.cpython-27-PYTEST.pyc b/cvp_checks/tests/__pycache__/test_rabbit_cluster.cpython-27-PYTEST.pyc
new file mode 100644
index 0000000..ff5eeb0
--- /dev/null
+++ b/cvp_checks/tests/__pycache__/test_rabbit_cluster.cpython-27-PYTEST.pyc
Binary files differ
diff --git a/cvp_checks/tests/__pycache__/test_repo_list.cpython-27-PYTEST.pyc b/cvp_checks/tests/__pycache__/test_repo_list.cpython-27-PYTEST.pyc
new file mode 100644
index 0000000..1de85b4
--- /dev/null
+++ b/cvp_checks/tests/__pycache__/test_repo_list.cpython-27-PYTEST.pyc
Binary files differ
diff --git a/cvp_checks/tests/__pycache__/test_services.cpython-27-PYTEST.pyc b/cvp_checks/tests/__pycache__/test_services.cpython-27-PYTEST.pyc
new file mode 100644
index 0000000..f28935a
--- /dev/null
+++ b/cvp_checks/tests/__pycache__/test_services.cpython-27-PYTEST.pyc
Binary files differ
diff --git a/cvp_checks/tests/__pycache__/test_single_vip.cpython-27-PYTEST.pyc b/cvp_checks/tests/__pycache__/test_single_vip.cpython-27-PYTEST.pyc
new file mode 100644
index 0000000..692eefd
--- /dev/null
+++ b/cvp_checks/tests/__pycache__/test_single_vip.cpython-27-PYTEST.pyc
Binary files differ
diff --git a/cvp_checks/tests/ceph/__pycache__/test_ceph.cpython-27-PYTEST.pyc b/cvp_checks/tests/ceph/__pycache__/test_ceph.cpython-27-PYTEST.pyc
new file mode 100644
index 0000000..c0451ed
--- /dev/null
+++ b/cvp_checks/tests/ceph/__pycache__/test_ceph.cpython-27-PYTEST.pyc
Binary files differ
diff --git a/cvp_checks/tests/ceph/__pycache__/test_ceph_haproxy.cpython-27-PYTEST.pyc b/cvp_checks/tests/ceph/__pycache__/test_ceph_haproxy.cpython-27-PYTEST.pyc
new file mode 100644
index 0000000..71abde3
--- /dev/null
+++ b/cvp_checks/tests/ceph/__pycache__/test_ceph_haproxy.cpython-27-PYTEST.pyc
Binary files differ
diff --git a/cvp_checks/tests/ceph/config.yaml b/cvp_checks/tests/ceph/config.yaml
new file mode 100644
index 0000000..bfc8e70
--- /dev/null
+++ b/cvp_checks/tests/ceph/config.yaml
@@ -0,0 +1,3 @@
+---
+ceph_monitors: ['ceph-003*', 'ceph-004*', 'ceph-005*']
+ceph_osd_probe_node: ['ceph-001*']
diff --git a/cvp_checks/tests/ceph/test_ceph.py b/cvp_checks/tests/ceph/test_ceph.py
new file mode 100644
index 0000000..05a61c2
--- /dev/null
+++ b/cvp_checks/tests/ceph/test_ceph.py
@@ -0,0 +1,11 @@
+from cvp_checks import utils
+
+
+def test_check_ceph_osd(local_salt_client):
+ config = utils.get_configuration(__file__)
+ osd_fail = \
+ local_salt_client.cmd(config["ceph_osd_probe_node"][0], 'cmd.run',
+ ['ceph osd tree | grep down'])
+ assert not osd_fail.values()[0], \
+ "Some osds are in down state or ceph is not found".format(
+ osd_fail.values()[0])
diff --git a/cvp_checks/tests/ceph/test_ceph_haproxy.py b/cvp_checks/tests/ceph/test_ceph_haproxy.py
new file mode 100644
index 0000000..2f2e1e0
--- /dev/null
+++ b/cvp_checks/tests/ceph/test_ceph_haproxy.py
@@ -0,0 +1,21 @@
+from cvp_checks import utils
+
+
+def test_ceph_haproxy(local_salt_client):
+ config = utils.get_configuration(__file__)
+
+ fail = {}
+
+ for monitor in config["ceph_monitors"]:
+ monitor_info = local_salt_client.cmd(monitor, 'cmd.run',
+ ["echo 'show stat' | nc -U "
+ "/var/run/haproxy/admin.sock | "
+ "grep ceph_mon_radosgw_cluster"])
+
+ for name, info in monitor_info.iteritems():
+ if "OPEN" and "UP" in info:
+ continue
+ else:
+ fail[name] = info
+
+ assert not fail, "Failed monitors: {}".format(fail)
diff --git a/cvp_checks/tests/conftest.py b/cvp_checks/tests/conftest.py
new file mode 100644
index 0000000..21af490
--- /dev/null
+++ b/cvp_checks/tests/conftest.py
@@ -0,0 +1 @@
+from cvp_checks.fixtures.base import *
diff --git a/cvp_checks/tests/test_contrail.py b/cvp_checks/tests/test_contrail.py
new file mode 100644
index 0000000..16c3389
--- /dev/null
+++ b/cvp_checks/tests/test_contrail.py
@@ -0,0 +1,78 @@
+import pytest
+import json
+
+def test_contrail_compute_status(local_salt_client):
+ cs = local_salt_client.cmd(
+ 'nova:compute', 'cmd.run',
+ ['contrail-status | grep -Pv \'(==|^$)\''],
+ expr_form='pillar'
+ )
+ # TODO: what if compute lacks these service unintentionally?
+ if not cs:
+ pytest.skip("Contrail services were not found on compute nodes")
+ broken_services = []
+
+ for node in cs:
+ for line in cs[node].split('\n'):
+ line = line.strip()
+ name, status = line.split(None, 1)
+ if status not in {'active'}:
+ err_msg = "{node}:{service} - {status}".format(
+ node=node, service=name, status=status)
+ broken_services.append(err_msg)
+
+ assert not broken_services, 'Broken services: {}'.format(json.dumps(
+ broken_services,
+ indent=4))
+
+
+def test_contrail_node_status(local_salt_client):
+ cs = local_salt_client.cmd(
+ 'opencontrail:client:analytics_node', 'cmd.run',
+ ['contrail-status | grep -Pv \'(==|^$|Disk|unix|support)\''],
+ expr_form='pillar'
+ )
+ cs.update(local_salt_client.cmd(
+ 'opencontrail:control', 'cmd.run',
+ ['contrail-status | grep -Pv \'(==|^$|Disk|unix|support)\''],
+ expr_form='pillar')
+ )
+ if not cs:
+ pytest.skip("Contrail nodes were not found on compute nodes")
+ broken_services = []
+ for node in cs:
+ for line in cs[node].split('\n'):
+ line = line.strip()
+ if 'crashes/core.java.' not in line:
+ name, status = line.split(None, 1)
+ else:
+ name, status = line,'FATAL'
+ if status not in {'active', 'backup'}:
+ err_msg = "{node}:{service} - {status}".format(
+ node=node, service=name, status=status)
+ broken_services.append(err_msg)
+
+ assert not broken_services, 'Broken services: {}'.format(json.dumps(
+ broken_services,
+ indent=4))
+
+
+def test_contrail_vrouter_count(local_salt_client):
+ cs = local_salt_client.cmd(
+ 'nova:compute', 'cmd.run', ['contrail-status | grep -Pv \'(==|^$)\''],
+ expr_form='pillar'
+ )
+ # TODO: what if compute lacks these service unintentionally?
+ if not cs:
+ pytest.skip("Contrail services were not found on compute nodes")
+
+ actual_vrouter_count = 0
+ for node in cs:
+ for line in cs[node].split('\n'):
+ if 'contrail-vrouter-nodemgr' in line:
+ actual_vrouter_count += 1
+
+ assert actual_vrouter_count == len(cs.keys()),\
+ 'The length of vRouters {} differs' \
+ ' from the length of compute nodes {}'.format(actual_vrouter_count,
+ len(cs.keys()))
diff --git a/cvp_checks/tests/test_default_gateway.py b/cvp_checks/tests/test_default_gateway.py
new file mode 100644
index 0000000..57a6d7b
--- /dev/null
+++ b/cvp_checks/tests/test_default_gateway.py
@@ -0,0 +1,29 @@
+import json
+import pytest
+
+from cvp_checks import utils
+
+
+@pytest.mark.parametrize(
+ "group",
+ utils.get_groups(utils.get_configuration(__file__))
+)
+def test_check_default_gateways(local_salt_client, group):
+ config = utils.get_configuration(__file__)
+ netstat_info = local_salt_client.cmd(
+ group, 'cmd.run', ['ip r | sed -n 1p'], expr_form='pcre')
+
+ gateways = {}
+ nodes = netstat_info.keys()
+
+ for node in nodes:
+ if netstat_info[node] not in gateways:
+ gateways[netstat_info[node]] = [node]
+ else:
+ gateways[netstat_info[node]].append(node)
+
+ assert len(gateways.keys()) == 1, \
+ "There were found few gateways within group {group}: {gw}".format(
+ group=group,
+ gw=json.dumps(gateways, indent=4)
+ )
diff --git a/cvp_checks/tests/test_galera_cluster.py b/cvp_checks/tests/test_galera_cluster.py
new file mode 100644
index 0000000..d2b2d5f
--- /dev/null
+++ b/cvp_checks/tests/test_galera_cluster.py
@@ -0,0 +1,17 @@
+def test_galera_cluster_status(local_salt_client):
+ gs = local_salt_client.cmd(
+ 'I@galera:*',
+ 'cmd.run',
+ ['salt-call mysql.status | grep -A1 wsrep_cluster_size | tail -n1'],
+ expr_form='pillar')
+
+ size_cluster = []
+ amount = len(gs)
+
+ for item in gs.values():
+ size_cluster.append(item.split('\n')[-1].strip())
+
+ assert all(item == str(amount) for item in size_cluster), \
+ '''There found inconsistency within cloud. MySQL galera cluster
+ is probably broken, the cluster size gathered from nodes:
+ {}'''.format(gs)
diff --git a/cvp_checks/tests/test_mtu.py b/cvp_checks/tests/test_mtu.py
new file mode 100644
index 0000000..f3c3511
--- /dev/null
+++ b/cvp_checks/tests/test_mtu.py
@@ -0,0 +1,68 @@
+import pytest
+import json
+from cvp_checks import utils
+
+
+@pytest.mark.parametrize(
+ "group",
+ utils.get_groups(utils.get_configuration(__file__))
+)
+def test_mtu(local_salt_client, group):
+ config = utils.get_configuration(__file__)
+ skipped_ifaces = config["skipped_ifaces"]
+ total = {}
+ network_info = local_salt_client.cmd(
+ group, 'cmd.run', ['ls /sys/class/net/'], expr_form='pcre')
+
+ kvm_nodes = local_salt_client.cmd(
+ 'salt:control', 'test.ping', expr_form='pillar').keys()
+
+ if len(network_info.keys()) < 2:
+ pytest.skip("Nothing to compare - only 1 node")
+
+ for node, ifaces_info in network_info.iteritems():
+ if node in kvm_nodes:
+ kvm_info = local_salt_client.cmd(node, 'cmd.run',
+ ["virsh list | "
+ "awk '{print $2}' | "
+ "xargs -n1 virsh domiflist | "
+ "grep -v br-pxe | grep br- | "
+ "awk '{print $1}'"])
+ ifaces_info = kvm_info.get(node)
+ node_ifaces = ifaces_info.split('\n')
+ ifaces = {}
+ for iface in node_ifaces:
+ for skipped_iface in skipped_ifaces:
+ if skipped_iface in iface:
+ break
+ else:
+ iface_mtu = local_salt_client.cmd(node, 'cmd.run',
+ ['cat /sys/class/'
+ 'net/{}/mtu'.format(iface)])
+ ifaces[iface] = iface_mtu.get(node)
+ total[node] = ifaces
+
+ nodes = []
+ mtu_data = []
+ my_set = set()
+
+ for node in total:
+ nodes.append(node)
+ my_set.update(total[node].keys())
+ for interf in my_set:
+ diff = []
+ row = []
+ for node in nodes:
+ if interf in total[node].keys():
+ diff.append(total[node][interf])
+ row.append("{}: {}".format(node, total[node][interf]))
+ else:
+ row.append("{}: No interface".format(node))
+ if diff.count(diff[0]) < len(nodes):
+ row.sort()
+ row.insert(0, interf)
+ mtu_data.append(row)
+ assert len(mtu_data) == 0, \
+ "Several problems found for {0} group: {1}".format(
+ group, json.dumps(mtu_data, indent=4))
+
diff --git a/cvp_checks/tests/test_ntp_sync.py b/cvp_checks/tests/test_ntp_sync.py
new file mode 100644
index 0000000..72ca43e
--- /dev/null
+++ b/cvp_checks/tests/test_ntp_sync.py
@@ -0,0 +1,27 @@
+import pytest
+import os
+from cvp_checks import utils
+
+
+@pytest.mark.parametrize(
+ "node",
+ utils.get_active_nodes(utils.get_configuration(__file__))
+)
+def test_ntp_sync(local_salt_client, node):
+ config = utils.get_configuration(__file__)
+ fail = {}
+
+ saltmaster_time = int(local_salt_client.cmd(
+ os.uname()[1] + '*', 'cmd.run', ['date +%s']).values()[0])
+
+ nodes_time = local_salt_client.cmd(
+ node, 'cmd.run', ['date +%s'], expr_form='pcre')
+
+ for node, time in nodes_time.iteritems():
+ if (int(time) - saltmaster_time) > config["time_deviation"] or \
+ (int(time) - saltmaster_time) < -config["time_deviation"]:
+ fail[node] = time
+
+ assert not fail, 'SaltMaster time: {}\n' \
+ 'Nodes with time mismatch:\n {}'.format(saltmaster_time,
+ fail)
diff --git a/cvp_checks/tests/test_packet_checker.py b/cvp_checks/tests/test_packet_checker.py
new file mode 100644
index 0000000..79915ec
--- /dev/null
+++ b/cvp_checks/tests/test_packet_checker.py
@@ -0,0 +1,83 @@
+import pytest
+import json
+from cvp_checks import utils
+
+
+@pytest.mark.parametrize(
+ "group",
+ utils.get_groups(utils.get_configuration(__file__))
+)
+def test_check_package_versions(local_salt_client, group):
+ config = utils.get_configuration(__file__)
+
+ output = local_salt_client.cmd(group, 'lowpkg.list_pkgs', expr_form='pcre')
+
+ if len(output.keys()) < 2:
+ pytest.skip("Nothing to compare - only 1 node")
+
+ nodes = []
+ pkts_data = []
+ my_set = set()
+
+ for node in output:
+ nodes.append(node)
+ my_set.update(output[node].keys())
+
+ for deb in my_set:
+ diff = []
+ row = []
+ for node in nodes:
+ if deb in output[node].keys():
+ diff.append(output[node][deb])
+ row.append("{}: {}".format(node, output[node][deb]))
+ else:
+ row.append("{}: No package".format(node))
+ if diff.count(diff[0]) < len(nodes):
+ row.sort()
+ row.insert(0, deb)
+ pkts_data.append(row)
+ assert len(pkts_data) <= 1, \
+ "Several problems found for {0} group: {1}".format(
+ group, json.dumps(pkts_data, indent=4))
+
+
+@pytest.mark.parametrize(
+ "group",
+ utils.get_groups(utils.get_configuration(__file__))
+)
+def test_check_module_versions(local_salt_client, group):
+ config = utils.get_configuration(__file__)
+
+ pre_check = local_salt_client.cmd(
+ group, 'cmd.run', ['dpkg -l | grep "python-pip "'], expr_form='pcre')
+ if pre_check.values().count('') > 0:
+ pytest.skip("pip is not installed on one or more nodes")
+ if len(pre_check.keys()) < 2:
+ pytest.skip("Nothing to compare - only 1 node")
+ output = local_salt_client.cmd(group, 'pip.freeze', expr_form='pcre')
+
+ nodes = []
+ pkts_data = []
+ my_set = set()
+
+ for node in output:
+ nodes.append(node)
+ my_set.update([x.split("=")[0] for x in output[node]])
+ output[node] = dict([x.split("==") for x in output[node]])
+
+ for deb in my_set:
+ diff = []
+ row = []
+ for node in nodes:
+ if deb in output[node].keys():
+ diff.append(output[node][deb])
+ row.append("{}: {}".format(node, output[node][deb]))
+ else:
+ row.append("{}: No module".format(node))
+ if diff.count(diff[0]) < len(nodes):
+ row.sort()
+ row.insert(0, deb)
+ pkts_data.append(row)
+ assert len(pkts_data) <= 1, \
+ "Several problems found for {0} group: {1}".format(
+ group, json.dumps(pkts_data, indent=4))
diff --git a/cvp_checks/tests/test_rabbit_cluster.py b/cvp_checks/tests/test_rabbit_cluster.py
new file mode 100644
index 0000000..94de975
--- /dev/null
+++ b/cvp_checks/tests/test_rabbit_cluster.py
@@ -0,0 +1,43 @@
+import re
+
+
+def test_checking_rabbitmq_cluster(local_salt_client):
+ # disable config for this test
+ # it may be reintroduced in future
+ # config = utils.get_configuration(__file__)
+ # request pillar data from rmq nodes
+ rabbitmq_pillar_data = local_salt_client.cmd(
+ 'rabbitmq:server', 'pillar.data',
+ ['rabbitmq:cluster'], expr_form='pillar')
+
+ # creating dictionary {node:cluster_size_for_the_node}
+ # with required cluster size for each node
+ control_dict = {}
+ required_cluster_size_dict = {}
+ for node in rabbitmq_pillar_data:
+ cluster_size_from_the_node = len(
+ rabbitmq_pillar_data[node]['rabbitmq:cluster']['members'])
+ required_cluster_size_dict.update({node: cluster_size_from_the_node})
+
+ # request actual data from rmq nodes
+ rabbit_actual_data = local_salt_client.cmd(
+ 'rabbitmq:server', 'cmd.run',
+ ['rabbitmqctl cluster_status'], expr_form='pillar')
+
+ # find actual cluster size for each node
+ for node in rabbit_actual_data:
+ running_nodes_count = 0
+ for line in rabbit_actual_data[node].split('\n'):
+ if 'running_nodes' in line:
+ running_nodes_count = line.count('rabbit@')
+ # update control dictionary with values
+ # {node:actual_cluster_size_for_node}
+ if required_cluster_size_dict[node] != running_nodes_count:
+ control_dict.update({node: running_nodes_count})
+
+ assert not len(control_dict), "Inconsistency found within cloud. " \
+ "RabbitMQ cluster is probably broken, " \
+ "the cluster size for each node " \
+ "should be: {} but the following " \
+ "nodes has other values: {}".format(
+ len(required_cluster_size_dict.keys()), control_dict)
diff --git a/cvp_checks/tests/test_repo_list.py b/cvp_checks/tests/test_repo_list.py
new file mode 100644
index 0000000..6df6027
--- /dev/null
+++ b/cvp_checks/tests/test_repo_list.py
@@ -0,0 +1,43 @@
+import pytest
+from cvp_checks import utils
+
+
+@pytest.mark.parametrize(
+ "group",
+ utils.get_groups(utils.get_configuration(__file__))
+)
+def test_list_of_repo_on_nodes(local_salt_client, group):
+ info_salt = local_salt_client.cmd(
+ group, 'pillar.data', ['linux:system:repo'], expr_form='pcre')
+
+ raw_actual_info = local_salt_client.cmd(
+ group,
+ 'cmd.run',
+ ['cat /etc/apt/sources.list.d/*;'
+ 'cat /etc/apt/sources.list|grep deb|grep -v "#"'],
+ expr_form='pcre')
+ actual_repo_list = [item.replace('/ ', ' ')
+ for item in raw_actual_info.values()[0].split('\n')]
+ expected_salt_data = [repo['source'].replace('/ ', ' ')
+ for repo in info_salt.values()[0]
+ ['linux:system:repo'].values()]
+
+ diff = {}
+ my_set = set()
+
+ my_set.update(actual_repo_list)
+ my_set.update(expected_salt_data)
+ import json
+ for repo in my_set:
+ rows = []
+ if repo not in actual_repo_list:
+ rows.append("{}: {}".format("pillars", "+"))
+ rows.append("{}: No repo".format('config'))
+ diff[repo] = rows
+ elif repo not in expected_salt_data:
+ rows.append("{}: {}".format("config", "+"))
+ rows.append("{}: No repo".format('pillars'))
+ diff[repo] = rows
+ assert len(diff) <= 1, \
+ "Several problems found for {0} group: {1}".format(
+ group, json.dumps(diff, indent=4))
diff --git a/cvp_checks/tests/test_services.py b/cvp_checks/tests/test_services.py
new file mode 100644
index 0000000..3d90218
--- /dev/null
+++ b/cvp_checks/tests/test_services.py
@@ -0,0 +1,41 @@
+import pytest
+import json
+from cvp_checks import utils
+
+
+@pytest.mark.parametrize(
+ "group",
+ utils.get_groups(utils.get_configuration(__file__))
+)
+def test_check_services(local_salt_client, group):
+ config = utils.get_configuration(__file__)
+
+ output = local_salt_client.cmd(group, 'service.get_all', expr_form='pcre')
+
+ if len(output.keys()) < 2:
+ pytest.skip("Nothing to compare - only 1 node")
+
+ nodes = []
+ pkts_data = []
+ my_set = set()
+
+ for node in output:
+ nodes.append(node)
+ my_set.update(output[node])
+
+ for srv in my_set:
+ diff = []
+ row = []
+ for node in nodes:
+ if srv in output[node]:
+ diff.append(srv)
+ row.append("{}: +".format(node))
+ else:
+ row.append("{}: No service".format(node))
+ if diff.count(diff[0]) < len(nodes):
+ row.sort()
+ row.insert(0, srv)
+ pkts_data.append(row)
+ assert len(pkts_data) <= 1, \
+ "Several problems found for {0} group: {1}".format(
+ group, json.dumps(pkts_data, indent=4))
diff --git a/cvp_checks/tests/test_single_vip.py b/cvp_checks/tests/test_single_vip.py
new file mode 100644
index 0000000..9e0f16e
--- /dev/null
+++ b/cvp_checks/tests/test_single_vip.py
@@ -0,0 +1,27 @@
+import pytest
+from cvp_checks import utils
+from collections import Counter
+
+
+@pytest.mark.parametrize(
+ "group",
+ utils.get_groups(utils.get_configuration(__file__))
+)
+def test_single_vip(local_salt_client, group):
+ local_salt_client.cmd(group, 'saltutil.sync_all', expr_form='pcre')
+ nodes_list = local_salt_client.cmd(
+ group, 'grains.item', ['ipv4'], expr_form='pcre')
+
+ ipv4_list = []
+
+ for node in nodes_list:
+ ipv4_list.extend(nodes_list.get(node).get('ipv4'))
+
+ cnt = Counter(ipv4_list)
+
+ for ip in cnt:
+ if ip == '127.0.0.1':
+ continue
+ elif cnt[ip] > 1:
+ assert "VIP IP duplicate found " \
+ "in group {}\n{}".format(group, ipv4_list)
diff --git a/cvp_checks/utils/__init__.py b/cvp_checks/utils/__init__.py
new file mode 100644
index 0000000..9762e8e
--- /dev/null
+++ b/cvp_checks/utils/__init__.py
@@ -0,0 +1,119 @@
+import os
+import yaml
+import requests
+import re
+
+
+class salt_remote:
+ def cmd(self, tgt, fun, param=None,expr_form=None):
+ config = get_configuration(__file__)
+ for salt_cred in ['SALT_USERNAME', 'SALT_PASSWORD', 'SALT_URL']:
+ if os.environ.get(salt_cred):
+ config[salt_cred] = os.environ[salt_cred]
+ headers = {'Accept':'application/json'}
+ login_payload = {'username':config['SALT_USERNAME'],'password':config['SALT_PASSWORD'],'eauth':'pam'}
+ accept_key_payload = {'fun': fun,'tgt':tgt,'client':'local','expr_form':expr_form}
+ if param:
+ accept_key_payload['arg']=param
+
+ login_request = requests.post(os.path.join(config['SALT_URL'],'login'),headers=headers,data=login_payload)
+ request = requests.post(config['SALT_URL'],headers=headers,data=accept_key_payload,cookies=login_request.cookies)
+ return request.json()['return'][0]
+
+
+def init_salt_client():
+ local = salt_remote()
+ return local
+
+
+def get_active_nodes(config):
+ local_salt_client = init_salt_client()
+
+ skipped_nodes = config.get('skipped_nodes') or []
+ # TODO add skipped nodes to cmd command instead of filtering
+ nodes = local_salt_client.cmd('*', 'test.ping')
+ active_nodes = [
+ node_name for node_name in nodes
+ if nodes[node_name] and node_name not in skipped_nodes
+ ]
+ return active_nodes
+
+
+def get_groups(config):
+ # assume that node name is like <name>.domain
+ # last 1-3 digits of name are index, e.g. 001 in cpu001
+ # name doesn't contain dots
+ active_nodes = get_active_nodes(config)
+ skipped_group = config.get('skipped_group') or []
+ groups = []
+
+ for node in active_nodes:
+ index = re.search('[0-9]{1,3}$', node.split('.')[0])
+ if index:
+ group_name = node.split('.')[0][:-len(index.group(0))]
+ else:
+ group_name = node
+ if group_name not in skipped_group and group_name not in groups:
+ groups.append(group_name)
+ test_groups = []
+ groups_from_config = config.get('groups')
+ # check if config.yaml contains `groups` key
+ if groups_from_config is not None:
+ invalid_groups = []
+ for group in groups_from_config:
+ # check if group name from config
+ # is substring of one of the groups
+ grp = [x for x in groups if group in x]
+ if grp:
+ test_groups.append(grp[0])
+ else:
+ invalid_groups.append(group)
+ if invalid_groups:
+ raise ValueError('Config file contains'
+ ' invalid groups name: {}'.format(invalid_groups))
+
+ groups = test_groups if test_groups else groups
+
+ # For splitting Ceph nodes
+ local_salt_client = init_salt_client()
+
+ if "ceph*" in groups:
+ groups.remove("ceph*")
+
+ ceph_status = local_salt_client.cmd(
+ 'ceph*', "cmd.run", ["ps aux | grep ceph-mon | grep -v grep"])
+
+ mon = []
+ ceph = []
+ for node in ceph_status:
+ if ceph_status[node] != '':
+ mon.append(node.split('.')[0])
+ else:
+ ceph.append(node.split('.')[0])
+
+ mon_regex = "({0}.*)".format(".*|".join(mon))
+ groups.append(mon_regex)
+
+ ceph_regex = "({0}.*)".format(".*|".join(ceph))
+ groups.append(ceph_regex)
+
+ return groups
+
+
+def get_configuration(path_to_test):
+ """function returns configuration for environment
+
+ and for test if it's specified"""
+ global_config_file = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)), "../global_config.yaml")
+ with open(global_config_file, 'r') as file:
+ global_config = yaml.load(file)
+
+ config_file = os.path.join(
+ os.path.dirname(os.path.abspath(path_to_test)), "config.yaml")
+
+ if os.path.exists(config_file):
+ with open(config_file, 'r') as file:
+ global_config.update(yaml.load(file))
+
+ return global_config
diff --git a/cvp_checks/utils/__init__.pyc b/cvp_checks/utils/__init__.pyc
new file mode 100644
index 0000000..1a1feed
--- /dev/null
+++ b/cvp_checks/utils/__init__.pyc
Binary files differ
diff --git a/docs/__init__.py b/docs/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/docs/__init__.py
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..396c0b8
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+pytest==3.0.6
+flake8
+PyYAML
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..8d8f66d
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+
+from setuptools import setup, find_packages
+
+
+with open('README.md') as f:
+ readme = f.read()
+
+with open('LICENSE') as f:
+ license = f.read()
+
+setup(
+ name='cvp-sanity',
+ version='0.1',
+ description='set of tests for MCP verification',
+ long_description=readme,
+ author='Mirantis',
+ license=license,
+ packages=find_packages(exclude=('tests', 'docs'))
+)