Skip to content
This repository has been archived by the owner on Oct 22, 2019. It is now read-only.

Changes to enable ST tests of golang calicoctl #163

Merged
merged 25 commits into from
Nov 4, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
901cbed
Code tidy up
lwr20 Oct 14, 2016
fca7c45
Add flag to calicoctl to choose python or Go calicoctl
lwr20 Oct 5, 2016
10341a9
Add assert_same function
lwr20 Oct 6, 2016
37fdc77
Updated docstring
lwr20 Oct 7, 2016
76ffb59
Change default calicoctl name to avoid breaking libnetwork-plugins tests
lwr20 Oct 12, 2016
c06e723
Logging and PEP8 improvements
lwr20 Oct 12, 2016
7a6e382
Add etcdctl to test container
lwr20 Oct 12, 2016
130589e
Move dictionary comparison to test_calicoctl.py
lwr20 Oct 12, 2016
450e36c
symlink etcdctl in same image layer as download
lwr20 Oct 14, 2016
a7a671a
Use calicoctl-go where possible
lwr20 Oct 14, 2016
c124d5f
Remove stray merge error
lwr20 Oct 14, 2016
69be47a
Remove mutable kwarg
lwr20 Oct 14, 2016
36b3d77
Add useful functions to test_base
lwr20 Oct 14, 2016
4eec3fa
Add method to write files onto hosts
lwr20 Oct 18, 2016
ef53a24
Load route reflector from a tgz file
lwr20 Oct 18, 2016
b99d401
Make tcp and udp checks work, add tests using them
lwr20 Oct 21, 2016
397108e
Changed hostname to node to match data model
lwr20 Nov 1, 2016
1b83a88
Write files out to debug logs
lwr20 Nov 4, 2016
0a94e29
New calicoctl supports running calico-node, no need for the old
lwr20 Nov 4, 2016
d0aa561
Review markups
lwr20 Nov 4, 2016
8e9cf44
Use golang calicoctl for everything
lwr20 Nov 4, 2016
4eb9cdc
Use tar images
lwr20 Nov 4, 2016
57961e0
Use tar images
lwr20 Nov 4, 2016
cfa0979
Always use golang calicoctl
lwr20 Nov 4, 2016
e5945c8
We now always use golang calicoctl
lwr20 Nov 4, 2016
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
15 changes: 10 additions & 5 deletions Dockerfile.calico_test
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
# running of the ST frameworks. Additionally, this allows for sharing of
# common ST framework code (which calico-containers and libnetwork both use).
# To run:
# - volume mount the docker socket, allowing the STs to launch docker
# - volume mount the docker socket, allowing the STs to launch docker
# containers alongside itself.
# - eliminate most isolation, (--uts=host --pid=host --net=host --privileged)
# - volume mount your ST source code
Expand All @@ -37,10 +37,10 @@ FROM docker
MAINTAINER Tom Denham <[email protected]>

# Running STs in this containers require that it has all dependencies installed
# for executing calicoctl. Install these dependencies (including glibc:
# for executing calicoctl. Install these dependencies (including glibc:
# https://github.com/jeanblanchard/docker-alpine-glibc/blob/master/Dockerfile)
# We install glibc onto the official docker image (instead of adding docker to
# the libc image) since glibc installs are more constant than the
# We install glibc onto the official docker image (instead of adding docker to
# the libc image) since glibc installs are more constant than the
# docker-in-docker installation and configuration.
RUN apk add --update python python-dev py-pip py-setuptools \
git musl-dev gcc \
Expand All @@ -62,6 +62,11 @@ RUN pip install -r /tmp/pycalico/calico_test/requirements.txt
# Add the testing framework
ADD calico_test/tests tests

# Install etcdctl
RUN wget https://github.com/coreos/etcd/releases/download/v2.3.3/etcd-v2.3.3-linux-amd64.tar.gz && \
tar -xzf etcd-v2.3.3-linux-amd64.tar.gz && \
cd etcd-v2.3.3-linux-amd64 && \
ln -s etcdctl /usr/local/bin/

# The container is used by mounting the code-under-test to /code
WORKDIR /code/

1 change: 1 addition & 0 deletions calico_test/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ monotime
ConcurrentLogHandler
pyyaml
simplejson
deepdiff
107 changes: 97 additions & 10 deletions calico_test/tests/st/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import yaml
import logging
import subprocess
from pprint import pformat
from unittest import TestCase

from deepdiff import DeepDiff

from tests.st.utils.utils import (get_ip, ETCD_SCHEME, ETCD_CA, ETCD_CERT,
ETCD_KEY, debug_failures, ETCD_HOSTNAME_SSL)
import logging

HOST_IPV6 = get_ip(v6=True)
HOST_IPV4 = get_ip()
Expand All @@ -34,6 +38,7 @@ class TestBase(TestCase):
"""
Base class for test-wide methods.
"""

def setUp(self):
"""
Clean up before every test.
Expand All @@ -46,13 +51,15 @@ def setUp(self):

# Disable Usage Reporting to usage.projectcalico.org
# We want to avoid polluting analytics data with unit test noise
self.curl_etcd("calico/v1/config/UsageReportingEnabled", options=["-XPUT -d value=False"])
self.curl_etcd("calico/v1/config/UsageReportingEnabled",
options=["-XPUT -d value=False"])

# Log a newline to ensure that the first log appears on its own line.
logger.info("")

@debug_failures
def assert_connectivity(self, pass_list, fail_list=None, retries=0):
def assert_connectivity(self, pass_list, fail_list=None, retries=0,
type_list=None):
"""
Assert partial connectivity graphs between workloads.

Expand All @@ -62,18 +69,33 @@ def assert_connectivity(self, pass_list, fail_list=None, retries=0):
ping each workload in this list. Interconnectivity is not checked
*within* the fail_list.
:param retries: The number of retries.
:param type_list: list of types to test. If not specified, defaults to
icmp only.
"""
if type_list is None:
type_list = ['icmp', 'tcp', 'udp']
if fail_list is None:
fail_list = []

for source in pass_list:
for dest in pass_list:
source.assert_can_ping(dest.ip, retries)
if 'icmp' in type_list:
source.assert_can_ping(dest.ip, retries)
if 'tcp' in type_list:
source.assert_can_tcp(dest.ip, retries)
if 'udp' in type_list:
source.assert_can_udp(dest.ip, retries)
for dest in fail_list:
source.assert_cant_ping(dest.ip, retries)
if 'icmp' in type_list:
source.assert_cant_ping(dest.ip, retries)
if 'tcp' in type_list:
source.assert_cant_tcp(dest.ip, retries)
if 'udp' in type_list:
source.assert_cant_udp(dest.ip, retries)

@debug_failures
def assert_ip_connectivity(self, workload_list, ip_pass_list,
ip_fail_list=None):
ip_fail_list=None, type_list=None):
"""
Assert partial connectivity graphs between workloads and given ips.

Expand All @@ -86,23 +108,40 @@ def assert_ip_connectivity(self, workload_list, ip_pass_list,
:param ip_fail_list: Every workload in workload_list should *not* be
able to ping any ip in this list. Interconnectivity is not checked
*within* the fail_list.
:param type_list: list of types to test. If not specified, defaults to
icmp only.
"""
if type_list is None:
type_list = ['icmp']
if ip_fail_list is None:
ip_fail_list = []
for workload in workload_list:
for ip in ip_pass_list:
workload.assert_can_ping(ip)
for ip in ip_fail_list:
workload.assert_cant_ping(ip)
if 'icmp' in type_list:
workload.assert_can_ping(ip)
if 'tcp' in type_list:
workload.assert_can_tcp(ip)
if 'udp' in type_list:
workload.assert_can_udp(ip)

def curl_etcd(self, path, options=[], recursive=True):
for ip in ip_fail_list:
if 'icmp' in type_list:
workload.assert_cant_ping(ip)
if 'tcp' in type_list:
workload.assert_cant_tcp(ip)
if 'udp' in type_list:
workload.assert_cant_udp(ip)

def curl_etcd(self, path, options=None, recursive=True):
"""
Perform a curl to etcd, returning JSON decoded response.
:param path: The key path to query
:param options: Additional options to include in the curl
:param recursive: Whether we want recursive query or not
:return: The JSON decoded response.
"""
if options is None:
options = []
if ETCD_SCHEME == "https":
# Etcd is running with SSL/TLS, require key/certificates
rc = subprocess.check_output(
Expand All @@ -118,3 +157,51 @@ def curl_etcd(self, path, options=[], recursive=True):
shell=True)

return json.loads(rc.strip())

def check_data_in_datastore(self, host, data, resource, yaml_format=True):
if yaml_format:
out = host.calicoctl(
"get %s --output=yaml" % resource)
output = yaml.safe_load(out)
else:
out = host.calicoctl(
"get %s --output=json" % resource)
output = json.loads(out)
self.assert_same(data, output)

@staticmethod
def assert_same(thing1, thing2):
"""
Compares two things. Debug logs the differences between them before
asserting that they are the same.
"""
assert cmp(thing1, thing2) == 0, \
"Items are not the same. Difference is:\n %s" % \
pformat(DeepDiff(thing1, thing2), indent=2)

@staticmethod
def writeyaml(filename, data):
Copy link
Contributor

Choose a reason for hiding this comment

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

Quick doc string here and below would be super

"""
Converts a python dict to yaml and outputs to a file.
:param filename: filename to write
:param data: dictionary to write out as yaml
"""
with open(filename, 'w') as f:
text = yaml.dump(data, default_flow_style=False)
logger.debug("Writing %s: \n%s" % (filename, text))
f.write(text)

@staticmethod
def writejson(filename, data):
"""
Converts a python dict to json and outputs to a file.
:param filename: filename to write
:param data: dictionary to write out as json
"""
with open(filename, 'w') as f:
text = json.dumps(data,
sort_keys=True,
indent=2,
separators=(',', ': '))
logger.debug("Writing %s: \n%s" % (filename, text))
f.write(text)
69 changes: 44 additions & 25 deletions calico_test/tests/st/utils/docker_host.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,33 @@
from functools import partial

from utils import get_ip, log_and_run, retry_until_success, ETCD_SCHEME, \
ETCD_CA, ETCD_KEY, ETCD_CERT, ETCD_HOSTNAME_SSL
ETCD_CA, ETCD_KEY, ETCD_CERT, ETCD_HOSTNAME_SSL
from workload import Workload
from network import DockerNetwork

logger = logging.getLogger(__name__)
CHECKOUT_DIR = os.getenv("HOST_CHECKOUT_DIR", os.getcwd())
# We want to default CHECKOUT_DIR if either the ENV var is unset
# OR its set to an empty string.
CHECKOUT_DIR = os.getenv("HOST_CHECKOUT_DIR", "")
if CHECKOUT_DIR == "":
CHECKOUT_DIR = os.getcwd()


class DockerHost(object):
"""
A host container which will hold workload containers to be networked by
Calico.

:param calico_node_autodetect_ip: When set to True, the test framework
will not perform IP detection, and will run `calicoctl node` without explicitly
passing in a value for --ip. This means calico-node will be forced to do its IP detection.
:param override_hostname: When set to True, the test framework will choose an alternate
hostname for the host which it will pass to all calicoctl components as the HOSTNAME
environment variable. If set to False, the HOSTNAME environment is not explicitly set.
will not perform IP detection, and will run `calicoctl node` without
explicitly passing in a value for --ip. This means calico-node will be
forced to do its IP detection.
:param override_hostname: When set to True, the test framework will
choose an alternate hostname for the host which it will pass to all
calicoctl components as the HOSTNAME environment variable. If set
to False, the HOSTNAME environment is not explicitly set.
"""

def __init__(self, name, start_calico=True, dind=True,
additional_docker_options="",
post_docker_commands=["docker load -i /code/calico-node.tar",
Expand All @@ -48,16 +56,18 @@ def __init__(self, name, start_calico=True, dind=True,
self.workloads = set()
self.ip = None
"""
An IP address value to pass to calicoctl as `--ip`. If left as None, no value will be passed,
forcing calicoctl to do auto-detection.
An IP address value to pass to calicoctl as `--ip`. If left as None,
no value will be passed, forcing calicoctl to do auto-detection.
"""

self.ip6 = None
"""
An IPv6 address value to pass to calicoctl as `--ipv6`. If left as None, no value will be passed.
An IPv6 address value to pass to calicoctl as `--ipv6`. If left as
None, no value will be passed.
"""

self.override_hostname = None if not override_hostname else uuid.uuid1().hex[:16]
self.override_hostname = None if not override_hostname else \
uuid.uuid1().hex[:16]
"""
Create an arbitrary hostname if we want to override.
"""
Expand All @@ -78,16 +88,19 @@ def __init__(self, name, start_calico=True, dind=True,
log_and_run("docker rm -f %s || true" % self.name)
# Pass the certs directory as a volume since the etcd SSL/TLS
# environment variables use the full path on the host.
# Set iptables=false to prevent iptables error when using dind libnetwork
# Set iptables=false to prevent iptables error when using dind
# libnetwork
log_and_run("docker run %s "
"calico/dind:latest "
" --storage-driver=aufs "
"--iptables=false "
"%s" %
(docker_args, additional_docker_options))
(docker_args, additional_docker_options))

self.ip = log_and_run("docker inspect --format "
"'{{.NetworkSettings.Networks.bridge.IPAddress}}' %s" % self.name)
self.ip = log_and_run(
"docker inspect --format "
"'{{.NetworkSettings.Networks.bridge.IPAddress}}' %s" %
self.name)

# Make sure docker is up
docker_ps = partial(self.execute, "docker ps")
Expand All @@ -96,7 +109,8 @@ def __init__(self, name, start_calico=True, dind=True,
for command in post_docker_commands:
self.execute(command)
elif not calico_node_autodetect_ip:
# Find the IP so it can be specified as `--ip` when launching node later.
# Find the IP so it can be specified as `--ip` when launching
# node later.
self.ip = get_ip(v6=False)
self.ip6 = get_ip(v6=True)

Expand All @@ -121,7 +135,6 @@ def execute(self, command):

return log_and_run(command)


def calicoctl(self, command):
"""
Convenience function for abstracting away calling the calicoctl
Expand Down Expand Up @@ -151,21 +164,20 @@ def calicoctl(self, command):
"export ETCD_KEY_FILE=%s; %s" % \
(etcd_auth, ETCD_SCHEME, ETCD_CA, ETCD_CERT, ETCD_KEY,
calicoctl)
# If the hostname is being overriden, then export the HOSTNAME environment.
# If the hostname is being overriden, then export the HOSTNAME
# environment.
if self.override_hostname:
calicoctl = "export HOSTNAME=%s; %s" % (self.override_hostname, calicoctl)
calicoctl = "export HOSTNAME=%s; %s" % (
self.override_hostname, calicoctl)

return self.execute(calicoctl + " " + command)

def start_calico_node(self, options=""):
"""
Start calico in a container inside a host by calling through to the
calicoctl node command.

:param as_num: The AS Number for this node. A value of None uses the
inherited default value.
"""
args = ['node']
args = ['node', 'run']
if self.ip:
args.append('--ip=%s' % self.ip)
if self.ip6:
Expand Down Expand Up @@ -210,7 +222,6 @@ def start_calico_node_with_docker(self):
self.ip,
etcd_auth, ETCD_SCHEME, ssl_args))


def remove_workloads(self):
"""
Remove all containers running on this host.
Expand Down Expand Up @@ -354,7 +365,6 @@ def get_hostname(self):
Note, this function only works with a host with dind enabled.
Raises an exception if dind is not enabled.

:param host: DockerHost object
:return: hostname of DockerHost
"""
# If overriding the hostname, return that one.
Expand All @@ -363,3 +373,12 @@ def get_hostname(self):

command = "docker inspect --format {{.Config.Hostname}} %s" % self.name
return log_and_run(command)

def writefile(self, filename, data):
"""
Writes a file on a host (e.g. a yaml file for loading into calicoctl).
:param filename: string, the filename to create
:param data: string, the data to put inthe file
:return: Return code of execute operation.
"""
return self.execute("cat << EOF > %s\n%s" % (filename, data))
Copy link
Member

Choose a reason for hiding this comment

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

I think you need another EOF at the end of this string to terminate, yeah?

Copy link
Member

Choose a reason for hiding this comment

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

It's weird, but it seems to work. Maybe docker adds the EOF or something. Since we're rushed I think it's fine as is.

Loading