github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-supply-chain-master/bin/run_docker_test (about)

     1  #!/usr/bin/env python3
     2  #
     3  # Copyright 2017 Intel Corporation
     4  #
     5  # Licensed under the Apache License, Version 2.0 (the "License");
     6  # you may not use this file except in compliance with the License.
     7  # You may obtain a copy of the License at
     8  #
     9  #     http://www.apache.org/licenses/LICENSE-2.0
    10  #
    11  # Unless required by applicable law or agreed to in writing, software
    12  # distributed under the License is distributed on an "AS IS" BASIS,
    13  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  # See the License for the specific language governing permissions and
    15  # limitations under the License.
    16  
    17  import subprocess
    18  import os
    19  import sys
    20  import argparse
    21  import time
    22  import traceback
    23  import logging
    24  import yaml
    25  
    26  
    27  logging.basicConfig(level=logging.DEBUG)
    28  LOGGER = logging.getLogger(__name__)
    29  
    30  
    31  DEFAULT_TIMEOUT = 600
    32  COMPOSE_DOWN_RETRY_TIMEOUT = 60
    33  DOCKER_PS_TIMEOUT=30
    34  
    35  
    36  class RunDockerTestError(BaseException):
    37      pass
    38  
    39  
    40  class Timer:
    41      def __init__(self, duration):
    42          self._duration = duration
    43          self._start = 0
    44  
    45      def start(self):
    46          self._start = time.time()
    47  
    48      def remaining(self):
    49          elapsed = time.time() - self._start
    50          return max(self._duration - elapsed, 0)
    51  
    52  
    53  def main():
    54      args = parse_args()
    55  
    56      # Search for compose file passed
    57      compose_file = _get_compose_file(args.compose_file)
    58      test_service = _get_test_service(compose_file)
    59  
    60      # Load isolation id if it is set and validate
    61      isolation_id = _get_isolation_id()
    62      _setup_environ(isolation_id)
    63  
    64      # Construct commands
    65      compose = [
    66          'docker-compose',
    67          '-p', isolation_id,
    68          '-f', compose_file
    69      ]
    70  
    71      compose_up = compose + [
    72          'up', '--abort-on-container-exit'
    73      ]
    74  
    75      compose_down = compose + ['down', '--remove-orphans']
    76  
    77      compose_kill = compose + ['kill']
    78  
    79      scrape = [
    80          'docker', 'ps', '-a',
    81          '--format={{.Names}},{{.Image}},{{.Label "install-type"}}',
    82      ]
    83  
    84      inspect = [
    85          'docker', 'inspect',
    86          '-f', "{{.State.ExitCode}}",
    87          "{}_{}_1".format(
    88              isolation_id,
    89              test_service)
    90      ]
    91  
    92      compose_dict = load_compose_file(compose_file)
    93      _validate_compose_dict(compose_dict, test_service, compose_file)
    94  
    95      if not args.clean:
    96          _check_for_existing_containers(compose_file, compose_dict, isolation_id)
    97  
    98      for service in compose_dict['services']:
    99          scrape += [
   100              '--filter', 'name={}_{}_1'.format(isolation_id, service),
   101          ]
   102  
   103      timer = Timer(args.timeout)
   104  
   105  
   106      # Run tests
   107      try:
   108          if not args.clean:
   109              exit_status = 0
   110              timer.start()
   111  
   112              LOGGER.info("Bringing up with %s", str(compose_up))
   113              try:
   114                  # 1. Run the tests
   115                  subprocess.run(compose_up, check=True, timeout=timer.remaining())
   116  
   117              except FileNotFoundError as err:
   118                  LOGGER.error("Bad docker-compose up command")
   119                  LOGGER.exception(err)
   120                  exit(1)
   121  
   122              except subprocess.CalledProcessError as err:
   123                  LOGGER.error("Failed to start test.")
   124                  LOGGER.exception(err)
   125                  exit_status = 1
   126  
   127              except subprocess.TimeoutExpired as err:
   128                  LOGGER.error("Test timed out.")
   129                  LOGGER.exception(err)
   130                  exit_status = 1
   131  
   132              if exit_status == 0:
   133                  LOGGER.info("Getting result with: %s", str(inspect))
   134                  try:
   135                      # 2. Get the exit code of the test container
   136                      exit_status = subprocess.run(
   137                          inspect, stdout=subprocess.PIPE,
   138                          timeout=timer.remaining(), check=True
   139                      ).stdout.decode().strip()
   140  
   141                  except FileNotFoundError as err:
   142                      LOGGER.error("Bad docker inspect or ps command")
   143                      LOGGER.exception(err)
   144                      exit_status = 1
   145  
   146                  except subprocess.CalledProcessError as err:
   147                      LOGGER.error("Failed to retrieve exit status of test.")
   148                      LOGGER.exception(err)
   149                      exit_status = 1
   150  
   151                  except subprocess.TimeoutExpired as err:
   152                      LOGGER.error("Retrieving exit status timed out.")
   153                      LOGGER.exception(err)
   154                      exit_status = 1
   155  
   156              try:
   157                  info = subprocess.run(
   158                      scrape, stdout=subprocess.PIPE,
   159                      timeout=timer.remaining(), check=True
   160                  ).stdout.decode().strip()
   161                  for line in info.split('\n'):
   162                      container, image, install_type = line.split(',')
   163                      LOGGER.info(
   164                          "Container {} ran image {} with install-type {}".format(
   165                              container, image, install_type
   166                          )
   167                      )
   168  
   169              except BaseException:
   170                  LOGGER.error("Could not gather information about image used.")
   171  
   172          else: # cleaning
   173              exit_status = 0
   174  
   175          LOGGER.info("Shutting down with: %s", str(compose_down))
   176          shutdown_success = False
   177          for i in range(2):
   178              if shutdown_success == False:
   179  
   180                  # Always give compose down time to cleanup
   181                  timeout = max(timer.remaining(), COMPOSE_DOWN_RETRY_TIMEOUT)
   182                  try:
   183                      # 3. Cleanup after the test
   184                      subprocess.run(compose_down, check=True, timeout=timeout)
   185                      shutdown_success = True
   186  
   187                  except FileNotFoundError as err:
   188                      LOGGER.error("Bad docker-compose down command.\n")
   189                      LOGGER.exception(err)
   190  
   191                  except subprocess.CalledProcessError as err:
   192                      LOGGER.error("Failed to cleanup after test.")
   193                      LOGGER.exception(err)
   194  
   195                  except subprocess.TimeoutExpired as err:
   196                      LOGGER.error("Shutting down the test timed out.")
   197                      LOGGER.exception(err)
   198  
   199          if not shutdown_success:
   200              LOGGER.critical(
   201                  "There are residual containers on the host that need to be"
   202                  " cleaned up! Do `docker ps -a` and `docker newtork list` to"
   203                  " see what was left behind or use `run_docker_test --clean`!"
   204              )
   205  
   206          exit(int(exit_status))
   207  
   208      except KeyboardInterrupt:
   209          subprocess.run(compose_down, check=True,
   210              timeout=COMPOSE_DOWN_RETRY_TIMEOUT)
   211          exit(1)
   212  
   213  
   214  def load_compose_file(compose_file):
   215  
   216      try:
   217          with open(compose_file) as fd:
   218              contents = fd.read()
   219          compose = yaml.load(contents)
   220          return compose
   221  
   222      except OSError:
   223          raise RunDockerTestError(
   224              "Docker compose file '{}' could not be opened. Make sure it "
   225              "exists and is readable.".format(compose_file))
   226  
   227  
   228  def parse_args():
   229      parser = argparse.ArgumentParser()
   230  
   231      parser.add_argument(
   232          "compose_file",
   233          help="docker-compose.yaml file that contains the test")
   234  
   235      parser.add_argument(
   236          "-c", "--clean",
   237          help="don't run the test, just cleanup a previous run",
   238          action='store_true',
   239          default=False)
   240  
   241      parser.add_argument(
   242          "-n", "--no-build",
   243          help="don't build docker images",
   244          action='store_true',
   245          default=False)
   246  
   247      parser.add_argument(
   248          "-t", "--timeout",
   249          help="how long to wait before timing out",
   250          type=int,
   251          default=DEFAULT_TIMEOUT)
   252  
   253      return parser.parse_args()
   254  
   255  
   256  def _get_test_service(compose_file):
   257      return os.path.basename(compose_file).replace('.yaml', '').replace('_', '-')
   258  
   259  
   260  def _validate_compose_dict(compose_dict, test_service, compose_file):
   261      if test_service not in compose_dict['services']:
   262          raise RunDockerTestError(
   263              "Test service '{}' does not exist in compose file: '{}'".format(
   264                  test_service, compose_file))
   265  
   266  
   267  def _check_for_existing_containers(compose_file, compose_dict, isolation_id):
   268      containers = _get_existing_containers()
   269      for service in compose_dict['services'].keys():
   270          container_name_to_create = "{}_{}_1".format(isolation_id, service)
   271          for existing_container_name in containers:
   272              if container_name_to_create == existing_container_name:
   273                  raise RunDockerTestError(
   274                      "The container '{}' which would be created by this test"
   275                      " already exists!\nDo:\n`run_docker_test --clean {}`\nto"
   276                      " remove the container, or use docker manually.".format(
   277                          container_name_to_create, compose_file
   278                      )
   279                  )
   280  
   281  def _check_for_existing_network(isolation_id):
   282      networks = _get_existing_networks()
   283      network_to_create = '{}_default'.format(isolation_id)
   284      if network_to_create in networks:
   285          raise RunDockerTestError(
   286              "The network '{}' which would be created by this test already"
   287              " exists!\nDo:\n`run_docker_test --clean {}`\nto remove the"
   288              " network, or use docker manually.".format(
   289                  network_to_create, compose_file
   290              )
   291          )
   292  
   293  
   294  def _get_existing_containers():
   295      cmd = ['docker', 'ps', '-a', '--format={{.Names}}']
   296      success = False
   297      try:
   298          containers = subprocess.run(
   299              cmd, stdout=subprocess.PIPE, check=True, timeout=DOCKER_PS_TIMEOUT
   300          ).stdout.decode().strip().split('\n')
   301          success = True
   302  
   303      except FileNotFoundError as err:
   304          LOGGER.error("Bad docker ps command")
   305          LOGGER.exception(err)
   306  
   307      except subprocess.CalledProcessError as err:
   308          LOGGER.error("Failed to retrieve exit status of test.")
   309          LOGGER.exception(err)
   310  
   311      except subprocess.TimeoutExpired as err:
   312          LOGGER.error("Retrieving exit status timed out.")
   313          LOGGER.exception(err)
   314  
   315      if not success:
   316          raise RunDockerTestError("Failed to get list of docker containers.")
   317  
   318      return containers
   319  
   320  
   321  def _get_existing_networks():
   322      cmd = ['docker', 'network', 'ls', '--format={{.Name}}']
   323      success = False
   324  
   325      try:
   326          networks = subprocess.run(
   327              cmd, stdout=subprocess.PIPE, check=True, timeout=DOCKER_PS_TIMEOUT
   328          ).stdout.decode().strip().split('\n')
   329          success = True
   330  
   331      except FileNotFoundError as err:
   332          LOGGER.error("Bad docker network ls command")
   333          LOGGER.exception(err)
   334  
   335      except subprocess.CalledProcessError as err:
   336          LOGGER.error("Failed to retrieve network list.")
   337          LOGGER.exception(err)
   338  
   339      except subprocess.TimeoutExpired as err:
   340          LOGGER.error("Retrieving network list timed out.")
   341          LOGGER.exception(err)
   342  
   343      if not success:
   344          raise RunDockerTestError("Failed to get list of docker networks.")
   345  
   346      return networks
   347  
   348  
   349  def _get_isolation_id():
   350      if 'ISOLATION_ID' in os.environ:
   351          isolation_id = os.environ['ISOLATION_ID']
   352      else:
   353          isolation_id = 'latest'
   354  
   355      if not isolation_id.isalnum():
   356          raise RunDockerTestError("ISOLATION_ID must be alphanumeric")
   357  
   358      return isolation_id
   359  
   360  
   361  def _setup_environ(isolation_id):
   362      os.environ['ISOLATION_ID'] = isolation_id
   363      os.environ['SAWTOOTH_CORE'] = os.path.dirname(
   364          os.path.dirname(
   365              os.path.realpath(__file__)
   366          )
   367      )
   368      print(os.environ['SAWTOOTH_CORE'])
   369  
   370  
   371  def _get_compose_dir():
   372      return os.path.join(
   373          os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
   374          "integration", "sawtooth_integration", "docker"
   375      )
   376  
   377  
   378  def _get_compose_file(compose_file):
   379      # Try "as given"
   380      if not os.path.exists(compose_file):
   381          # If not, try in integration docker directory
   382          compose_file = os.path.join(_get_compose_dir(), compose_file)
   383      if not os.path.exists(compose_file):
   384          # If not, try appending .yaml
   385          compose_file = os.path.join(
   386              _get_compose_dir(),
   387              "{}.yaml".format(compose_file)
   388          )
   389      if not os.path.exists(compose_file):
   390          raise RunDockerTestError(
   391              "Docker compose file '{}' does not exist.".format(compose_file))
   392  
   393      return compose_file
   394  
   395  
   396  if __name__ == "__main__":
   397      try:
   398          main()
   399  
   400      except RunDockerTestError as err:
   401          LOGGER.error(err)
   402          exit(1)