Skip to content

Commit ae16208

Browse files
committed
[FAB-3494] Enhance reporting
Working on allowing for storage and reload of directory information. Now will only print logs if composition in context. Now allow for recreation of directory and composition. Now seeking for latest block works correctly. Added appendix section to demonstrate interactive behavior support. Removed some print statements. Change-Id: I50573cc38afde20374e83229d9d93ea5187c4d50 Signed-off-by: Jeff Garratt <[email protected]>
1 parent 7023d29 commit ae16208

12 files changed

+209
-43
lines changed

bddtests/environment.py

+21-20
Original file line numberDiff line numberDiff line change
@@ -43,26 +43,27 @@ def after_scenario(context, scenario):
4343
contextHelper.after_scenario(scenario)
4444

4545
get_logs = context.config.userdata.get("logs", "N")
46-
if get_logs.lower() == "force" or (scenario.status == "failed" and get_logs.lower() == "y" and "compose_containers" in context):
47-
print("Scenario {0} failed. Getting container logs".format(scenario.name))
48-
file_suffix = "_" + scenario.name.replace(" ", "_") + ".log"
49-
# get logs from the peer containers
50-
for containerData in context.compose_containers:
51-
(fileName, fileExists) = contextHelper.getTmpPathForName(name="{0}_{1}".format(containerData.containerName, scenario.name), extension="log", path_relative_to_tmp="logs")
52-
with open(fileName, "w+") as logfile:
53-
sys_rc = subprocess.call(["docker", "logs", containerData.containerName], stdout=logfile, stderr=logfile)
54-
if sys_rc !=0 :
55-
print("Cannot get logs for {0}. Docker rc = {1}".format(containerData.containerName,sys_rc))
56-
# get logs from the chaincode containers
57-
cc_output, cc_error, cc_returncode = \
58-
cli_call(["docker", "ps", "-f", "name=dev-", "--format", "{{.Names}}"], expect_success=True)
59-
for containerName in cc_output.splitlines():
60-
namePart,sep,junk = containerName.rpartition("-")
61-
(fileName, fileExists) = contextHelper.getTmpPathForName(name="{0}_{1}".format(namePart, scenario.name), extension="log", path_relative_to_tmp="logs")
62-
with open(fileName, "w+") as logfile:
63-
sys_rc = subprocess.call(["docker", "logs", containerName], stdout=logfile, stderr=logfile)
64-
if sys_rc !=0 :
65-
print("Cannot get logs for {0}. Docker rc = {1}".format(namepart,sys_rc))
46+
if "compose_containers" in context:
47+
if get_logs.lower() == "force" or (scenario.status == "failed" and get_logs.lower() == "y"):
48+
print("Scenario {0} failed. Getting container logs".format(scenario.name))
49+
file_suffix = "_" + scenario.name.replace(" ", "_") + ".log"
50+
# get logs from the peer containers
51+
for containerData in context.compose_containers:
52+
(fileName, fileExists) = contextHelper.getTmpPathForName(name="{0}_{1}".format(containerData.containerName, scenario.name), extension="log", path_relative_to_tmp="logs")
53+
with open(fileName, "w+") as logfile:
54+
sys_rc = subprocess.call(["docker", "logs", containerData.containerName], stdout=logfile, stderr=logfile)
55+
if sys_rc !=0 :
56+
print("Cannot get logs for {0}. Docker rc = {1}".format(containerData.containerName,sys_rc))
57+
# get logs from the chaincode containers
58+
cc_output, cc_error, cc_returncode = \
59+
cli_call(["docker", "ps", "-f", "name=dev-", "--format", "{{.Names}}"], expect_success=True)
60+
for containerName in cc_output.splitlines():
61+
namePart,sep,junk = containerName.rpartition("-")
62+
(fileName, fileExists) = contextHelper.getTmpPathForName(name="{0}_{1}".format(namePart, scenario.name), extension="log", path_relative_to_tmp="logs")
63+
with open(fileName, "w+") as logfile:
64+
sys_rc = subprocess.call(["docker", "logs", containerName], stdout=logfile, stderr=logfile)
65+
if sys_rc !=0 :
66+
print("Cannot get logs for {0}. Docker rc = {1}".format(namepart,sys_rc))
6667
if 'doNotDecompose' in scenario.tags:
6768
if 'compose_yaml' in context:
6869
print("Not going to decompose after scenario {0}, with yaml '{1}'".format(scenario.name, context.compose_yaml))

bddtests/steps/bdd_grpc_util.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def getGRPCChannel(ipAddress, port, root_certificates, ssl_target_name_override)
2323
channel = grpc.secure_channel("{0}:{1}".format(ipAddress, port), creds,
2424
options=(('grpc.ssl_target_name_override', ssl_target_name_override,),('grpc.default_authority', ssl_target_name_override,),('grpc.max_receive_message_length', 100*1024*1024)))
2525

26-
print("Returning GRPC for address: {0}".format(ipAddress))
26+
# print("Returning GRPC for address: {0}".format(ipAddress))
2727
return channel
2828

2929
def toStringArray(items):

bddtests/steps/bootstrap_impl.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ def step_impl(context, userName, templateName, chainCreatePolicyName):
144144
' At the moment, only really defining MSP Config Items (NOT SIGNED)'
145145
directory = bootstrap_util.getDirectory(context)
146146
user = directory.getUser(userName)
147-
user.setTagValue(templateName, [directory.getOrganization(row['Organization']) for row in context.table.rows])
147+
user.setTagValue(templateName, [directory.getOrganization(row['Organization']).name for row in context.table.rows])
148148

149149
@given(u'the user "{userName}" creates a configUpdateEnvelope "{configUpdateEnvelopeName}" using configUpdate "{configUpdateName}"')
150150
def step_impl(context, userName, configUpdateEnvelopeName, configUpdateName):
@@ -234,9 +234,7 @@ def step_impl(context, userName, transactionAlias, orderer, channelId):
234234
def step_impl(context, userName, certAlias, composeService):
235235
directory = bootstrap_util.getDirectory(context)
236236
user = directory.getUser(userName=userName)
237-
nodeAdminTuple = user.tags[certAlias]
238-
cert = directory.findCertForNodeAdminTuple(nodeAdminTuple)
239-
user.connectToDeliverFunction(context, composeService, certAlias, nodeAdminTuple=nodeAdminTuple)
237+
user.connectToDeliverFunction(context, composeService, nodeAdminTuple=user.tags[certAlias])
240238

241239
@when(u'user "{userName}" sends deliver a seek request on orderer "{composeService}" with properties')
242240
def step_impl(context, userName, composeService):

bddtests/steps/bootstrap_util.py

+62-11
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,13 @@ def createCertificate(req, issuerCertKey, serial, validityPeriod, digest="sha256
162162
# SUBJECT_DEFAULT = {countryName : "US", stateOrProvinceName : "NC", localityName : "RTP", organizationName : "IBM", organizationalUnitName : "Blockchain"}
163163

164164
class Entity:
165-
def __init__(self, name):
165+
def __init__(self, name, ecdsaSigningKey, rsaSigningKey):
166166
self.name = name
167167
# Create a ECDSA key, then a crypto pKey from the DER for usage with cert requests, etc.
168-
self.ecdsaSigningKey = createECDSAKey()
169-
self.rsaSigningKey = createRSAKey()
170-
self.pKey = crypto.load_privatekey(crypto.FILETYPE_ASN1, self.ecdsaSigningKey.to_der())
168+
self.ecdsaSigningKey = ecdsaSigningKey
169+
self.rsaSigningKey = rsaSigningKey
170+
if self.ecdsaSigningKey:
171+
self.pKey = crypto.load_privatekey(crypto.FILETYPE_ASN1, self.ecdsaSigningKey.to_der())
171172
# Signing related ecdsa config
172173
self.hashfunc = hashlib.sha256
173174
self.sigencode = ecdsa.util.sigencode_der_canonize
@@ -200,10 +201,16 @@ def verifySignature(self, signature, signersCert, data):
200201
def getPrivateKeyAsPEM(self):
201202
return self.ecdsaSigningKey.to_pem()
202203

204+
def __getstate__(self):
205+
state = dict(self.__dict__)
206+
del state['ecdsaSigningKey']
207+
del state['rsaSigningKey']
208+
del state['pKey']
209+
return state
203210

204211
class User(Entity, orderer_util.UserRegistration):
205-
def __init__(self, name, directory):
206-
Entity.__init__(self, name)
212+
def __init__(self, name, directory, ecdsaSigningKey, rsaSigningKey):
213+
Entity.__init__(self, name, ecdsaSigningKey=ecdsaSigningKey, rsaSigningKey=rsaSigningKey)
207214
orderer_util.UserRegistration.__init__(self, name, directory)
208215
self.tags = {}
209216

@@ -222,8 +229,8 @@ def cleanup(self):
222229

223230
class Organization(Entity):
224231

225-
def __init__(self, name):
226-
Entity.__init__(self, name)
232+
def __init__(self, name, ecdsaSigningKey, rsaSigningKey):
233+
Entity.__init__(self, name, ecdsaSigningKey, rsaSigningKey)
227234
req = createCertRequest(self.pKey, C="US", ST="North Carolina", L="RTP", O="IBM", CN=name)
228235
numYrs = 1
229236
self.signedCert = createCertificate(req, (req, self.pKey), 1000, (0, 60 * 60 * 24 * 365 * numYrs), isCA=True)
@@ -265,21 +272,23 @@ def addToNetwork(self, network):
265272

266273
class Directory:
267274
def __init__(self):
275+
import atexit
268276
self.organizations = {}
269277
self.users = {}
270278
self.ordererAdminTuples = {}
279+
atexit.register(self.cleanup)
271280

272281
def getNamedCtxTuples(self):
273282
return self.ordererAdminTuples
274283

275284
def _registerOrg(self, orgName):
276285
assert orgName not in self.organizations, "Organization already registered {0}".format(orgName)
277-
self.organizations[orgName] = Organization(orgName)
286+
self.organizations[orgName] = Organization(orgName, ecdsaSigningKey = createECDSAKey(), rsaSigningKey = createRSAKey())
278287
return self.organizations[orgName]
279288

280289
def _registerUser(self, userName):
281290
assert userName not in self.users, "User already registered {0}".format(userName)
282-
self.users[userName] = User(userName, directory=self)
291+
self.users[userName] = User(userName, directory=self, ecdsaSigningKey = createECDSAKey(), rsaSigningKey = createRSAKey())
283292
return self.users[userName]
284293

285294
def getUser(self, userName, shouldCreate=False):
@@ -353,6 +362,48 @@ def registerOrdererAdminTuple(self, userName, ordererName, organizationName):
353362
self.ordererAdminTuples[ordererAdminTuple] = userCert
354363
return ordererAdminTuple
355364

365+
def dump(self, output):
366+
'Will dump the directory to the provided store'
367+
import cPickle
368+
data = {'users' : {}, 'organizations' : {}, 'nats' : {}}
369+
dump_cert = lambda cert: crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
370+
for userName, user in self.users.iteritems():
371+
# for k, v in user.tags.iteritems():
372+
# try:
373+
# cPickle.dumps(v)
374+
# except:
375+
# raise Exception("Failed on key {0}".format(k))
376+
data['users'][userName] = (user.ecdsaSigningKey.to_pem(), crypto.dump_privatekey(crypto.FILETYPE_PEM, user.rsaSigningKey), user.tags)
377+
for orgName, org in self.organizations.iteritems():
378+
networks = [n.name for n in org.networks]
379+
data['organizations'][orgName] = (
380+
org.ecdsaSigningKey.to_pem(), crypto.dump_privatekey(crypto.FILETYPE_PEM, org.rsaSigningKey),
381+
dump_cert(org.getSelfSignedCert()), networks)
382+
for nat, cert in self.ordererAdminTuples.iteritems():
383+
data['nats'][nat] = dump_cert(cert)
384+
cPickle.dump(data, output)
385+
386+
def initFromPath(self, path):
387+
'Will initialize the directory from the path supplied'
388+
import cPickle
389+
data = None
390+
with open(path,'r') as f:
391+
data = cPickle.load(f)
392+
assert data != None, "Expected some data, did not load any."
393+
priv_key_from_pem = lambda x: crypto.load_privatekey(crypto.FILETYPE_PEM, x)
394+
for userName, keyTuple in data['users'].iteritems():
395+
self.users[userName] = User(userName, directory=self,
396+
ecdsaSigningKey=ecdsa.SigningKey.from_pem(keyTuple[0]),
397+
rsaSigningKey=priv_key_from_pem(keyTuple[1]))
398+
self.users[userName].tags = keyTuple[2]
399+
for orgName, tuple in data['organizations'].iteritems():
400+
org = Organization(orgName, ecdsaSigningKey=ecdsa.SigningKey.from_pem(keyTuple[0]),
401+
rsaSigningKey=priv_key_from_pem(keyTuple[0]))
402+
org.signedCert = crypto.load_certificate(crypto.FILETYPE_PEM, tuple[2])
403+
org.networks = [Network[name] for name in tuple[3]]
404+
self.organizations[orgName] = org
405+
for nat, cert_as_pem in data['nats'].iteritems():
406+
self.ordererAdminTuples[nat] = crypto.load_certificate(crypto.FILETYPE_PEM, cert_as_pem)
356407

357408
class AuthDSLHelper:
358409
@classmethod
@@ -1075,7 +1126,7 @@ def get_latest_configuration_block(deliverer_stream_helper, channel_id):
10751126
deliverer_stream_helper.seekToRange(chainID=channel_id, start=last_config.index, end=last_config.index)
10761127
blocks = deliverer_stream_helper.getBlocks()
10771128
assert len(blocks) == 1, "Expected single block, received: {0} blocks".format(len(blocks))
1078-
assert len(block.data.data) == 1, "Expected single transaction for configuration block, instead found {0} transactions".format(len(block.data.data))
1129+
assert len(blocks[0].data.data) == 1, "Expected single transaction for configuration block, instead found {0} transactions".format(len(block.data.data))
10791130
latest_config_block = blocks[0]
10801131
return latest_config_block
10811132

bddtests/steps/compose.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def issueCommand(self, command, components=[]):
193193
return output
194194

195195
def rebuildContainerData(self):
196-
self.containerDataList = []
196+
self.containerDataList[:] = []
197197
for containerID in self.refreshContainerIDs():
198198

199199
# get container metadata

bddtests/steps/contexthelper.py

+21
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,27 @@
88
import shutil
99
from slugify import slugify
1010

11+
12+
class Context(object):
13+
def __init__(self):
14+
pass
15+
16+
def __setattr__(self, attr, value):
17+
self.__dict__[attr] = value
18+
19+
def __getattr__(self, attr):
20+
return self.__dict__[attr]
21+
# raise AttributeError(e)
22+
23+
def __getstate__(self):
24+
return self.__dict__
25+
26+
def __setstate__(self, value):
27+
return self.__dict__.update(value)
28+
29+
def __contains__(self, attr):
30+
return attr in self.__dict__
31+
1132
class ContextHelper:
1233

1334
@classmethod

bddtests/steps/docgen.py

+19
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ def registerCompositionAdvice(self, joinpoint):
137137
#Now get the config for the composition and dump out.
138138
self.composition = composition
139139
configAsYaml = composition.getConfig()
140+
(dokerComposeYmlFileName, fileExists) = self.contextHelper.getTmpPathForName(name="docker-compose", extension="yml")
141+
with open(dokerComposeYmlFileName, 'w') as f:
142+
f.write(configAsYaml)
143+
self.output.write(env.get_template("html/composition-py.html").render(compose_project_name= self.composition.projectName,docker_compose_yml_file=dokerComposeYmlFileName))
140144
self.output.write(env.get_template("html/header.html").render(text="Configuration", level=4))
141145
self.output.write(env.get_template("html/cli.html").render(command=configAsYaml))
142146
#Inject the graph
@@ -180,8 +184,23 @@ def registerUserAdvice(self, joinpoint):
180184
self.output.write(env.get_template("html/user.html").render(user=newlyRegisteredUser, private_key_href=self._getLinkInfoForFile(fileName)))
181185
return newlyRegisteredUser
182186

187+
def _dump_context(self):
188+
(dirPickleFileName, fileExists) = self.contextHelper.getTmpPathForName("dir", extension="pickle")
189+
with open(dirPickleFileName, 'w') as f:
190+
self.directory.dump(f)
191+
#Now the jinja output
192+
self.output.write(env.get_template("html/directory.html").render(directory=self.directory, path_to_pickle=dirPickleFileName))
193+
if self.composition:
194+
(dokerComposeYmlFileName, fileExists) = self.contextHelper.getTmpPathForName(name="docker-compose", extension="yml")
195+
self.output.write(env.get_template("html/appendix-py.html").render(directory=self.directory,
196+
path_to_pickle=dirPickleFileName,
197+
compose_project_name=self.composition.projectName,
198+
docker_compose_yml_file=dokerComposeYmlFileName))
199+
200+
183201
def afterScenarioAdvice(self, joinpoint):
184202
scenario = joinpoint.kwargs['scenario']
203+
self._dump_context()
185204
#Render with jinja
186205
header = env.get_template("html/scenario.html").render(scenario=scenario, steps=scenario.steps)
187206
main = env.get_template("html/main.html").render(header=header, body=self.output.getvalue())

bddtests/steps/orderer_util.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def handleNetworkError(self, networkError):
103103

104104
class DeliverStreamHelper(StreamHelper):
105105

106-
def __init__(self, ordererStub, entity, directory, nodeAdminTuple, timeout = 110):
106+
def __init__(self, ordererStub, entity, directory, nodeAdminTuple, timeout = 600):
107107
StreamHelper.__init__(self)
108108
self.nodeAdminTuple = nodeAdminTuple
109109
self.directory = directory
@@ -118,8 +118,6 @@ def createSeekInfo(self, chainID, start = 'Oldest', end = 'Newest', behavior =
118118
stop = seekPosition(end),
119119
behavior = ab_pb2.SeekInfo.SeekBehavior.Value(behavior),
120120
)
121-
print("SeekInfo = {0}".format(seekInfo))
122-
print("")
123121
return seekInfo
124122

125123
def seekToRange(self, chainID = TEST_CHAIN_ID, start = 'Oldest', end = 'Newest'):
@@ -138,7 +136,7 @@ def getBlocks(self):
138136
else:
139137
if reply.status != common_pb2.SUCCESS:
140138
print("Got error: {0}".format(reply.status))
141-
print("Done receiving blocks")
139+
# print("Done receiving blocks")
142140
break
143141
except Exception as e:
144142
print("getBlocks got error: {0}".format(e) )
@@ -163,7 +161,7 @@ def closeStreams(self):
163161
for compose_service, deliverStreamHelper in self.abDeliversStreamHelperDict.iteritems():
164162
deliverStreamHelper.sendQueue.put(None)
165163

166-
def connectToDeliverFunction(self, context, composeService, certAlias, nodeAdminTuple, timeout=1):
164+
def connectToDeliverFunction(self, context, composeService, nodeAdminTuple, timeout=1):
167165
'Connect to the deliver function and drain messages to associated orderer queue'
168166
assert not composeService in self.abDeliversStreamHelperDict, "Already connected to deliver stream on {0}".format(composeService)
169167
streamHelper = DeliverStreamHelper(directory=self.directory,
@@ -200,7 +198,7 @@ def getABStubForComposeService(self, context, composeService):
200198
# Get the IP address of the server that the user registered on
201199
root_certificates = self.directory.getTrustedRootsForOrdererNetworkAsPEM()
202200
ipAddress, port = bdd_test_util.getPortHostMapping(context.compose_containers, composeService, 7050)
203-
print("ipAddress in getABStubForComposeService == {0}:{1}".format(ipAddress, port))
201+
# print("ipAddress in getABStubForComposeService == {0}:{1}".format(ipAddress, port))
204202
channel = bdd_grpc_util.getGRPCChannel(ipAddress=ipAddress, port=port, root_certificates=root_certificates, ssl_target_name_override=composeService)
205203
newABStub = ab_pb2_grpc.AtomicBroadcastStub(channel)
206204
self.atomicBroadcastStubsDict[composeService] = newABStub

0 commit comments

Comments
 (0)