github.com/leonlxy/hyperledger@v1.0.0-alpha.0.20170427033203-34922035d248/bddtests/steps/compose.py (about)

     1  # Copyright IBM Corp. 2016 All Rights Reserved.
     2  #
     3  # Licensed under the Apache License, Version 2.0 (the "License");
     4  # you may not use this file except in compliance with the License.
     5  # You may obtain a copy of the License at
     6  #
     7  #      http://www.apache.org/licenses/LICENSE-2.0
     8  #
     9  # Unless required by applicable law or agreed to in writing, software
    10  # distributed under the License is distributed on an "AS IS" BASIS,
    11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  # See the License for the specific language governing permissions and
    13  # limitations under the License.
    14  #
    15  
    16  import os
    17  import uuid
    18  import bdd_test_util
    19  from contexthelper import ContextHelper
    20  import json
    21  
    22  from abc import ABCMeta, abstractmethod
    23  
    24  class ContainerData:
    25      def __init__(self, containerName, ipAddress, envFromInspect, composeService, ports):
    26          self.containerName = containerName
    27          self.ipAddress = ipAddress
    28          self.envFromInspect = envFromInspect
    29          self.composeService = composeService
    30          self.ports = ports
    31  
    32      def getEnv(self, key):
    33          envValue = None
    34          for val in self.envFromInspect:
    35              if val.startswith(key):
    36                  envValue = val[len(key):]
    37                  break
    38          if envValue == None:
    39              raise Exception("ENV key not found ({0}) for container ({1})".format(key, self.containerName))
    40          return envValue
    41  
    42  
    43  class CompositionCallback:
    44      __metaclass__ = ABCMeta
    45      @abstractmethod
    46      def composing(self, composition, context):
    47          pass
    48      @abstractmethod
    49      def decomposing(self, composition, context):
    50          pass
    51      @abstractmethod
    52      def getEnv(self, composition, context, env):
    53          pass
    54  
    55  class Test(CompositionCallback):
    56      def composing(self, composition, context):
    57          pass
    58      def decomposing(self, composition, context):
    59          pass
    60      def getEnv(self, composition, context, env):
    61          pass
    62  
    63  def GetDockerSafeUUID():
    64      return str(uuid.uuid1()).replace('-','')
    65  
    66  class Composition:
    67  
    68      @classmethod
    69      def RegisterCallbackInContext(cls, context, callback):
    70          if not isinstance(callback, CompositionCallback):
    71              raise TypeError("Expected type to be {0}, instead received {1}".format(CompositionCallback, type(callback)))
    72          Composition.GetCompositionCallbacksFromContext(context).append(callback)
    73  
    74      @classmethod
    75      def GetCompositionCallbacksFromContext(cls, context):
    76          if not "compositionCallbacks" in context:
    77              context.compositionCallbacks = []
    78          return context.compositionCallbacks
    79  
    80  
    81      @classmethod
    82      def GetUUID(cls):
    83          return GetDockerSafeUUID()
    84  
    85      def __init__(self, context, composeFilesYaml, projectName = None,
    86                   force_recreate = True, components = []):
    87          self.contextHelper = ContextHelper.GetHelper(context=context)
    88          if not projectName:
    89              projectName = self.contextHelper.getGuuid()
    90          self.projectName = projectName
    91          self.context = context
    92          self.containerDataList = []
    93          self.composeFilesYaml = composeFilesYaml
    94          self.serviceNames = []
    95          self.serviceNames = self._collectServiceNames()
    96          # Register with contextHelper (Supports docgen)
    97          self.contextHelper.registerComposition(self)
    98          [callback.composing(self, context) for callback in Composition.GetCompositionCallbacksFromContext(context)]
    99          self.up(context, force_recreate, components)
   100  
   101      def _collectServiceNames(self):
   102          'First collect the services names.'
   103          servicesList = [service for service in self.issueCommand(["config", "--services"]).splitlines() if "WARNING" not in service]
   104          return servicesList
   105  
   106      def up(self, context, force_recreate=True, components=[]):
   107          self.serviceNames = self._collectServiceNames()
   108          command = ["up", "-d"]
   109          if force_recreate:
   110              command += ["--force-recreate"]
   111          self.issueCommand(command + components)
   112  
   113      def scale(self, context, serviceName, count=1):
   114          self.serviceNames = self._collectServiceNames()
   115          command = ["scale", "%s=%d" %(serviceName, count)]
   116          self.issueCommand(command)
   117  
   118      def stop(self, context, components=[]):
   119          self.serviceNames = self._collectServiceNames()
   120          command = ["stop"]
   121          self.issueCommand(command, components)
   122  
   123      def start(self, context, components=[]):
   124          self.serviceNames = self._collectServiceNames()
   125          command = ["start"]
   126          self.issueCommand(command, components)
   127  
   128      def getServiceNames(self):
   129          return list(self.serviceNames)
   130  
   131      def parseComposeFilesArg(self, composeFileArgs):
   132          args = [arg for sublist in [["-f", file] for file in [file if not os.path.isdir(file) else os.path.join(file, 'docker-compose.yml') for file in composeFileArgs.split()]] for arg in sublist]
   133          return args
   134  
   135      def getFileArgs(self):
   136          return self.parseComposeFilesArg(self.composeFilesYaml)
   137  
   138      def getEnvAdditions(self):
   139          myEnv = {}
   140          myEnv["COMPOSE_PROJECT_NAME"] = self.projectName
   141          myEnv["CORE_PEER_NETWORKID"] = self.projectName
   142          # Invoke callbacks
   143          [callback.getEnv(self, self.context, myEnv) for callback in Composition.GetCompositionCallbacksFromContext(self.context)]
   144          return myEnv
   145  
   146      def getEnv(self):
   147          myEnv = os.environ.copy()
   148          for key,value in self.getEnvAdditions().iteritems():
   149              myEnv[key] = value
   150          # myEnv["COMPOSE_PROJECT_NAME"] = self.projectName
   151          # myEnv["CORE_PEER_NETWORKID"] = self.projectName
   152          # # Invoke callbacks
   153          # [callback.getEnv(self, self.context, myEnv) for callback in Composition.GetCompositionCallbacksFromContext(self.context)]
   154          return myEnv
   155  
   156      def getConfig(self):
   157          return self.issueCommand(["config"])
   158  
   159      def refreshContainerIDs(self):
   160          containers = self.issueCommand(["ps", "-q"]).split()
   161          return containers
   162  
   163      def _callCLI(self, argList, expect_success, env):
   164          return bdd_test_util.cli_call(argList, expect_success=expect_success, env=env)
   165  
   166      def issueCommand(self, command, components=[]):
   167          componentList = []
   168          useCompose = True
   169          for component in components:
   170              if '_' in component:
   171                  useCompose = False
   172                  componentList.append("%s_%s" % (self.projectName, component))
   173              else:
   174                  break
   175  
   176          # If we need to perform an operation on a specific container, use
   177          # docker not docker-compose
   178          if useCompose:
   179              cmdArgs = self.getFileArgs()+ command + components
   180              cmd = ["docker-compose"] + cmdArgs
   181          else:
   182              cmdArgs = command + componentList
   183              cmd = ["docker"] + cmdArgs
   184  
   185          #print("cmd:", cmd)
   186          output, error, returncode = \
   187              self._callCLI(cmd, expect_success=True, env=self.getEnv())
   188  
   189          # Don't rebuild if ps command
   190          if command[0] !="ps" and command[0] !="config":
   191              self.rebuildContainerData()
   192          return output
   193  
   194      def rebuildContainerData(self):
   195          self.containerDataList = []
   196          for containerID in self.refreshContainerIDs():
   197  
   198              # get container metadata
   199              container = json.loads(bdd_test_util.cli_call(["docker", "inspect", containerID], expect_success=True)[0])[0]
   200  
   201              # container name
   202              container_name = container['Name'][1:]
   203  
   204              # container ip address (only if container is running)
   205              container_ipaddress = None
   206              if container['State']['Running']:
   207                  container_ipaddress = container['NetworkSettings']['IPAddress']
   208                  if not container_ipaddress:
   209                      # ipaddress not found at the old location, try the new location
   210                      container_ipaddress = container['NetworkSettings']['Networks'].values()[0]['IPAddress']
   211  
   212              # container environment
   213              container_env = container['Config']['Env']
   214  
   215              # container exposed ports
   216              container_ports = container['NetworkSettings']['Ports']
   217  
   218              # container docker-compose service
   219              container_compose_service = container['Config']['Labels']['com.docker.compose.service']
   220  
   221              self.containerDataList.append(ContainerData(container_name, container_ipaddress, container_env, container_compose_service, container_ports))
   222  
   223      def decompose(self):
   224          self.issueCommand(["unpause"])
   225          self.issueCommand(["down"])
   226          self.issueCommand(["kill"])
   227          self.issueCommand(["rm", "-f"])
   228  
   229          # Now remove associated chaincode containers if any
   230          output, error, returncode = \
   231              bdd_test_util.cli_call(["docker"] + ["ps", "-qa", "--filter", "name={0}".format(self.projectName)], expect_success=True, env=self.getEnv())
   232          for containerId in output.splitlines():
   233              output, error, returncode = \
   234                  bdd_test_util.cli_call(["docker"] + ["rm", "-f", containerId], expect_success=True, env=self.getEnv())
   235  
   236          # Remove the associated network
   237          output, error, returncode = \
   238              bdd_test_util.cli_call(["docker"] + ["network", "ls", "-q", "--filter", "name={0}".format(self.projectName)], expect_success=True, env=self.getEnv())
   239          for networkId in output.splitlines():
   240              output, error, returncode = \
   241                  bdd_test_util.cli_call(["docker"] + ["network", "rm", networkId], expect_success=True, env=self.getEnv())
   242  
   243          # Invoke callbacks
   244          [callback.decomposing(self, self.context) for callback in Composition.GetCompositionCallbacksFromContext(self.context)]