1
+ #
2
+ # Copyright IBM Corp. 2016 All Rights Reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ import os , time , re , requests
18
+
19
+ from bdd_rest_util import buildUrl , CORE_REST_PORT
20
+ from bdd_json_util import getAttributeFromJSON
21
+ from bdd_test_util import cli_call , bdd_log
22
+
23
+ class ContainerData :
24
+ def __init__ (self , containerName , ipAddress , envFromInspect , composeService ):
25
+ self .containerName = containerName
26
+ self .ipAddress = ipAddress
27
+ self .envFromInspect = envFromInspect
28
+ self .composeService = composeService
29
+
30
+ def getEnv (self , key ):
31
+ envValue = None
32
+ for val in self .envFromInspect :
33
+ if val .startswith (key ):
34
+ envValue = val [len (key ):]
35
+ break
36
+ if envValue == None :
37
+ raise Exception ("ENV key not found ({0}) for container ({1})" .format (key , self .containerName ))
38
+ return envValue
39
+
40
+ def __str__ (self ):
41
+ return "{} - {}" .format (self .containerName , self .ipAddress )
42
+
43
+ def __repr__ (self ):
44
+ return self .__str__ ()
45
+
46
+ def getDockerComposeFileArgsFromYamlFile (compose_yaml ):
47
+ parts = compose_yaml .split ()
48
+ args = []
49
+ for part in parts :
50
+ args = args + ["-f" ] + [part ]
51
+ return args
52
+
53
+ def parseComposeOutput (context ):
54
+ """Parses the compose output results and set appropriate values into context. Merges existing with newly composed."""
55
+ # Use the prefix to get the container name
56
+ containerNamePrefix = os .path .basename (os .getcwd ()) + "_"
57
+ containerNames = []
58
+ for l in context .compose_error .splitlines ():
59
+ tokens = l .split ()
60
+ bdd_log (tokens )
61
+ if 1 < len (tokens ):
62
+ thisContainer = tokens [1 ]
63
+ if containerNamePrefix not in thisContainer :
64
+ thisContainer = containerNamePrefix + thisContainer + "_1"
65
+ if thisContainer not in containerNames :
66
+ containerNames .append (thisContainer )
67
+
68
+ bdd_log ("Containers started: " )
69
+ bdd_log (containerNames )
70
+ # Now get the Network Address for each name, and set the ContainerData onto the context.
71
+ containerDataList = []
72
+ for containerName in containerNames :
73
+ output , error , returncode = \
74
+ cli_call (["docker" , "inspect" , "--format" , "{{ .NetworkSettings.IPAddress }}" , containerName ], expect_success = True )
75
+ bdd_log ("container {0} has address = {1}" .format (containerName , output .splitlines ()[0 ]))
76
+ ipAddress = output .splitlines ()[0 ]
77
+
78
+ # Get the environment array
79
+ output , error , returncode = \
80
+ cli_call (["docker" , "inspect" , "--format" , "{{ .Config.Env }}" , containerName ], expect_success = True )
81
+ env = output .splitlines ()[0 ][1 :- 1 ].split ()
82
+
83
+ # Get the Labels to access the com.docker.compose.service value
84
+ output , error , returncode = \
85
+ cli_call (["docker" , "inspect" , "--format" , "{{ .Config.Labels }}" , containerName ], expect_success = True )
86
+ labels = output .splitlines ()[0 ][4 :- 1 ].split ()
87
+ dockerComposeService = [composeService [27 :] for composeService in labels if composeService .startswith ("com.docker.compose.service:" )][0 ]
88
+ bdd_log ("dockerComposeService = {0}" .format (dockerComposeService ))
89
+ bdd_log ("container {0} has env = {1}" .format (containerName , env ))
90
+ containerDataList .append (ContainerData (containerName , ipAddress , env , dockerComposeService ))
91
+ # Now merge the new containerData info with existing
92
+ newContainerDataList = []
93
+ if "compose_containers" in context :
94
+ # Need to merge I new list
95
+ newContainerDataList = context .compose_containers
96
+ newContainerDataList = newContainerDataList + containerDataList
97
+
98
+ setattr (context , "compose_containers" , newContainerDataList )
99
+ bdd_log ("" )
100
+
101
+ def allContainersAreReadyWithinTimeout (context , timeout ):
102
+ timeoutTimestamp = time .time () + timeout
103
+ formattedTime = time .strftime ("%X" , time .localtime (timeoutTimestamp ))
104
+ bdd_log ("All containers should be up by {}" .format (formattedTime ))
105
+
106
+ allContainers = context .compose_containers
107
+
108
+ for container in allContainers :
109
+ if not containerIsInitializedByTimestamp (container , timeoutTimestamp ):
110
+ return False
111
+
112
+ peersAreReady = peersAreReadyByTimestamp (context , allContainers , timeoutTimestamp )
113
+
114
+ if peersAreReady :
115
+ bdd_log ("All containers in ready state, ready to proceed" )
116
+
117
+ return peersAreReady
118
+
119
+ def containerIsInitializedByTimestamp (container , timeoutTimestamp ):
120
+ while containerIsNotInitialized (container ):
121
+ if timestampExceeded (timeoutTimestamp ):
122
+ bdd_log ("Timed out waiting for {} to initialize" .format (container .containerName ))
123
+ return False
124
+
125
+ bdd_log ("{} not initialized, waiting..." .format (container .containerName ))
126
+ time .sleep (1 )
127
+
128
+ bdd_log ("{} now available" .format (container .containerName ))
129
+ return True
130
+
131
+ def timestampExceeded (timeoutTimestamp ):
132
+ return time .time () > timeoutTimestamp
133
+
134
+ def containerIsNotInitialized (container ):
135
+ return not containerIsInitialized (container )
136
+
137
+ def containerIsInitialized (container ):
138
+ isReady = tcpPortsAreReady (container )
139
+ isReady = isReady and restPortRespondsIfContainerIsPeer (container )
140
+
141
+ return isReady
142
+
143
+ def tcpPortsAreReady (container ):
144
+ netstatOutput = getContainerNetstatOutput (container .containerName )
145
+
146
+ for line in netstatOutput .splitlines ():
147
+ if re .search ("ESTABLISHED|LISTEN" , line ):
148
+ return True
149
+
150
+ bdd_log ("No TCP connections are ready in container {}" .format (container .containerName ))
151
+ return False
152
+
153
+ def getContainerNetstatOutput (containerName ):
154
+ command = ["docker" , "exec" , containerName , "netstat" , "-atun" ]
155
+ stdout , stderr , returnCode = cli_call (command , expect_success = False )
156
+
157
+ return stdout
158
+
159
+ def restPortRespondsIfContainerIsPeer (container ):
160
+ containerName = container .containerName
161
+ command = ["docker" , "exec" , containerName , "curl" , "localhost:{}" .format (CORE_REST_PORT )]
162
+
163
+ if containerIsPeer (container ):
164
+ stdout , stderr , returnCode = cli_call (command , expect_success = False )
165
+
166
+ if returnCode != 0 :
167
+ bdd_log ("Connection to REST Port on {} failed" .format (containerName ))
168
+
169
+ return returnCode == 0
170
+
171
+ return True
172
+
173
+ def peersAreReadyByTimestamp (context , containers , timeoutTimestamp ):
174
+ peers = getPeerContainers (containers )
175
+ bdd_log ("Detected Peers: {}" .format (peers ))
176
+
177
+ for peer in peers :
178
+ if not peerIsReadyByTimestamp (context , peer , peers , timeoutTimestamp ):
179
+ return False
180
+
181
+ return True
182
+
183
+ def getPeerContainers (containers ):
184
+ peers = []
185
+
186
+ for container in containers :
187
+ if containerIsPeer (container ):
188
+ peers .append (container )
189
+
190
+ return peers
191
+
192
+ def containerIsPeer (container ):
193
+ # This is not an ideal way of detecting whether a container is a peer or not since
194
+ # we are depending on the name of the container. Another way of detecting peers is
195
+ # is to determine if the container is listening on the REST port. However, this method
196
+ # may run before the listening port is ready. Hence, as along as the current
197
+ # convention of vp[0-9] is adhered to this function will be good enough.
198
+ return re .search ("vp[0-9]+" , container .containerName , re .IGNORECASE )
199
+
200
+ def peerIsReadyByTimestamp (context , peerContainer , allPeerContainers , timeoutTimestamp ):
201
+ while peerIsNotReady (context , peerContainer , allPeerContainers ):
202
+ if timestampExceeded (timeoutTimestamp ):
203
+ bdd_log ("Timed out waiting for peer {}" .format (peerContainer .containerName ))
204
+ return False
205
+
206
+ bdd_log ("Peer {} not ready, waiting..." .format (peerContainer .containerName ))
207
+ time .sleep (1 )
208
+
209
+ bdd_log ("Peer {} now available" .format (peerContainer .containerName ))
210
+ return True
211
+
212
+ def peerIsNotReady (context , thisPeer , allPeers ):
213
+ return not peerIsReady (context , thisPeer , allPeers )
214
+
215
+ def peerIsReady (context , thisPeer , allPeers ):
216
+ connectedPeers = getConnectedPeersFromPeer (context , thisPeer )
217
+
218
+ if connectedPeers is None :
219
+ return False
220
+
221
+ numPeers = len (allPeers )
222
+ numConnectedPeers = len (connectedPeers )
223
+
224
+ if numPeers != numConnectedPeers :
225
+ bdd_log ("Expected {} peers, got {}" .format (numPeers , numConnectedPeers ))
226
+ bdd_log ("Connected Peers: {}" .format (connectedPeers ))
227
+ bdd_log ("Expected Peers: {}" .format (allPeers ))
228
+
229
+ return numPeers == numConnectedPeers
230
+
231
+ def getConnectedPeersFromPeer (context , thisPeer ):
232
+ url = buildUrl (context , thisPeer .ipAddress , "/network/peers" )
233
+ response = requests .get (url , headers = {'Accept' : 'application/json' }, verify = False )
234
+
235
+ if response .status_code != 200 :
236
+ return None
237
+
238
+ return getAttributeFromJSON ("peers" , response .json (), "There should be a peer json attribute" )
0 commit comments