files: # Copyright 2015 Tigera, Inc
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  import copy
  import netaddr
  import yaml
  from nose_parameterized import parameterized
  
  from tests.st.test_base import TestBase
  from tests.st.utils.docker_host import DockerHost, CLUSTER_STORE_DOCKER_OPTIONS
  from tests.st.utils.exceptions import CommandExecError
  from tests.st.utils.utils import assert_network, assert_profile, \
      assert_number_endpoints, get_profile_name
  
  POST_DOCKER_COMMANDS = ["docker load -i /code/calico-node.tar",
                          "docker load -i /code/busybox.tar",
                          "docker load -i /code/workload.tar"]
  
  
  class MultiHostMainline(TestBase):
      @parameterized.expand([
          #"tags",
          ("rules.tags", False),
          ("rules.tags", True),
          #"rules.protocol.icmp",
          #"rules.ip.addr",
          #"rules.ip.net",
          #"rules.selector",
          #"rules.tcp.port",
          #"rules.udp.port",
      ])
      def test_multi_host(self, test_type, simulate_gce_routing):
          """
          Run a mainline multi-host test.
          Because multihost tests are slow to setup, this tests most mainline
          functionality in a single test.
          - Create two hosts
          - Create a network using the default IPAM driver, and a workload on
            each host assigned to that network.
          - Create a network using the Calico IPAM driver, and a workload on
            each host assigned to that network.
          - Check that hosts on the same network can ping each other.
          - Check that hosts on different networks cannot ping each other.
          - Modify the profile rules
          - Check that connectivity has changed to match the profile we set up
          - Re-apply the original profile
          - Check that connectivity goes back to what it was originally.
          """
          with DockerHost("host1",
                          additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS,
                          post_docker_commands=POST_DOCKER_COMMANDS,
                          simulate_gce_routing=simulate_gce_routing,
                          start_calico=False) as host1, \
                  DockerHost("host2",
                             additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS,
                             post_docker_commands=POST_DOCKER_COMMANDS,
                             simulate_gce_routing=simulate_gce_routing,
                             start_calico=False) as host2:
              (n1_workloads, n2_workloads, networks) = \
                  self._setup_workloads(host1, host2, simulate_gce_routing)
  
              # Get the original profiles:
              output = host1.calicoctl("get profile -o yaml")
              original_profiles = yaml.safe_load(output)
              # Make a copy of the profiles to mess about with.
              new_profiles = copy.deepcopy(original_profiles)
  
              if test_type == "tags":
                  profile0_tag = new_profiles[0]['metadata']['tags'][0]
                  profile1_tag = new_profiles[1]['metadata']['tags'][0]
                  # Make a new profiles dict where the two networks have each
                  # other in their tags list
                  new_profiles[0]['metadata']['tags'].append(profile1_tag)
                  new_profiles[1]['metadata']['tags'].append(profile0_tag)
  
                  self._apply_new_profile(new_profiles, host1)
                  # Check everything can contact everything else now
                  self.assert_connectivity(retries=2,
                                           pass_list=n1_workloads + n2_workloads)
  
              elif test_type == "rules.tags":
                  profile0_tag = new_profiles[0]['metadata']['tags'][0]
                  profile1_tag = new_profiles[1]['metadata']['tags'][0]
                  rule0 = {'action': 'allow',
                           'source':
                               {'tag': profile1_tag}}
                  rule1 = {'action': 'allow',
                           'source':
                               {'tag': profile0_tag}}
                  new_profiles[0]['spec']['ingress'].append(rule0)
                  new_profiles[1]['spec']['ingress'].append(rule1)
                  self._apply_new_profile(new_profiles, host1)
                  # Check everything can contact everything else now
                  self.assert_connectivity(retries=3,
                                           pass_list=n1_workloads + n2_workloads)
  
              elif test_type == "rules.protocol.icmp":
                  rule = {'action': 'allow',
                          'source':
                              {'protocol': 'icmp'}}
                  # The copy.deepcopy(rule) is needed to ensure that we don't
                  # end up with a yaml document with a reference to the same
                  # rule.  While this is probably legal, it isn't main line.
                  new_profiles[0]['spec']['ingress'].append(rule)
                  new_profiles[1]['spec']['ingress'].append(copy.deepcopy(rule))
                  self._apply_new_profile(new_profiles, host1)
                  # Check everything can contact everything else now
                  self.assert_connectivity(retries=2,
                                           pass_list=n1_workloads + n2_workloads)
  
              elif test_type == "rules.ip.addr":
                  prof_n1, prof_n2 = self._get_profiles(new_profiles)
                  for workload in n1_workloads:
                      ip = workload.ip
                      rule = {'action': 'allow',
                              'source':
                                  {'net': '%s/32' % ip}}
                      prof_n2['spec']['ingress'].append(rule)
                  for workload in n2_workloads:
                      ip = workload.ip
                      rule = {'action': 'allow',
                              'source':
                                  {'net': '%s/32' % ip}}
                      prof_n1['spec']['ingress'].append(rule)
                  self._apply_new_profile(new_profiles, host1)
                  self.assert_connectivity(retries=2,
                                           pass_list=n1_workloads + n2_workloads)
  
              elif test_type == "rules.ip.net":
                  prof_n1, prof_n2 = self._get_profiles(new_profiles)
                  n1_ips = [workload.ip for workload in n1_workloads]
                  n2_ips = [workload.ip for workload in n2_workloads]
                  n1_subnet = netaddr.spanning_cidr(n1_ips)
                  n2_subnet = netaddr.spanning_cidr(n2_ips)
                  rule = {'action': 'allow',
                          'source':
                              {'net': str(n1_subnet)}}
                  prof_n2['spec']['ingress'].append(rule)
                  rule = {'action': 'allow',
                          'source':
                              {'net': str(n2_subnet)}}
                  prof_n1['spec']['ingress'].append(rule)
                  self._apply_new_profile(new_profiles, host1)
                  self.assert_connectivity(retries=2,
                                           pass_list=n1_workloads + n2_workloads)
  
              elif test_type == "rules.selector":
                  new_profiles[0]['metadata']['labels'] = {'net': 'n1'}
                  new_profiles[1]['metadata']['labels'] = {'net': 'n2'}
                  rule = {'action': 'allow',
                          'source':
                              {'selector': 'net=="n2"'}}
                  new_profiles[0]['spec']['ingress'].append(rule)
                  rule = {'action': 'allow',
                          'source':
                              {'selector': "net=='n1'"}}
                  new_profiles[1]['spec']['ingress'].append(rule)
                  self._apply_new_profile(new_profiles, host1)
                  self.assert_connectivity(retries=2,
                                           pass_list=n1_workloads + n2_workloads)
  
              elif test_type == "rules.tcp.port":
                  rule = {'action': 'allow',
                          'protocol': 'tcp',
                          'destination':
                              {'ports': [80]}}
                  # The copy.deepcopy(rule) is needed to ensure that we don't
                  # end up with a yaml document with a reference to the same
                  # rule.  While this is probably legal, it isn't main line.
                  new_profiles[0]['spec']['ingress'].append(rule)
                  new_profiles[1]['spec']['ingress'].append(copy.deepcopy(rule))
                  self._apply_new_profile(new_profiles, host1)
                  self.assert_connectivity(retries=2,
                                           pass_list=n1_workload
  
  [... the rest is omitted ...]
files: tests/st/policy/test_profile.py
files: # Copyright 2015 Tigera, Inc
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  import copy
  import netaddr
  import yaml
  from nose_parameterized import parameterized
  
  from tests.st.test_base import TestBase
  from tests.st.utils.docker_host import DockerHost, CLUSTER_STORE_DOCKER_OPTIONS
  from tests.st.utils.exceptions import CommandExecError
  from tests.st.utils.utils import assert_network, assert_profile, \
      assert_number_endpoints, get_profile_name
  
  POST_DOCKER_COMMANDS = ["docker load -i /code/calico-node.tar",
                          "docker load -i /code/busybox.tar",
                          "docker load -i /code/workload.tar"]
  
  
  class MultiHostMainline(TestBase):
      @parameterized.expand([
          #"tags",
          "rules.tags",
          #"rules.protocol.icmp",
          #"rules.ip.addr",
          #"rules.ip.net",
          #"rules.selector",
          #"rules.tcp.port",
          #"rules.udp.port",
      ])
      def test_multi_host(self, test_type):
          """
          Run a mainline multi-host test.
          Because multihost tests are slow to setup, this tests most mainline
          functionality in a single test.
          - Create two hosts
          - Create a network using the default IPAM driver, and a workload on
            each host assigned to that network.
          - Create a network using the Calico IPAM driver, and a workload on
            each host assigned to that network.
          - Check that hosts on the same network can ping each other.
          - Check that hosts on different networks cannot ping each other.
          - Modify the profile rules
          - Check that connectivity has changed to match the profile we set up
          - Re-apply the original profile
          - Check that connectivity goes back to what it was originally.
          """
          with DockerHost("host1",
                          additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS,
                          post_docker_commands=POST_DOCKER_COMMANDS,
                          start_calico=False) as host1, \
                  DockerHost("host2",
                             additional_docker_options=CLUSTER_STORE_DOCKER_OPTIONS,
                             post_docker_commands=POST_DOCKER_COMMANDS,
                             start_calico=False) as host2:
              (n1_workloads, n2_workloads, networks) = \
                  self._setup_workloads(host1, host2)
  
              # Get the original profiles:
              output = host1.calicoctl("get profile -o yaml")
              original_profiles = yaml.safe_load(output)
              # Make a copy of the profiles to mess about with.
              new_profiles = copy.deepcopy(original_profiles)
  
              if test_type == "tags":
                  profile0_tag = new_profiles[0]['metadata']['tags'][0]
                  profile1_tag = new_profiles[1]['metadata']['tags'][0]
                  # Make a new profiles dict where the two networks have each
                  # other in their tags list
                  new_profiles[0]['metadata']['tags'].append(profile1_tag)
                  new_profiles[1]['metadata']['tags'].append(profile0_tag)
  
                  self._apply_new_profile(new_profiles, host1)
                  # Check everything can contact everything else now
                  self.assert_connectivity(retries=2,
                                           pass_list=n1_workloads + n2_workloads)
  
              elif test_type == "rules.tags":
                  profile0_tag = new_profiles[0]['metadata']['tags'][0]
                  profile1_tag = new_profiles[1]['metadata']['tags'][0]
                  rule0 = {'action': 'allow',
                           'source':
                               {'tag': profile1_tag}}
                  rule1 = {'action': 'allow',
                           'source':
                               {'tag': profile0_tag}}
                  new_profiles[0]['spec']['ingress'].append(rule0)
                  new_profiles[1]['spec']['ingress'].append(rule1)
                  self._apply_new_profile(new_profiles, host1)
                  # Check everything can contact everything else now
                  self.assert_connectivity(retries=3,
                                           pass_list=n1_workloads + n2_workloads)
  
              elif test_type == "rules.protocol.icmp":
                  rule = {'action': 'allow',
                          'source':
                              {'protocol': 'icmp'}}
                  # The copy.deepcopy(rule) is needed to ensure that we don't
                  # end up with a yaml document with a reference to the same
                  # rule.  While this is probably legal, it isn't main line.
                  new_profiles[0]['spec']['ingress'].append(rule)
                  new_profiles[1]['spec']['ingress'].append(copy.deepcopy(rule))
                  self._apply_new_profile(new_profiles, host1)
                  # Check everything can contact everything else now
                  self.assert_connectivity(retries=2,
                                           pass_list=n1_workloads + n2_workloads)
  
              elif test_type == "rules.ip.addr":
                  prof_n1, prof_n2 = self._get_profiles(new_profiles)
                  for workload in n1_workloads:
                      ip = workload.ip
                      rule = {'action': 'allow',
                              'source':
                                  {'net': '%s/32' % ip}}
                      prof_n2['spec']['ingress'].append(rule)
                  for workload in n2_workloads:
                      ip = workload.ip
                      rule = {'action': 'allow',
                              'source':
                                  {'net': '%s/32' % ip}}
                      prof_n1['spec']['ingress'].append(rule)
                  self._apply_new_profile(new_profiles, host1)
                  self.assert_connectivity(retries=2,
                                           pass_list=n1_workloads + n2_workloads)
  
              elif test_type == "rules.ip.net":
                  prof_n1, prof_n2 = self._get_profiles(new_profiles)
                  n1_ips = [workload.ip for workload in n1_workloads]
                  n2_ips = [workload.ip for workload in n2_workloads]
                  n1_subnet = netaddr.spanning_cidr(n1_ips)
                  n2_subnet = netaddr.spanning_cidr(n2_ips)
                  rule = {'action': 'allow',
                          'source':
                              {'net': str(n1_subnet)}}
                  prof_n2['spec']['ingress'].append(rule)
                  rule = {'action': 'allow',
                          'source':
                              {'net': str(n2_subnet)}}
                  prof_n1['spec']['ingress'].append(rule)
                  self._apply_new_profile(new_profiles, host1)
                  self.assert_connectivity(retries=2,
                                           pass_list=n1_workloads + n2_workloads)
  
              elif test_type == "rules.selector":
                  new_profiles[0]['metadata']['labels'] = {'net': 'n1'}
                  new_profiles[1]['metadata']['labels'] = {'net': 'n2'}
                  rule = {'action': 'allow',
                          'source':
                              {'selector': 'net=="n2"'}}
                  new_profiles[0]['spec']['ingress'].append(rule)
                  rule = {'action': 'allow',
                          'source':
                              {'selector': "net=='n1'"}}
                  new_profiles[1]['spec']['ingress'].append(rule)
                  self._apply_new_profile(new_profiles, host1)
                  self.assert_connectivity(retries=2,
                                           pass_list=n1_workloads + n2_workloads)
  
              elif test_type == "rules.tcp.port":
                  rule = {'action': 'allow',
                          'protocol': 'tcp',
                          'destination':
                              {'ports': [80]}}
                  # The copy.deepcopy(rule) is needed to ensure that we don't
                  # end up with a yaml document with a reference to the same
                  # rule.  While this is probably legal, it isn't main line.
                  new_profiles[0]['spec']['ingress'].append(rule)
                  new_profiles[1]['spec']['ingress'].append(copy.deepcopy(rule))
                  self._apply_new_profile(new_profiles, host1)
                  self.assert_connectivity(retries=2,
                                           pass_list=n1_workloads + n2_workloads,
                                           type_list=['tcp'])
                  self.assert_connectivity(retries=2,
                                           pass_list=n1_workloads,
                                           fail_list=n2_workloads,
                                           type_list
  
  [... the rest is omitted ...]
files: tests/st/policy/test_profile.py
files: # Copyright (c) 2015-2017 Tigera, Inc. All rights reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  import logging
  import json
  import os
  import uuid
  import yaml
  from functools import partial
  from subprocess import CalledProcessError, Popen, PIPE
  
  from log_analyzer import LogAnalyzer, FELIX_LOG_FORMAT, TIMESTAMP_FORMAT
  from network import DockerNetwork
  from tests.st.utils.exceptions import CommandExecError
  from utils import get_ip, log_and_run, retry_until_success, ETCD_SCHEME, \
      ETCD_CA, ETCD_KEY, ETCD_CERT, ETCD_HOSTNAME_SSL
  from workload import Workload
  
  logger = logging.getLogger(__name__)
  # 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()
  
  NODE_CONTAINER_NAME = os.getenv("NODE_CONTAINER_NAME", "calico/node:latest")
  
  if ETCD_SCHEME == "https":
      CLUSTER_STORE_DOCKER_OPTIONS = "--cluster-store=etcd://%s:2379 " \
                                  "--cluster-store-opt kv.cacertfile=%s " \
                                  "--cluster-store-opt kv.certfile=%s " \
                                  "--cluster-store-opt kv.keyfile=%s " % \
                                  (ETCD_HOSTNAME_SSL, ETCD_CA, ETCD_CERT,
                                   ETCD_KEY)
  else:
      CLUSTER_STORE_DOCKER_OPTIONS = "--cluster-store=etcd://%s:2379 " % \
                                  get_ip()
  
  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.
      """
  
      # A static list of Docker networks that are created by the tests.  This
      # list covers all Docker hosts.
      docker_networks = []
  
      def __init__(self, name, start_calico=True, dind=True,
                   additional_docker_options="",
                   post_docker_commands=["docker load -i /code/calico-node.tar",
                                         "docker load -i /code/busybox.tar"],
                   calico_node_autodetect_ip=False,
                   simulate_gce_routing=False,
                   override_hostname=False):
          self.name = name
          self.dind = dind
          self.workloads = set()
          self.ip = None
          self.log_analyzer = 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.
          """
  
          self.ip6 = None
          """
          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]
          """
          Create an arbitrary hostname if we want to override.
          """
  
          # This variable is used to assert on destruction that this object was
          # cleaned up.  If not used as a context manager, users of this object
          # must invoke cleanup.
          self._cleaned = False
  
          docker_args = "--privileged -tid " \
                        "-v /lib/modules:/lib/modules " \
                        "-v %s/certs:%s/certs -v %s:/code --name %s" % \
                        (CHECKOUT_DIR, CHECKOUT_DIR, CHECKOUT_DIR,
                         self.name)
          if ETCD_SCHEME == "https":
              docker_args += " --add-host %s:%s" % (ETCD_HOSTNAME_SSL, get_ip())
  
          if dind:
              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
              log_and_run("docker run %s "
                          "calico/dind:latest "
                          "--iptables=false "
                          "%s" %
                          (docker_args, additional_docker_options))
  
              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")
              retry_until_success(docker_ps, ex_class=CalledProcessError,
                                  retries=10)
  
              if simulate_gce_routing:
                  # Simulate addressing and routing setup as on a GCE instance:
                  # the instance has a /32 address (which means that it appears
                  # not to be directly connected to anything) and a default route
                  # that does not have the 'onlink' flag to override that.
                  #
                  # First check that we can ping the Docker bridge, and trace out
                  # initial state.
                  self.execute("ping -c 1 -W 2 172.17.0.1")
                  self.execute("ip a")
                  self.execute("ip r")
  
                  # Change the normal /16 IP address to /32.
                  self.execute("ip a del %s/16 dev eth0" % self.ip)
                  self.execute("ip a add %s/32 dev eth0" % self.ip)
  
                  # Add a default route via the Docker bridge.
                  self.execute("ip r a 172.17.0.1 dev eth0")
                  self.execute("ip r a default via 172.17.0.1 dev eth0")
  
                  # Trace out final state, and check that we can still ping the
                  # Docker bridge.
                  self.execute("ip a")
                  self.execute("ip r")
                  self.execute("ping -c 1 -W 2 172.17.0.1")
  
              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.
              self.ip = get_ip(v6=False)
              self.ip6 = get_ip(v6=True)
  
          if start_calico:
              self.start_calico_node()
  
      def execute(self, command, raise_exception_on_failure=True):
          """
          Pass a command into a host container.
  
          Raises a CommandExecError() if the command returns a non-zero
          return code.
  
          :param command:  The command to execute.
          :return: The output from the command with leading and trailing
          whitespace removed.
          """
          if self.dind:
              command = self.escape_shell_single_quotes(command)
              command = "docker exec -it %s sh -c '%s'" % (self.name,
                                                           command)
  
          return log_and_run(command, raise_exception_on_failure=raise_exception_on_failure)
  
      def execute_readline(self, command):
          """
          Execute a command and return individual lines as a generator.
          Raises an exception if the return code is non-zero.  Stderr is ignored.
  
          Use this rather than execute if the command outputs a large amount of
          data that cannot be handled as a single string.
  
          :return: Generator of individual lines.
          """
          logger.debug("Running command on %s", self.name)
          logger.debug("  - Command: %s", command)
          if self.dind:
              command = self.escape_shell_single_quotes(command)
              command = "docker exec -it %s sh -c '%s'" % (self.name,
                                                           command)
          logger.debug("Final command: %s", command)
          proc = Popen(command, stdout=PIPE, shell=True)
  
          try:
              # Read and return one line at a time until no more data is
  
  
  [... the rest is omitted ...]
files: tests/st/utils/docker_host.py
files: # Copyright (c) 2015-2017 Tigera, Inc. All rights reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  import logging
  import json
  import os
  import uuid
  import yaml
  from functools import partial
  from subprocess import CalledProcessError, Popen, PIPE
  
  from log_analyzer import LogAnalyzer, FELIX_LOG_FORMAT, TIMESTAMP_FORMAT
  from network import DockerNetwork
  from tests.st.utils.exceptions import CommandExecError
  from utils import get_ip, log_and_run, retry_until_success, ETCD_SCHEME, \
      ETCD_CA, ETCD_KEY, ETCD_CERT, ETCD_HOSTNAME_SSL
  from workload import Workload
  
  logger = logging.getLogger(__name__)
  # 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()
  
  NODE_CONTAINER_NAME = os.getenv("NODE_CONTAINER_NAME", "calico/node:latest")
  
  if ETCD_SCHEME == "https":
      CLUSTER_STORE_DOCKER_OPTIONS = "--cluster-store=etcd://%s:2379 " \
                                  "--cluster-store-opt kv.cacertfile=%s " \
                                  "--cluster-store-opt kv.certfile=%s " \
                                  "--cluster-store-opt kv.keyfile=%s " % \
                                  (ETCD_HOSTNAME_SSL, ETCD_CA, ETCD_CERT,
                                   ETCD_KEY)
  else:
      CLUSTER_STORE_DOCKER_OPTIONS = "--cluster-store=etcd://%s:2379 " % \
                                  get_ip()
  
  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.
      """
  
      # A static list of Docker networks that are created by the tests.  This
      # list covers all Docker hosts.
      docker_networks = []
  
      def __init__(self, name, start_calico=True, dind=True,
                   additional_docker_options="",
                   post_docker_commands=["docker load -i /code/calico-node.tar",
                                         "docker load -i /code/busybox.tar"],
                   calico_node_autodetect_ip=False,
                   override_hostname=False):
          self.name = name
          self.dind = dind
          self.workloads = set()
          self.ip = None
          self.log_analyzer = 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.
          """
  
          self.ip6 = None
          """
          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]
          """
          Create an arbitrary hostname if we want to override.
          """
  
          # This variable is used to assert on destruction that this object was
          # cleaned up.  If not used as a context manager, users of this object
          # must invoke cleanup.
          self._cleaned = False
  
          docker_args = "--privileged -tid " \
                        "-v /lib/modules:/lib/modules " \
                        "-v %s/certs:%s/certs -v %s:/code --name %s" % \
                        (CHECKOUT_DIR, CHECKOUT_DIR, CHECKOUT_DIR,
                         self.name)
          if ETCD_SCHEME == "https":
              docker_args += " --add-host %s:%s" % (ETCD_HOSTNAME_SSL, get_ip())
  
          if dind:
              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
              log_and_run("docker run %s "
                          "calico/dind:latest "
                          "--iptables=false "
                          "%s" %
                          (docker_args, additional_docker_options))
  
              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")
              retry_until_success(docker_ps, ex_class=CalledProcessError,
                                  retries=10)
              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.
              self.ip = get_ip(v6=False)
              self.ip6 = get_ip(v6=True)
  
          if start_calico:
              self.start_calico_node()
  
      def execute(self, command, raise_exception_on_failure=True):
          """
          Pass a command into a host container.
  
          Raises a CommandExecError() if the command returns a non-zero
          return code.
  
          :param command:  The command to execute.
          :return: The output from the command with leading and trailing
          whitespace removed.
          """
          if self.dind:
              command = self.escape_shell_single_quotes(command)
              command = "docker exec -it %s sh -c '%s'" % (self.name,
                                                           command)
  
          return log_and_run(command, raise_exception_on_failure=raise_exception_on_failure)
  
      def execute_readline(self, command):
          """
          Execute a command and return individual lines as a generator.
          Raises an exception if the return code is non-zero.  Stderr is ignored.
  
          Use this rather than execute if the command outputs a large amount of
          data that cannot be handled as a single string.
  
          :return: Generator of individual lines.
          """
          logger.debug("Running command on %s", self.name)
          logger.debug("  - Command: %s", command)
          if self.dind:
              command = self.escape_shell_single_quotes(command)
              command = "docker exec -it %s sh -c '%s'" % (self.name,
                                                           command)
          logger.debug("Final command: %s", command)
          proc = Popen(command, stdout=PIPE, shell=True)
  
          try:
              # Read and return one line at a time until no more data is
              # returned.
              for line in proc.stdout:
                  yield line
          finally:
              status = proc.wait()
              logger.debug("- return: %s", status)
  
          if status:
              raise Exception("Command %s returned non-zero exit code %s" %
                              (command, status))
  
      def calicoctl(self, command, version=None):
          """
          Convenience function for abstracting away calling the calicoctl
          command.
  
          Raises a CommandExecError() if the command returns a non-zero
          return code.
  
          :param command:  The calicoctl command line parms as a single string.
          :param version:  The calicoctl version to use (this is appended to the
                           executable name.  It is assumed the Makefile will ensure
                           the required versions are downloaded.
          :return: The output from the command with leading and trailing
          whitespace removed.
          """
          if not version:
              calicoctl = os.environ.get("CALICOCTL", "/code/dist/calicoctl")
          else:
              calicoctl = "/code/dist/calicoctl-" + version
  
          if ETCD_SCHEME == "https":
              etcd_auth = "%s:2379" % ETCD_HOSTNAME_SSL
          else:
              etcd_auth = "%s:2379" % get_ip()
          # Export the environment,
  
  [... the rest is omitted ...]
files: tests/st/utils/docker_host.py