Traffic Parrot needs to know about the schema of your messages.
One way to do this is to provide your protocol buffer files. We currently support the proto3 specification.
To do this, simply copy your .proto files into the trafficparrot-x.y.z/proto directory and Traffic Parrot will use these definitions during record/replay.
Alternatively, precompiled proto descriptor files can be provided in the trafficparrot-x.y.z/proto-bin directory
Traffic Parrot can discover the schema of your messages during recording if you have server reflection enabled in your gRPC server.
Discovered schemas are saved with extension .bin in the trafficparrot-x.y.z/proto-bin directory and are used during replay.
You can also use gRPCurl to manually export a proto .set.bin schema using:grpcurl -protoset-out proto.set.bin server-to-record:443 listTo enable server reflection please follow the instructions for your gRPC server language:
Typically, gRPC is authenticated using SSL/TLS
Traffic Parrot has the following properties that can be used to configure SSL/TLS:The URL property values can be set to classpath entries e.g. classpath:certificates/grpc/server.pem
By default Traffic Parrot has properties that correspond to the certificates found in the trafficparrot-x.y.z/certificates/grpc directory.
Alternatively, you can specify file URL entries e.g. file:///path/to/server.pem
The property trafficparrot.virtualservice.grpc.non.tls.port specifies a port that can be used if SSL/TLS is not required (default 5552).The following properties have a significant impact on gRPC performance during replay:
Property | Description |
---|---|
trafficparrot.virtualservice.grpc.server.replay.proto.cache.milliseconds | Proto files can be cached in memory during replay to improve performance. The default value of 0 means do not cache at all. |
trafficparrot.virtualservice.grpc.server.replay.proto.cache.populate.on.startup | When true the proto file cache will be populated immediately on startup. |
trafficparrot.virtualservice.mapping.cache.milliseconds | Mapping files can be cached in memory during replay to improve performance. The default value of 0 means do not cache at all. |
trafficparrot.virtualservice.mapping.cache.populate.on.startup | When true the mapping file cache will be populated immediately on startup. |
trafficparrot.virtualservice.grpc.server.boss.threads | The number of boss threads the server uses to process gRPC network requests. Typically best set to either DOUBLE_NUMBER_OF_PROCESSORS or NUMBER_OF_PROCESSORS based on the number of available processors the system has. |
trafficparrot.virtualservice.grpc.server.worker.threads | The number of worker threads the server uses to process gRPC network requests. Typically best set to either DOUBLE_NUMBER_OF_PROCESSORS or NUMBER_OF_PROCESSORS based on the number of available processors the system has. |
trafficparrot.virtualservice.grpc.server.receiveThreads | The number of background threads used to process gRPC requests and find mock responses once offloaded from the worker and boss threads. Typically best set to either DOUBLE_NUMBER_OF_PROCESSORS or NUMBER_OF_PROCESSORS based on the number of available processors the system has. |
trafficparrot.virtualservice.grpc.server.sendThreads | The number of background threads used to send the gRPC mock responses determined by the receive threads. Typically best set to either DOUBLE_NUMBER_OF_PROCESSORS or NUMBER_OF_PROCESSORS based on the number of available processors the system has. |
trafficparrot.virtualservice.grpc.server.replay.maxMessagesWaitingToBeSent | When the rate at which request messages are received exceeds the rate at which response messages can be produced (e.g. if there is complex response behaviour that takes time) then response messages will queue up waiting to be sent. This property controls the maximum number of messages in that queue and therefore how much memory is required. When the queue is full, requests will block waiting for the responses to catch up. |
During recording, incoming and outgoing messages are matched up to provide mappings. Then upon playback receipt of a matching incoming message will trigger generation of an outgoing message.
This diagram shows how two production systems connect:One system sends a gRPC request message to another system, which consumes the messages and returns a gRPC response message to the first system. If our goal is to test the system-under-test in isolation, we must record these interactions in order to replay them.
As you can see in the diagram below, the virtual service acts as a proxy between the gRPC client and gRPC server. The provided proto files are used to determine the message schema of the request and response messages for the requested gRPC method. Mappings are saved representing request/response pairs that have been recorded.
As the system under test generates messages and receives responses they are paired into mappings and listed in the 'Mappings' table.
On replay, the provided proto files are again used to determine the schema of the requested gRPC method. The request is matched against the mappings to find the corresponding response message.
As you can see in the diagram below, the virtual service acts as a proxy between the gRPC client and gRPC server. Server reflection is used to pull the proto files definitions from the gRPC server for the requested gRPC method. These proto files are saved for later use during replay. Mappings are saved representing request/response pairs that have been recorded.
As the system under test generates messages and receives responses they are paired into mappings and listed in the 'Mappings' table.
On replay, the recored proto files used to determine the schema of the requested gRPC method. The request is matched against the mappings to find the corresponding response message.
If there are existing mappings present before a recording starts, these mappings will not be used to return responses instead of recording a new response.
For example, if there is a mapping for service helloworld.Greeter/SayHello then any traffic to helloworld.Greeter/SayHello will record a new response and the existing mapping will not be considered.
Please contact support@trafficparrot.com if you have a requirement for a mixed recording mode similar to HTTP recording where existing mappings during recording are used to provide responses instead of recording a new response.
You can record multiple gRPC servers at the same time using the following syntax:
com.example.Service/method -> host1:port1 com.example.packageA.* -> host2:port2 com.example.packageB.* -> host3:port3
The incoming request service and method name are matched top down against the list of server pattern choices. The patterns are regular expressions, where .* can be used as a wildcard.
Traffic Parrot uses JSON to represent gRPC Protocol Buffers message payloads. It stores the gRPC Protocol Buffers request and response message payloads as JSON.
This means, the request and response body can be edited as if they were a JSON body. You are free to change the JSON as you wish, so long as the edits are compatible with the underlying proto message schema that the JSON represents.
First, go to gRPC in the top navigation bar and click Add/Edit.
Fill in the Request/Response fields and click Save to configure a mapping.
After saving the mapping, it will appear in the list of mappings.
Clicking the edit button will allow you to edit an existing mapping.
When adding a mapping manually, you can also select from the message skeleton dropdown which will populate the Request/Response fields with a message skeleton for that message type. To configure skeletons see how to configure skeletons.
message TestMessage { google.protobuf.Any any_field = 1; }will generate a skeleton with body JSON representation:
{ "anyField": { "@type": "type.googleapis.com/google.protobuf.StringValue", "value": "" } }
gRPC allows for returning one or more custom detail objects in exception responses. Traffic Parrot currently supports specifying a single details object:
You must specify the fully qualified type e.g. com.example.Details as well as the JSON representation of the object.
There are two exception details formats supported:The request priority can be set in order to set up a preference order for matching mappings.
The highest priority value is 1. If two or more mappings both match a request, the mapping with the higher priority will be used to provide the response. The default priority is 5.
This can be useful, if you want a "catch-all" mapping that returns a general response for most requests and specific mappings on top that return more specific responses.
Request metadata can be specified as key: value pairs. This means that Traffic Parrot will only match requests that contain the specified metadata values.
You can use the property trafficparrot.virtualservice.grpc.exclude.request.headers.from.matching to exclude request headers from matching. For example typical headers such as content-type,user-agent,grpc-accept-encoding are not typically useful to match on.
To populate the list of message skeletons, you need to configure Traffic Parrot with your proto files.
Then you can select the name of a gRPC method from the list and a sample request and response value will be generated, containing the default values for the message.
Traffic Parrot saves gRPC mappings in JSON files in the grpc-mappings directory, which can be version controlled in e.g. Git or SVN and edited directly on the file system in your IDE or text editor.
Here is a representative sample of the possible JSON schema that can be used:{ "id" : "ef7b503d-c4b0-40d4-89c6-2a0afa83f940", "name" : "example.json", "request" : { "urlPath" : "example.ExampleService/ExampleOperation", "method" : "ANY", "headers" : { "Protocol" : { "equalTo" : "GRPC" } }, "bodyPatterns" : [ { "equalToJson" : { "RequestName" : "" } } ] }, "response" : { "status" : 0, "jsonBody" : { "Success" : { "StringValue" : "" } } }, "uuid" : "ef7b503d-c4b0-40d4-89c6-2a0afa83f940" }Example of response body with complex template string value, which are best to edit via the UI or move to a template file:
{ "response" : { "status" : 0, "body" : "{\r\n \"type\": \"response\",\r\n \"items\": [\r\n {{#each (jsonPathList request.body '$.items') }}\r\n { \"id\": {{ jsonPath this '$.id' }}, \"status\": \"OK\" }{{#unless @last}},{{/unless}}\r\n {{/each}}\r\n ]\r\n}" } }Example of response body with reference to a template file:
{ "response" : { "status" : 0, "body" : "{{>data/responses/example}}", } }Example of request matcher with string value:
{ "request" : { "bodyPatterns" : [ { "matchesJsonPath" : "$[?(@.id == 1 && 2 in @.items[*].id)]" } ] } }Example of simple error code response with error message:
{ "response" : { "status" : 13, "body" : "Last name is required" }, }Example of error code response with error status details object using status key convention:
{ "response" : { "status" : 2, "headers" : { "grpc-status-details-bin" : "{\n \"error\" : \"some error information\",\n \"@type\" : \"type.googleapis.com/com.example.Details\"\n}" } } }Example of error code response with error status details object using proto key convention:
{ "response" : { "status" : 2, "headers" : { "com.example.Details-bin" : "{\r\n \"error\": \"some error information\"\r\n}" } } }Fields of interest:
When Traffic Parrot receives a request, it will try to simulate the system it is replacing by sending back a response to the client that sent the request. To decide which response to send, it will go through all the request to response mappings it has available to find the response to be returned. For more details how request matching works, see Request matching.
There are several matchers available to match gRPC requests, depending on the attribute.
helloworld.Greeter/SayHello
Key1: Value1 Key2: Value2
Traffic Parrot uses JSON to represent gRPC Protocol Buffers message payloads.
service HelloService { rpc SayHello (HelloRequest) returns (HelloResponse); } message HelloRequest { string greeting = 1; } message HelloResponse { string reply = 1; }then, if you choose "equal to JSON representation" as the request body matcher and populate the request body with:
{ "greeting": "Hello World!" }then Traffic Parrot will match this request any time it receives a gRPC request message with greeting "Hello World!".
You can use not only the "equal to JSON representation" matcher but any of the ones described below.
The most common matchers are shown below. All other WireMock 3 request body patterns are also supported.
Matcher name | Matcher Id | Description |
---|---|---|
any | any | Any gRPC request payload will match. |
equal to JSON representation | equalToJson | Check that the received request payload has all attributes equal to the ones specified in the mapping as JSON. |
matches JSON representation | matchesJson |
Check that the received request payload matches (allowing for special wildcard tokens) attributes specified in the mapping as JSON.
Tokens allowed:
For example a "matches JSON" request body matcher:
{ "name": "{{ anyValue }}", "lastName": "{{ anyValue }}", "age": "{{ anyNumber }}", "children": "{{ anyElements }}" }will match a request payload JSON representation: { "name": "Bob", "lastName": "Smith", "age": 37, "children": [{"name": "sam"}, {"name": "mary"}] } |
matches JSONPath | matchesJsonPath | Check that the received request payload, when converted by Traffic Parrot to JSON,
matches JSONPath
specified in the mapping.
For example, given a proto message:
message UserRequest { string name = 1; string lastName = 2; int32 age = 3; repeated Child children = 4; }when Traffic Parrot receives a "UserRequest" it will convert it to JSON for matching purposes. A sample "UserRequest" message, when received by Traffic Parrot and converted to JSON would look like this: { "name": "John", "lastName": "Smith", "age": 10, "children": [ { "name": "Hello" } ] }And then, a sample "matches JSONPath" matcher to match the message above could look like this: $[?(@.name =~ /J[a-z]+/ && @.lastName == 'Smith')]For more examples see the request matching documentation. |
does not contain | doesNotContain | Check that the received request payload, when converted to JSON, contains the sequence of characters specified in the mapping |
contains | contains | Check that the received request payload, when converted to JSON, does not contain the sequence of characters specified in the mapping |
matches regex | matches | Check that the received request payload, when converted to JSON, matches the regexp specified in the mapping.
For example, given a proto message:
message UserRequest { string name = 1; string lastName = 2; int32 age = 3; repeated Child children = 4; }when Traffic Parrot receives a "UserRequest" it will convert it to JSON for matching purposes. A sample "UserRequest" message, when received by Traffic Parrot and converted to JSON would look like this: { "name": "John", "lastName": "Smith", "age": 10, "children": [ { "name": "Hello" } ] }And then, a sample "matches regex" matcher to match the message above could look like this: .*"name": "J[a-z]+".*"lastName": "Smith".* |
does not match regexp | doesNotMatch | Check that the received request payload, when converted to JSON, does not match the regexp specified in the mapping. See "matches regexp" above for more details. |