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)]