Coverage for backend.py: 84%

75 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-12 14:47 +0000

1# Flex Net Sim Backend API 

2# A Flask API for running Flex Net Sim network simulations 

3 

4from flask import Flask, request, Response, stream_with_context 

5from flask_cors import CORS 

6from utils.helpers import * 

7import time 

8import json 

9 

10# --- Flask Application Setup ---  

11app = Flask(__name__) 

12 

13# Enable CORS only for /run_simulation_stream 

14CORS(app, resources={r"/run_simulation_stream": {"origins": "*"}}) 

15 

16@app.route("/run_simulation", methods=["POST"]) 

17def run_simulation(): 

18 """ 

19 Executes a network simulation with the provided parameters. 

20  

21 Accepts a JSON request with simulation parameters and runs the C++ executable 

22 with those parameters. Returns the simulation results. 

23  

24 Returns: 

25 JSON response: Simulation data or error details 

26 """ 

27 # Validate prerequisites 

28 is_valid, error_response = validate_simulation_prerequisites() 

29 if not is_valid: 

30 compile_simulation(True) 

31 return error_response 

32 

33 try: 

34 data = request.get_json() 

35 

36 # Parse and validate parameters 

37 is_valid, result = parse_simulation_parameters(data) 

38 if not is_valid: 

39 return result 

40 

41 # Build and execute command 

42 command = build_simulation_command(result) 

43 logger.debug(f"Running simulation with command: {' '.join(command)}") 

44 

45 # Execute simulation 

46 process = subprocess.Popen( 

47 command, 

48 stdout=subprocess.PIPE, 

49 stderr=subprocess.PIPE, 

50 text=True 

51 ) 

52 stdout, stderr = process.communicate() 

53 

54 # Handle execution result 

55 if process.returncode != 0: 

56 logger.error(f"Simulation execution failed. Return code: {process.returncode}, Error: {stderr.strip()}") 

57 return jsonify({ 

58 "status": "error", 

59 "message": "Simulation execution failed", 

60 "error": stderr.strip() 

61 }), 500 

62 

63 # Return successful result 

64 return jsonify({ 

65 "status": "success", 

66 "data": stdout.strip() 

67 }), 200 

68 

69 except Exception as e: 

70 # Handle unexpected errors 

71 logger.exception("Unexpected error during simulation:") 

72 return jsonify({ 

73 "status": "error", 

74 "message": "An unexpected error occurred" 

75 }), 500 

76 

77@app.route("/run_simulation_stream", methods=["POST"]) 

78def run_simulation_stream(): 

79 """ 

80 Executes a network simulation with the provided parameters and streams the output. 

81 

82 Accepts a JSON request with simulation parameters and runs the C++ executable 

83 with those parameters. Returns a streaming response with simulation results 

84 as they become available. 

85 

86 Returns: 

87 Streaming response: Line-by-line simulation data 

88 """ 

89 # Validate prerequisites 

90 is_valid, error_response = validate_simulation_prerequisites() 

91 if not is_valid: 

92 compile_simulation(True) 

93 return error_response 

94 

95 try: 

96 data = request.get_json() 

97 

98 # Parse and validate parameters 

99 is_valid, result = parse_simulation_parameters(data) 

100 if not is_valid: 

101 return result 

102 

103 # Build command 

104 command = build_simulation_command(result) 

105 logger.debug(f"Running streaming simulation with command: {' '.join(command)}") 

106 

107 # Create streaming function 

108 def generate(): 

109 # Send initial event 

110 yield f"event: start\n" 

111 yield f"data: {json.dumps({'status': 'started', 'message': 'Simulation started', 'timestamp': time.time()})}\n\n" 

112 

113 # Execute simulation with streaming output 

114 process = subprocess.Popen( 

115 command, 

116 stdout=subprocess.PIPE, 

117 stderr=subprocess.PIPE, 

118 text=True, 

119 bufsize=1 # Line buffered 

120 ) 

121 

122 # Stream stdout 

123 for line in iter(process.stdout.readline, ""): 

124 if line: 

125 yield f"event: data\n" 

126 yield f"data: {json.dumps({'status': 'running', 'message': line.strip(), 'timestamp': time.time()})}\n\n" 

127 

128 # Check for errors at the end 

129 process.stdout.close() 

130 return_code = process.wait() 

131 

132 if return_code != 0: 

133 error = process.stderr.read().strip() 

134 yield f"event: error\n" 

135 yield f"data: {json.dumps({'status': 'error', 'message': 'Simulation execution failed', 'error': error, 'timestamp': time.time()})}\n\n" 

136 logger.error(f"Streaming simulation failed. Return code: {return_code}, Error: {error}") 

137 

138 # Close the stream resources 

139 process.stderr.close() 

140 

141 # Send completion event 

142 yield f"event: end\n" 

143 yield f"data: {json.dumps({'status': 'completed', 'message': 'Simulation completed', 'timestamp': time.time()})}\n\n" 

144 

145 return Response( 

146 stream_with_context(generate()), 

147 mimetype="text/event-stream", 

148 headers={ 

149 "Cache-Control": "no-cache", 

150 "X-Accel-Buffering": "no", 

151 "Connection": "keep-alive" 

152 } 

153 ) 

154 

155 except Exception as e: 

156 # Handle unexpected errors 

157 logger.exception("Unexpected error during streaming simulation:") 

158 return jsonify({ 

159 "status": "error", 

160 "message": "An unexpected error occurred", 

161 "timestamp": time.time() 

162 }), 500 

163 

164@app.route("/help", methods=["GET"]) 

165def simulation_help(): 

166 """ 

167 Provides API documentation in plain text format. 

168  

169 Returns information about available endpoints, parameters, 

170 and example usage for the Flex Net Sim API. 

171  

172 Returns: 

173 Plain text response: API documentation 

174 """ 

175 

176 help_message = """\ 

177 Flex Net Sim API Documentation 

178 

179 ENDPOINTS: 

180 - /run_simulation (POST): Returns complete simulation results 

181 - /run_simulation_stream (POST): Streams results in real-time using Server-Sent Events 

182 

183 COMMON PARAMETERS (JSON body, all optional): 

184 algorithm: "FirstFit" or "BestFit" (default: "FirstFit") 

185 networkType: 1 for EON (default: 1) 

186 goalConnections: 1-10000000 (default: 100000) 

187 confidence: 0-1 (default: 0.05) 

188 lambdaParam: > 0 (default: 1.0) 

189 mu: > 0 (default: 10.0) 

190 network: "NSFNet", "Cost239", "EuroCore", "GermanNet", "UKNet" (default: "NSFNet") 

191 bitrate: "fixed-rate" or "flex-rate" (default: "fixed-rate") 

192 K: 1-6 (default: 3) 

193 

194 EXAMPLE - STANDARD REQUEST: 

195 curl -X POST -H "Content-Type: application/json" \\ 

196 -d '{"algorithm": "FirstFit", "goalConnections": 1000000, "lambdaParam": 120, "mu": 1}' \\ 

197 https://fns-api-cloud-run-787143541358.us-central1.run.app/run_simulation 

198 

199 EXAMPLE - STREAMING REQUEST: 

200 curl -X POST -H "Content-Type: application/json" \\ 

201 -d '{"algorithm": "FirstFit", "goalConnections": 1000000, "lambdaParam": 120, "mu": 1}' \\ 

202 https://fns-api-cloud-run-787143541358.us-central1.run.app/run_simulation_stream 

203 

204 RESPONSES: 

205 Standard (/run_simulation): 

206 Success (200): {"status": "success", "data": "simulation results..."} 

207 Invalid Parameters (400): {"status": "error", "message": "Invalid parameters", "error": "Details"} 

208 Error (500): {"status": "error", "message": "Error message", "error": "Details"} 

209 

210 Streaming (/run_simulation_stream): 

211 Success (200): Server-Sent Events (text/event-stream) 

212 event: start 

213 data: {"status": "started", "message": "Simulation started"} 

214 

215 event: data 

216 data: {"status": "running", "message": "Line of output"} 

217 

218 event: end 

219 data: {"status": "completed", "message": "Simulation completed"} 

220  

221 Invalid Parameters (400): {"status": "error", "message": "Invalid parameters", "error": "Details"} 

222 Error (500): {"status": "error", "message": "Error message", "error": "Details"} 

223 """ 

224 

225 return app.response_class( 

226 response=help_message, 

227 status=200, 

228 mimetype="text/plain" 

229 ) 

230 

231# --- Application Initialization --- 

232with app.app_context(): 

233 # Compile the simulation on startup 

234 compile_success = compile_simulation() 

235 

236# --- Main Entry Point --- 

237if __name__ == "__main__": 

238 if not compile_success: 

239 logger.error("Application startup failed: Compilation error. Check logs for details.") 

240 else: 

241 logger.info("Starting Flex Net Sim API server...") 

242 app.run(host="0.0.0.0")