cloudFPGA (cF) API  1.0
The documentation of the source code of cloudFPGA (cF)
tc_TcpTest.py
Go to the documentation of this file.
1 # *
2 # * Copyright 2016 -- 2021 IBM Corporation
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 # *****************************************************************************
18 # * @file : tc_TcpRecv.py
19 # * @brief : A single-threaded script to test the receive traffic on the
20 # * TCP connection of an FPGA module.
21 # *
22 # * System: : cloudFPGA
23 # * Component : cFp_BringUp/ROLE
24 # * Language : Python 3
25 # *
26 # *****************************************************************************
27 
28 # ### REQUIRED PYTHON PACKAGES ################################################
29 import argparse
30 import datetime
31 import errno
32 import netifaces as ni
33 import socket
34 import struct
35 import time
36 
37 # ### REQUIRED TESTCASE MODULES ###############################################
38 from tc_utils import *
39 
40 
41 def tcp_rx_loop(clientSock, serverSock, size, ip_da, tcp_dp, count, verbose=False):
42  """TCP Rx Single-Thread Ramp.
43  Requests the FPGA to open a new active port and expects to receive 'count' segments
44  of 'size' bytes. Each segment is made of the following repetitive pattern
45  '48692066726f6d200x464d4b553630210a' which decodes into the string "Hi from FMKU60\n".
46  :param clientSock The socket to send to.
47  :param serverSock The socket to receive from.
48  :param size The size of the expected segment.
49  :param ip_da The destination address of the host.
50  :param tcp_dp The active destination port number that the FPGA is requested to open.
51  :param count The number of segments to receive.
52  :param verbose Enables verbosity.
53  :return None"""
54  if verbose:
55  print("[INFO] Requesting the FPGA to send %d segments of %d bytes on TCP port number %d." % (count, size, tcp_dp))
56  nrErr = 0
57  loop = 0
58  totalReceivedBytes = 0
59 
60  # Set the client and server sockets non-blocking
61  # -----------------------------------------------
62  clientSock.settimeout(5)
63  clientSock.setblocking(False)
64  serverSock.settimeout(5)
65  serverSock.setblocking(False)
66 
67  # Request the test to generate a segment of length='size' and to send it to socket={ip_da, tcp_dp}
68  # by sending 'ip_da', 'tcp_dp' and 'size' to the FPGA.
69  # Turn the 'ip_da' into an unsigned int binary and 'tcp_dp' and 'size' into an unsigned short binary data.
70  reqMsgAsBytes = struct.pack(">IHH", ip_da, tcp_dp, size)
71  if verbose:
72  print("[DEBUG] >>> reqMsgAsBytes = %s" % reqMsgAsBytes)
73 
74  startTime = datetime.datetime.now()
75  while loop < count:
76  # SEND message length request to FPGA
77  # ------------------------------------
78  try:
79  clientSock.sendall(reqMsgAsBytes)
80  except socket.error as exception:
81  # Any exception
82  print("[EXCEPTION] Socket error while transmitting :: %s" % exception)
83  exit(1)
84  finally:
85  pass
86 
87  # RECEIVE message length bytes from FPGA
88  # ---------------------------------------
89  currRxByteCnt = 0
90  while currRxByteCnt < size:
91  try:
92  data = serverSock.recv(MTU)
93  except IOError as e:
94  # On non blocking connections - when there are no incoming data, error is going to be raised
95  # Some operating systems will indicate that using AGAIN, and some using WOULDBLOCK error code
96  # We are going to check for both - if one of them - that's expected, means no incoming data,
97  # continue as normal. If we got different error code - something happened
98  if e.errno != errno.EAGAIN and e.errno != errno.EWOULDBLOCK:
99  print('[ERROR] Socket reading error: {}'.format(str(e)))
100  exit(1)
101  # We just did not receive anything
102  if verbose:
103  print("[DEBUG] So far we received %d bytes out of %d." % (currRxByteCnt, size))
104  continue
105  except socket.error as exc:
106  # Any other exception
107  print("[EXCEPTION] Socket error while receiving :: %s" % exc)
108  exit(1)
109  else:
110  currRxByteCnt += len(data)
111  if verbose:
112  print("[INFO] Loop=%d | RxBytes=%d | RxData=%s\n" % (loop, len(data), data))
113  # [FIXME-TODO: assess the stream of received bytes]
114  finally:
115  pass
116  totalReceivedBytes += currRxByteCnt;
117  loop += 1
118  endTime = datetime.datetime.now()
119  elapseTime = endTime - startTime
120 
121  if totalReceivedBytes < 1000000:
122  print("[INFO] Received a total of %d bytes." % totalReceivedBytes)
123  elif totalReceivedBytes < 1000000000:
124  megaBytes = (totalReceivedBytes * 1.0) / (1024 * 1024 * 1.0)
125  print("[INFO] Received a total of %.1f MB." % megaBytes)
126  else:
127  gigaBytes = (totalReceivedBytes * 1.0) / (1024 * 1024 * 1024 * 1.0)
128  print("[INFO] Transferred a total of %.1f GB." % gigaBytes)
129 
130  bandwidth = (totalReceivedBytes * 8 * 1.0) / (elapseTime.total_seconds() * 1024 * 1024)
131  print("#####################################################")
132  if bandwidth < 1000:
133  print("#### TCP Rx DONE with bandwidth = %6.1f Mb/s ####" % bandwidth)
134  else:
135  bandwidth = bandwidth / 1000
136  print("#### TCP Rx DONE with bandwidth = %2.1f Gb/s ####" % bandwidth)
137  if (totalReceivedBytes != (size * count)):
138  print("#### [WARNING] TCP data loss = %.1f%%" % (1 - (totalReceivedBytes) / (size * count)))
139  print("#####################################################")
140  print()
141 
142 
143 
148 
149 # STEP-1: Parse the command line strings into Python objects
150 # -----------------------------------------------------------------------------
151 parser = argparse.ArgumentParser(description='A script to receive TCP data from an FPGA module.')
152 parser.add_argument('-fi', '--fpga_ipv4', type=str, default='',
153  help='The IPv4 address of the FPGA (a.k.a image_ip / e.g. 10.12.200.163)')
154 parser.add_argument('-ii', '--inst_id', type=int, default=0,
155  help='The instance ID assigned by the cloudFPGA Resource Manager (e.g. 42)')
156 parser.add_argument('-lc', '--loop_count', type=int, default=10,
157  help='The number of test runs (default is 10)')
158 parser.add_argument('-mi', '--mngr_ipv4', type=str, default='10.12.0.132',
159  help='The IPv4 address of the cloudFPGA Resource Manager (default is 10.12.0.132)')
160 parser.add_argument('-mp', '--mngr_port', type=int, default=8080,
161  help='The TCP port of the cloudFPGA Resource Manager (default is 8080)')
162 parser.add_argument('-sd', '--seed', type=int, default=-1,
163  help='The initial number to seed the pseudorandom number generator.')
164 parser.add_argument('-sz', '--size', type=int, default=-1,
165  help='The size of the datagram to receive.')
166 parser.add_argument('-un', '--user_name', type=str, default='',
167  help='A user-name as used to log in ZYC2 (.e.g \'fab\')')
168 parser.add_argument('-up', '--user_passwd', type=str, default='',
169  help='The ZYC2 password attached to the user-name')
170 parser.add_argument('-v', '--verbose', action="store_true",
171  help='Enable verbosity')
172 
173 args = parser.parse_args()
174 
175 if args.user_name == '' or args.user_passwd == '':
176  print("\n[WARNING] You must provide a ZYC2 user name and the corresponding password for this script to execute.\n")
177  exit(1)
178 
179 # STEP-2a: Retrieve the IP address of the FPGA module (this will be the SERVER)
180 # -----------------------------------------------------------------------------
181 ipFpga = getFpgaIpv4(args)
182 
183 # STEP-2b: Retrieve the instance Id assigned by the cloudFPGA Resource Manager
184 # -----------------------------------------------------------------------------
185 instId = getInstanceId(args)
186 
187 # STEP-2c: Retrieve the IP address of the cF Resource Manager
188 # -----------------------------------------------------------------------------
189 ipResMngr = getResourceManagerIpv4(args)
190 
191 # STEP-3a: Set the TCP listen port of the FPGA server (this one is static)
192 # -----------------------------------------------------------------------------
193 portFpgaServer = XMIT_MODE_LSN_PORT # 8801
194 
195 # STEP-3b: Retrieve the TCP port of the cloudFPGA Resource Manager
196 # -----------------------------------------------------------------------------
197 portResMngr = getResourceManagerPort(args)
198 
199 # STEP-4: Trigger the FPGA role to restart (i.e. perform SW reset of the role)
200 # -----------------------------------------------------------------------------
201 restartApp(instId, ipResMngr, portResMngr, args.user_name, args.user_passwd)
202 
203 # STEP-5: Ping the FPGA
204 # -----------------------------------------------------------------------------
205 pingFpga(ipFpga)
206 
207 # STEP-6a: Set the socket association for sending to the FPGA server
208 # -----------------------------------------------------------------------------
209 fpgaServerAssociation = (str(ipFpga), portFpgaServer)
210 
211 # STEP-6b: Create a TCP/IP client socket for sending to FPGA server
212 # -----------------------------------------------------------------------------
213 try:
214  tcpClientSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
215 except Exception as exc:
216  print("[EXCEPTION] %s" % exc)
217  exit(1)
218 
219 # STEP-6c: Connect the host client to the remote FPGA server.
220 # -----------------------------------------------------------------------------
221 try:
222  tcpClientSock.connect(fpgaServerAssociation)
223  print("[INFO] Connecting host client to FPGA server socket ", fpgaServerAssociation)
224 except Exception as exc:
225  print("[EXCEPTION] %s" % exc)
226  exit(1)
227 else:
228  print("[INFO] \tStatus --> Successful connection.")
229 
230 # STEP-7a: Set the socket association for listening from the FPGA client
231 # -----------------------------------------------------------------------------
232 hostname = socket.gethostname()
233 ipHostStr = socket.gethostbyname(hostname)
234 if ipHostStr.startswith('9.4.'):
235  # Use the IP address of 'tun0' (.e.g 10.2.0.18)
236  ipHostStr = ni.ifaddresses('tun0')[2][0]['addr']
237 ipHost = int(ipaddress.IPv4Address(ipHostStr))
238 if args.verbose:
239  print("[INFO] Hostname = %s | IP is %s (0x%8.8X)" % (hostname, ipHostStr, ipHost))
240 dpHost = 2718 # Default TCP cF-Themisto ports are in range 2718-2750
241 hostListenAssociation = (str(ipHostStr), dpHost)
242 
243 # STEP-7b: Create a TCP/IP socket for listening to FPGA client(s) and allow its re-use
244 # -----------------------------------------------------------------------------
245 try:
246  tcpListenSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
247 except Exception as exc:
248  print("[EXCEPTION] %s" % exc)
249  exit(1)
250 tcpListenSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
251 
252 # STEP-7c: Bind the listen socket of the HOST (compulsory) and start listening on it.
253 # bind() -> associates the listen socket with a specific IP address and TCP port.
254 # listen() -> puts the listening socket in server mode.
255 # -----------------------------------------------------------------------------
256 try:
257  tcpListenSock.bind(hostListenAssociation)
258  print("[INFO] Binding and start listening on host socket ", hostListenAssociation)
259 except Exception as exc:
260  print("[EXCEPTION] %s" % exc)
261  exit(1)
262 tcpListenSock.listen(1)
263 
264 # STEP-8: Request the remote FPGA to open a new connection. This will trigger the 3-way
265 # handshake connection with the listening socket and will provide us with a new TCP
266 # socket for the server to communicate with the FPGA.
267 # -----------------------------------------------------------------------------
268 ipHost = int(ipaddress.IPv4Address(ipHostStr))
269 reqMsgAsBytes = struct.pack(">IHH", ipHost, dpHost, 0)
270 print("[INFO] Requesting the remote FPGA client to open a connection with the host")
271 print("[DEBUG] With message = reqMsgAsBytes = %s" % reqMsgAsBytes)
272 try:
273  tcpClientSock.sendall(reqMsgAsBytes)
274 except socket.error as exception:
275  # Any exception
276  print("[EXCEPTION] Socket error while transmitting :: %s" % exception)
277  exit(1)
278 finally:
279  pass
280 
281 # STEP-9: Block and wait for incoming connection from remote FPGA client(s).
282 # -----------------------------------------------------------------------------
283 print("[INFO] Waiting for a connection from remote FPGA")
284 tcpServerSock, fpgaClientAssociation = tcpListenSock.accept()
285 print("[INFO] Received a connection from FPGA socket address ", fpgaClientAssociation)
286 print()
287 
288 # STEP-10: Setup the test
289 # -------------------------------
290 print("[INFO] Testcase `%s` is run with:" % (os.path.basename(__file__)))
291 if 1:
292  seed = args.seed
293  if seed == -1:
294  seed = random.randint(0, 100000)
295  random.seed(seed)
296  print("\t\t seed = %d" % seed)
297 
298  size = args.size
299  if size == -1:
300  size = random.randint(1, 0xFFFF)
301  elif size > 0xFFFF:
302  print('\nERROR: ')
303  print("[ERROR] This test limits the size of the received segments to %d bytes.\n" % 0xFFFF)
304  exit(1)
305  print("\t\t size = %d" % size)
306 
307  count = args.loop_count
308  print("\t\t loop = %d" % count)
309 
310  verbose = args.verbose
311 
312  # STEP-11: Run the test
313  # -------------------------------
314  print("[INFO] This run is executed in single-threading mode.\n")
315  tcp_rx_loop(tcpClientSock, tcpServerSock, size, ipHost, dpHost, count, args.verbose)
316 
317 # STEP-14: Close sockets
318 # -----------------------
319 tcpClientSock.close()
320 tcpServerSock.close()
321 tcpListenSock.close()
def tcp_rx_loop(clientSock, serverSock, size, ip_da, tcp_dp, count, verbose=False)
Definition: tc_TcpTest.py:41
def restartApp(instId, ipResMngr, portResMngr, user_name, user_passwd)
Definition: tc_utils.py:174
def getFpgaIpv4(args)
Definition: tc_utils.py:95
def getInstanceId(args)
Definition: tc_utils.py:153
def getResourceManagerIpv4(args)
Definition: tc_utils.py:124
def pingFpga(ipFpga)
Definition: tc_utils.py:199
def getResourceManagerPort(args)
Definition: tc_utils.py:142