Skip to content

Commit e94e00a

Browse files
authored
Merge pull request #18 from SmilingPixel/dev/schedule_feat_0401
Dev/schedule feat 0401
2 parents 7636f02 + 9d7c6fe commit e94e00a

61 files changed

Lines changed: 4559 additions & 1003 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.vscode/launch.json

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,36 @@
1+
// This file configures debugging settings for the project.
2+
// It defines multiple configurations for launching the Go application with different arguments.
3+
// Each configuration specifies the program to run, arguments to pass, and the working directory.
4+
15
{
26
// Use IntelliSense to learn about possible attributes.
37
// Hover to view descriptions of existing attributes.
48
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
59
"version": "0.2.0",
610
"configurations": [
711
{
8-
"name": "Launch Package",
12+
"name": "OpenTelemetry Demo", // Configuration for debugging the OpenTelemetry demo
13+
"type": "go", // Specifies that this is a Go application
14+
"request": "launch", // Indicates that the program should be launched
15+
"mode": "auto", // Automatically determines the mode (debug or run)
16+
"program": "${workspaceFolder}/cmd/api-fuzzer", // Path to the main Go program
17+
"args": [
18+
"--config-file",
19+
"${workspaceFolder}/config/my_config.json", // Path to the configuration file
20+
"--log-level",
21+
"debug" // Sets the log level to debug
22+
],
23+
"cwd": "${workspaceFolder}", // This is the working directory, which is "" by default when launching
24+
},
25+
{
26+
"name": "Train Ticket", // Configuration for debugging the Train Ticket system
927
"type": "go",
1028
"request": "launch",
1129
"mode": "auto",
1230
"program": "${workspaceFolder}/cmd/api-fuzzer",
1331
"args": [
1432
"--config-file",
15-
"${workspaceFolder}/config/my_config.json",
33+
"${workspaceFolder}/config/my_tt_config.json", // Path to the Train Ticket configuration file
1634
"--log-level",
1735
"debug"
1836
],

.vscode/tasks.json

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
{
22
// See https://go.microsoft.com/fwlink/?LinkId=733558
33
// for the documentation about the tasks.json format
4+
// This file defines tasks for automating common actions in the project.
5+
// It uses Makefile commands to build, run, and clean the project.
6+
// Tasks can be executed directly from the VS Code interface.
47
"version": "2.0.0",
58

6-
// Reuse make commands to build, run, and clean the project
79
"tasks": [
810
{
9-
"type": "shell",
10-
"label": "Build",
11-
"command": "make build",
11+
"type": "shell", // Specifies that the task runs a shell command
12+
"label": "Build", // Label displayed in the VS Code task list
13+
"command": "make build", // Command to build the project
1214
},
1315
{
1416
"type": "shell",
15-
"label": "Run",
16-
"command": "make run",
17+
"label": "Run on config", // Task to run the project with a specific configuration
18+
"command": "make run CONFIG_FILE=./config/custom_config.json", // Example usage of the CONFIG_FILE variable
1719
},
1820
{
1921
"type": "shell",
20-
"label": "Clean",
21-
"command": "make clean",
22+
"label": "Clean", // Task to clean the project (both build and output)
23+
"command": "make clean", // Command to clean the project
2224
}
23-
]
25+
],
26+
27+
"cwd": "${workspaceFolder}" // Sets the working directory for the tasks
2428
}

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ build:
1717
# Run the project
1818
.PHONY: run
1919
run:
20-
bash scripts/run.sh "$(BUILD_DIR)/$(BINARY)"
20+
bash scripts/run.sh "$(BUILD_DIR)/$(BINARY)" "$(CONFIG_FILE)"
21+
22+
# Example usage:
23+
# make run CONFIG_FILE=./config/custom_config.json
2124

2225
# Clean the program output
2326
.PHONY: clean-output

README.md

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This project is a tool designed to test a microservice system. The most novel pa
1010
- [Dependencies](#dependencies)
1111
- [Installation](#installation)
1212
- [Preparation](#preparation)
13+
- [Integration with other tools](#Integration-with-other-tools)
1314
- [Usage](#usage)
1415
- [Configuration](#configuration)
1516
- [License](#license)
@@ -53,10 +54,51 @@ To install the tool, follow these steps:
5354
## Preparation
5455

5556
1. Prepare the OpenAPI specification file for the system under test. The tool supports OpenAPI 3 by default.
56-
2. Prepare a protobuf file for all RPC which internal services use.
57+
2. Prepare a protobuf file for all RPC which internal services use, or the OpenAPI specification file for all REST APIs which internal services use.
5758
- We use [protoc-gen-openapi](https://github.com/google/gnostic/tree/main/cmd/protoc-gen-openapi) to convert protobuf to openapi.
5859
- You should annotate the proto file, and you can refer to this [issue](https://github.com/google/gnostic/issues/412).
59-
3. Ensure that your service includes a unique trace ID in the headers of each response. You can configure the header key in the settings.
60+
- If you use OpenAPI spec, you should ensure that the OpenAPI spec is in the same format as a file generated by the tool (including `operationId`). Note that you must add tag `APIType_HTTP` to all HTTP APIs, as we treat internal service APIs as RPC APIs by default. You can refer to the example at the end of this section.
61+
3. Optionally, you can provide API dependencies to enhance fuzzing, including system API dependencies and internal service API dependencies (a map from service name to single service dependencies). We support parse dependency file generated by RESTler now, you can refer to its [Github Repo](https://github.com/microsoft/restler-fuzzer) for more information.
62+
4. Ensure that your service includes a unique trace ID in the headers of each response. You can configure the header key in the settings.
63+
64+
Internal service API Spec example:
65+
```yaml
66+
/v1/cart/add:
67+
post:
68+
tags:
69+
- CartService
70+
- APIType_gRPC
71+
operationId: CartService_AddItem
72+
requestBody:
73+
content:
74+
application/json:
75+
schema:
76+
$ref: '#/components/schemas/AddItemRequest'
77+
required: true
78+
responses:
79+
"200":
80+
description: OK
81+
content:
82+
application/json:
83+
schema:
84+
$ref: '#/components/schemas/Empty'
85+
default:
86+
description: Default error response
87+
content:
88+
application/json:
89+
schema:
90+
$ref: '#/components/schemas/Status'
91+
```
92+
93+
## Integration with Other Tools
94+
95+
### Restler
96+
97+
Restler is a tool designed to generate REST API test cases from OpenAPI specifications. During the compilation process, it produces a dependency file for the system's APIs. Our tool can parse this dependency file to enhance the fuzzing process. For more details, refer to the `--dependency-file` and `--dependency-file-type` options.
98+
99+
### CoSREST
100+
101+
CoSREST is a tool that leverages Large Language Models (LLMs) to generate REST API test cases from OpenAPI specifications. It enhances test case generation by using LLMs to make decisions on input space partitioning (e.g., parameter generation), which improves both efficiency and effectiveness. Our tool supports parsing the space partition file generated by CoSREST and converts it into a fuzz value dictionary. This helps the fuzzer create more diverse and valid test cases. For more details, refer to our [script](scripts/CoSREST/convert_sisp_to_dict.py)
60102
61103
## Usage
62104
@@ -88,32 +130,41 @@ In addition, we provide vscode tasks to use the tool. You can build, run and deb
88130
89131
The tool can be configured using command-line arguments. The following options are available:
90132
91-
- `--openapi-spec`: Path to the OpenAPI specification file.
92-
- `--server-base-url`: Base URL of the server to test.
93-
- `--internal-service-openapi-spec`: Path to the internal service OpenAPI specification file.
94-
- `--trace-backend-url`: URL of the trace backend.
95-
- `--trace-backend-type`: Type of the trace backend. Currently only supports 'Jaeger'.
96-
- `--fuzzer-type`: Type of fuzzer to use (e.g., Basic).
97-
- `--fuzzer-budget`: Time budget for fuzzing (e.g., 30s).
98-
- `--log-level`: Log level: debug, info, warn, error, fatal, panic.
99-
- `--output-dir`: Directory to save the output reports.
100133
- `--config-file`: Path to the config file. If an argument is provided in both the config file and command line, the config file argument will be used.
101134
- `--dependency-file`: Path to the dependency file generated by other tools or manually.
102-
- `--dependency-file-type`: Type of the dependency file. Currently only supports 'Restler'.
103-
- `--extra-headers`: Extra headers to be added to the request, in the format of stringified JSON, e.g., '{"header1": "value1", "header2": "value2"}'. If you want to add complex logic or modify the parameters and body, you can use the `--http-middleware-script` argument.
104-
- `--fuzz-value-dict-file`: Path to the file containing the dictionary of fuzz values, in the format of a JSON list. Each element in the list is a dictionary with two key-value pairs, one is `name` (value is of type string) and the other is `value` (value can be any JSON).
105-
- `--log-to-file`: Should log to file.
106-
- `--trace-id-header-key`: The response header key to be used for trace ID.
107-
- `--http-middleware-script`: Path to the script file that contains the HTTP middleware functions, see [HTTP Middleware Script](#about-http-middleware-script).
108-
109-
You can also use a configuration file with `--config-file` option to set the options. The configuration file should be in JSON format, we provide an example configuration file [here](configs/config.json).
110-
111-
In addition, for token, private key, or other sensitive information, you can use environment variables. You can set an environment variable with the same name as that in configuration file (but in uppercase) to override the value in the configuration file. For example, you can set `SERVER_BASE_URL` in `.env` file:
135+
- `--dependency-file-type`: Type of the dependency file. Currently only supports 'Restler'. Required if `--dependency-file` is provided.
136+
- `--enable-energy-operation`: Enable energy (priority) of test operations. If true, energy affects the test operation selection when extending the test scenario.
137+
- `--enable-energy-scenario`: Enable energy (priority) of test scenarios. If true, energy affects the test scenario selection when starting a new test loop.
138+
- `--extra-headers`: Extra headers to be added to the request, in the format of stringified JSON, e.g., `{"header1": "value1", "header2": "value2"}`.
139+
- `--fuzz-value-dict-file`: Path to the file containing the dictionary of fuzz values, in JSON format. Each element is a dictionary with `name` (string) and `value` (any JSON).
140+
- `--fuzzer-budget`: The maximum time the fuzzer can run, in seconds (default: 5).
141+
- `--fuzzer-type`: Type of the fuzzer. Currently only supports 'Basic' (default: Basic).
142+
- `--http-client-dial-timeout`: Timeout for the HTTP client dial, in seconds (default: 30).
143+
- `--http-middleware-script`: Path to the script file that contains the HTTP middleware functions.
144+
- `--internal-service-api-dependency-file`: Path to the internal service API dependency file generated by other tools or manually. It should be a map of service name to a list of API dependencies.
145+
- `--internal-service-openapi-spec`: Path to the internal service OpenAPI specification file (required).
146+
- `--log-level`: Log level: debug, info, warn, error, fatal, panic (default: info).
147+
- `--log-to-file`: Whether to log to a file (default: false).
148+
- `--max-ops-per-scenario`: Maximum number of operations to execute in each scenario (default: 1).
149+
- `--max-allowed-operation-case-executed-count`: Maximum number of times a test operation case can be executed (default: 14).
150+
- `--max-allowed-operation-cases`: Maximum number of test operation cases in the queue of an API method (default: 7).
151+
- `--max-allowed-scenario-executed-count`: Maximum number of times a test scenario can be executed (default: 6).
152+
- `--max-allowed-scenarios`: Maximum number of test scenarios in the queue (default: 114).
153+
- `--openapi-spec`: Path to the OpenAPI specification file (required).
154+
- `--output-dir`: Directory to save the output reports (default: ./output).
155+
- `--server-base-url`: Base URL of the server to test (default: https://www.example.com).
156+
- `--trace-backend-type`: Type of the trace backend. Currently supports 'Jaeger' and 'Tempo' (default: Jaeger).
157+
- `--trace-backend-url`: URL of the trace backend (required).
158+
- `--trace-id-header-key`: The response header key to be used for trace ID (default: X-Trace-Id).
159+
- `--use-internal-service-api-dependency`: Indicates whether to use the internal service API dependency. If true, the internal service API dependency will be used to enhance the external service API dependency.
160+
161+
You can also use a configuration file with the `--config-file` option to set the options. The configuration file should be in JSON format. We provide an example configuration file [here](configs/config.json).
162+
163+
For sensitive information like tokens or private keys, you can use environment variables. Set an environment variable with the same name as the configuration file key (in uppercase) to override the value in the configuration file. For example, you can set `SERVER_BASE_URL` in a `.env` file:
112164
```sh
113165
SERVER_BASE_URL=http://localhost:6789
114166
```
115167
116-
117168
## About HTTP Middleware Script
118169
119170
The HTTP Middleware Script allows you to intercept and modify HTTP requests and responses using a Starlark script. The script can modify headers, path parameters, query parameters, and the body of the request.

cmd/api-fuzzer/main.go

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"resttracefuzzer/pkg/parser"
1212
"resttracefuzzer/pkg/report"
1313
"resttracefuzzer/pkg/resource"
14+
fuzzruntime "resttracefuzzer/pkg/runtime"
1415
"resttracefuzzer/pkg/static"
1516
"resttracefuzzer/pkg/strategy"
1617
"time"
@@ -81,26 +82,55 @@ func main() {
8182
}
8283
log.Info().Msgf("[main] Log to file is enabled, I will write logs to %s", logFilePath)
8384
log.Logger = log.Output(fileWriter)
85+
86+
// log config again to file
87+
configStr, _ := sonic.MarshalString(config.GlobalConfig)
88+
log.Info().Msgf("[main] Fuzzer config: %s", configStr)
8489
}
8590

8691
APIManager := static.NewAPIManager()
8792

88-
// read OpenAPI spec and parse it
93+
// read system OpenAPI spec and parse it
8994
APIParser := parser.NewOpenAPIParser()
90-
doc, err := APIParser.ParseSystemDocFromPath(config.GlobalConfig.OpenAPISpecPath)
95+
systemDoc, err := APIParser.ParseSystemDocFromPath(config.GlobalConfig.OpenAPISpecPath)
9196
if err != nil {
92-
log.Err(err).Msgf("[main] Failed to parse OpenAPI spec")
97+
log.Err(err).Msgf("[main] Failed to parse system OpenAPI spec")
9398
return
9499
}
95-
APIManager.InitFromSystemDoc(doc)
96100

97101
// Parse doc of internal services
98102
serviceDoc, err := APIParser.ParseServiceDocFromPath(config.GlobalConfig.InternalServiceOpenAPIPath)
99103
if err != nil {
100104
log.Err(err).Msgf("[main] Failed to parse internal service OpenAPI spec")
101105
return
102106
}
103-
APIManager.InitFromServiceDoc(serviceDoc)
107+
108+
// Initialize the API manager using parsed docs
109+
APIManager.InitFromDocs(systemDoc, serviceDoc)
110+
111+
// Read API dependency files
112+
// You can generate the dependency files by running tools we support (e.g., Restler)
113+
if config.GlobalConfig.DependencyFileType != "" {
114+
dependencyFileParser, err := parser.NewAPIDependencyParserByType(config.GlobalConfig.DependencyFileType)
115+
if err != nil {
116+
log.Err(err).Msgf("[main] Failed to create dependency file parser, type: %s", config.GlobalConfig.DependencyFileType)
117+
return
118+
}
119+
dependencyGraph, err := dependencyFileParser.ParseFromFile(config.GlobalConfig.DependencyFilePath)
120+
if err != nil {
121+
log.Err(err).Msgf("[main] Failed to parse dependency file, path: %s", config.GlobalConfig.DependencyFilePath)
122+
return
123+
}
124+
var internalServiceAPIDependencyGraphMap map[string]*static.APIDependencyGraph
125+
if config.GlobalConfig.InternalServiceAPIDependencyFilePath != "" {
126+
internalServiceAPIDependencyGraphMap, err = dependencyFileParser.ParseFromServiceMapFile(config.GlobalConfig.InternalServiceAPIDependencyFilePath)
127+
if err != nil {
128+
log.Err(err).Msgf("[main] Failed to parse internal service API dependency map file, path: %s", config.GlobalConfig.InternalServiceAPIDependencyFilePath)
129+
return
130+
}
131+
}
132+
APIManager.InitDependencyGraph(dependencyGraph, internalServiceAPIDependencyGraphMap)
133+
}
104134

105135
// Parse extra headers
106136
extraHeaders := make(map[string]string)
@@ -124,29 +154,16 @@ func main() {
124154
}
125155
fuzzStrategist := strategy.NewFuzzStrategist(resourceManager)
126156
resourceMutateStrategist := strategy.NewResourceMutateStrategy()
127-
caseManager := casemanager.NewCaseManager(APIManager, resourceManager, fuzzStrategist, resourceMutateStrategist, extraHeaders)
128157
responseProcesser := feedback.NewResponseProcesser(APIManager, resourceManager)
129-
runTimeGraph := feedback.NewRuntimeGraph(APIManager.APIDataflowGraph)
130-
131-
// Read API dependency files
132-
// You can generate the dependency files by running Restler
133-
// We only parse Restler's output for now
134-
// TODO: parse other dependency files @xunzhou24
135-
var dependencyFileParser parser.APIDependencyParser
136-
if config.GlobalConfig.DependencyFileType != "" {
137-
if config.GlobalConfig.DependencyFileType == "Restler" {
138-
dependencyFileParser = parser.NewAPIDependencyRestlerParser()
139-
} else {
140-
log.Err(err).Msgf("[main] Unsupported dependency file type: %s", config.GlobalConfig.DependencyFileType)
141-
return
142-
}
143-
dependecyGraph, err := dependencyFileParser.ParseFromPath(config.GlobalConfig.DependencyFilePath)
144-
if err != nil {
145-
log.Err(err).Msgf("Failed to parse dependency file")
146-
return
147-
}
148-
APIManager.APIDependencyGraph = dependecyGraph
158+
traceDBs := make([]trace.TraceDB, 0) // traceDBs is a list of trace databases, used to store traces
159+
if config.GlobalConfig.SaveRawTrace {
160+
saveDir := fmt.Sprintf("%s/raw_trace_%s", config.GlobalConfig.OutputDir, t.Format(outputFileTimeFormat))
161+
traceDBs = append(traceDBs, trace.NewRawTraceFileSaver(saveDir))
149162
}
163+
traceManager := trace.NewTraceManager(traceDBs)
164+
callInfoGraph := fuzzruntime.NewCallInfoGraph(APIManager.APIDataflowGraph)
165+
reachabilityMap := fuzzruntime.NewRuntimeReachabilityMapFromStaticMap(APIManager.StaticReachabilityMap)
166+
caseManager := casemanager.NewCaseManager(APIManager, resourceManager, fuzzStrategist, resourceMutateStrategist, reachabilityMap, extraHeaders)
150167

151168
// testLogReporter logs the tested operations
152169
testLogReporter := report.NewTestLogReporter()
@@ -158,8 +175,9 @@ func main() {
158175
APIManager,
159176
caseManager,
160177
responseProcesser,
161-
trace.NewTraceManager(),
162-
runTimeGraph,
178+
traceManager,
179+
callInfoGraph,
180+
reachabilityMap,
163181
testLogReporter,
164182
)
165183
} else {
@@ -191,7 +209,11 @@ func main() {
191209
}
192210
internalServiceReporter := report.NewInternalServiceReporter()
193211
internalServiceReportPath := fmt.Sprintf("%s/internal_service_report_%s.json", config.GlobalConfig.OutputDir, t.Format(outputFileTimeFormat))
194-
err = internalServiceReporter.GenerateInternalServiceReport(mainFuzzer.GetRuntimeGraph(), internalServiceReportPath)
212+
err = internalServiceReporter.GenerateInternalServiceReport(
213+
mainFuzzer.GetCallInfoGraph(),
214+
reachabilityMap,
215+
internalServiceReportPath,
216+
)
195217
if err != nil {
196218
log.Err(err).Msgf("[main] Failed to generate internal service report")
197219
return

0 commit comments

Comments
 (0)