Skip to content

Commit 0083468

Browse files
Starting a collection of Synthetics test examples for learning / teaching purposes (#82)
* synthetics API test examples * synthetics examples * update readme and comments --------- Co-authored-by: Jeremy Hicks <[email protected]>
1 parent 048746d commit 0083468

File tree

17 files changed

+967
-0
lines changed

17 files changed

+967
-0
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# GraphQL API Check Example
2+
This example API test shows how to query a GraphQL endpoint and validate the returned data using built in matching and JavaScript parsing.
3+
The test and it's configuration are included in this directory:
4+
- [`synthetics_example_graphql_api_check.tf`](./synthetics_example_graphql_api_check.tf)
5+
- Uses the [Splunk Synthetics Terraform provider](https://registry.terraform.io/providers/splunk/synthetics/latest/docs)
6+
7+
## Synthetic API Test
8+
The API test will call [https://countries.trevorblades.com/graphql](https://countries.trevorblades.com/graphql) which provides data on various countries. We use this data for ease of accessibility purposes. But this approach applies just as well to internal GraphQL endpoints where you want to validate the responses being provided or specific values of data within those responses.
9+
10+
Our test will query the info on Canada and check that:
11+
- The endpoint returns the expected national languages in the expected JSON format.
12+
- The endpoint returns an array of states within the country. We will use JavaScript and custom variables to validate that there are still exactly 13 "states" (provences) in Canada.
13+
14+
The expected body we will receive from our request and that we will be validating against looks like so:
15+
```JSON
16+
{
17+
"data":
18+
{
19+
"country":
20+
{
21+
"name": "Canada",
22+
"native": "Canada",
23+
"capital": "Ottawa",
24+
"emoji": "🇨🇦",
25+
"currency": "CAD",
26+
"languages":
27+
[
28+
{
29+
"code": "en",
30+
"name": "English"
31+
},
32+
{
33+
"code": "fr",
34+
"name": "French"
35+
}
36+
],
37+
"states":
38+
[
39+
{
40+
"code": "AB"
41+
},
42+
{
43+
"code": "BC"
44+
},
45+
{
46+
"code": "MB"
47+
},
48+
{
49+
"code": "NB"
50+
},
51+
{
52+
"code": "NL"
53+
},
54+
{
55+
"code": "NS"
56+
},
57+
{
58+
"code": "NU"
59+
},
60+
{
61+
"code": "NT"
62+
},
63+
{
64+
"code": "ON"
65+
},
66+
{
67+
"code": "PE"
68+
},
69+
{
70+
"code": "QC"
71+
},
72+
{
73+
"code": "SK"
74+
},
75+
{
76+
"code": "YT"
77+
}
78+
]
79+
}
80+
}
81+
}
82+
```
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
resource "synthetics_create_api_check_v2" "synthetics_example_graphql_api_check" {
2+
test {
3+
active = true
4+
automatic_retries = 0
5+
device_id = 34
6+
frequency = 1440
7+
location_ids = ["aws-us-east-1", "aws-us-west-1"]
8+
name = "Canada - Languages - Number of States"
9+
scheduling_strategy = "round_robin"
10+
requests {
11+
configuration {
12+
body = jsonencode({
13+
operationName = "Query"
14+
query = "query Query {\n country(code: \"CA\") {\n name\n native\n capital\n emoji\n currency\n languages {\n code\n name\n }\n states {\n code\n }\n }\n}\n"
15+
variables = {}
16+
})
17+
headers = {
18+
Content-Type = "application/json"
19+
}
20+
name = "https://countries.trevorblades.com/graphql"
21+
request_method = "POST"
22+
url = "https://countries.trevorblades.com/graphql"
23+
}
24+
validations {
25+
actual = "{{response.code}}"
26+
code = null
27+
comparator = "is_less_than"
28+
expected = "300"
29+
extractor = null
30+
name = "Assert response code is less than 300"
31+
source = null
32+
type = "assert_numeric"
33+
value = null
34+
variable = null
35+
}
36+
validations {
37+
actual = "{{response.body}}"
38+
code = null
39+
comparator = "contains"
40+
expected = "\"languages\":[{\"code\":\"en\",\"name\":\"English\"},{\"code\":\"fr\",\"name\":\"French\"}]"
41+
extractor = null
42+
name = "Assert response body value"
43+
source = null
44+
type = "assert_string"
45+
value = null
46+
variable = null
47+
}
48+
validations {
49+
actual = null
50+
code = null
51+
comparator = null
52+
expected = null
53+
extractor = "$.data.country.states"
54+
name = "Extract from response body"
55+
source = "{{response.body}}"
56+
type = "extract_json"
57+
value = null
58+
variable = "statearray"
59+
}
60+
validations {
61+
actual = null
62+
code = "const apiResponse = custom[\"statearray\"] ? JSON.parse(custom[\"statearray\"]) || [] : [];\n\nfunction countStates(jsonData) { \n const stateCount = jsonData.length;\n \n custom.statcount = stateCount;\n return stateCount;\n}\n\n\ncountStates(apiResponse);"
63+
comparator = null
64+
expected = null
65+
extractor = null
66+
name = "JavaScript run"
67+
source = null
68+
type = "javascript"
69+
value = null
70+
variable = "statecount"
71+
}
72+
validations {
73+
actual = "{{custom.statecount}}"
74+
code = null
75+
comparator = "equals"
76+
expected = "13"
77+
extractor = null
78+
name = "Assert custom variable statecount equals 13"
79+
source = null
80+
type = "assert_numeric"
81+
value = null
82+
variable = null
83+
}
84+
}
85+
}
86+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Third-party Status Page API Check to Metric
2+
This example API test shows how to call multiple APIs, collect data, turn that data into a usable JSON payload, and send it off to another API.
3+
This test creates metrics using a Splunk Synthetics API test.
4+
The test and it's configuration are included in this directory:
5+
- [`synthetics_thirdparty_status_api_check.tf`](./synthetics_thirdparty_status_api_check.tf)
6+
- Uses the [Splunk Synthetics Terraform provider](https://registry.terraform.io/providers/splunk/synthetics/latest/docs)
7+
8+
For a detailed description of this test and how it functions check out the [Splunk Lantern Article: Constructing an API test JSON payload](https://lantern.splunk.com/Observability/Product_Tips/Synthetic_Monitoring/Constructing_an_API_test_JSON_payload_for_alerting_on_external_dependencies)
9+
10+
## Synthetic API Test
11+
The synthetic API test will call the CloudFlare and GitHub status pages and report a metric with a value of 1 (status is impacted) or 0 (status is normal) for each:
12+
- `cloudflare.status`
13+
- `github.status`
14+
15+
These metrics include dimensions for description of any impact to status and an indicator (none, minor, major, or critical).
16+
![alt text](image.png)
17+
18+
### Required Splunk Synthetic Global Variables
19+
The following [global variables](https://docs.splunk.com/observability/en/synthetics/test-config/global-variables.html) are **REQUIRED** to run the included API test.
20+
- `org_ingest_token`: A provisioned INGEST token
21+
![required synthetic variables](synthetic-variables.png)
22+
214 KB
Loading
43.4 KB
Loading
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
resource "synthetics_create_api_check_v2" "synthetics_thirdparty_status_api_check" {
2+
test {
3+
active = true
4+
automatic_retries = 0
5+
device_id = 34
6+
frequency = 60
7+
location_ids = ["aws-us-east-1", "aws-us-west-1"]
8+
name = "Cloudflare Status API"
9+
scheduling_strategy = "round_robin"
10+
requests {
11+
configuration {
12+
body = null
13+
headers = {}
14+
name = "https://www.cloudflarestatus.com/api/v2/status.json"
15+
request_method = "GET"
16+
url = "https://www.cloudflarestatus.com/api/v2/status.json"
17+
}
18+
validations {
19+
actual = null
20+
code = null
21+
comparator = null
22+
expected = null
23+
extractor = "*"
24+
name = "Extract from response body"
25+
source = "{{response.body}}"
26+
type = "extract_json"
27+
value = null
28+
variable = "response"
29+
}
30+
validations {
31+
actual = null
32+
code = "var apiResponse = JSON.parse(custom[\"response\"]);\n\nfunction createGaugeEntry(apiResponse) {\n var value = (apiResponse.status.indicator === \"none\") ? \"0\" : \"1\";\n \n var gaugeEntry = {\n \"gauge\": [\n {\n \"metric\": \"cloudflare.status\",\n \"dimensions\": {\n \"description\": apiResponse.status.description,\n \"indicator\": apiResponse.status.indicator\n },\n \"value\": value\n }\n ]\n };\n\n return gaugeEntry;\n}\n\nvar gaugeEntry = createGaugeEntry(apiResponse);\n\ncustom.payload_cloudflare = gaugeEntry;"
33+
comparator = null
34+
expected = null
35+
extractor = null
36+
name = "JavaScript run"
37+
source = null
38+
type = "javascript"
39+
value = null
40+
variable = "payload_cloudflare"
41+
}
42+
validations {
43+
actual = "{{custom.payload_cloudflare}}"
44+
code = null
45+
comparator = "is_not_empty"
46+
expected = null
47+
extractor = null
48+
name = "Assert custom variable payload_cloudflare is not empty"
49+
source = null
50+
type = "assert_string"
51+
value = null
52+
variable = null
53+
}
54+
}
55+
requests {
56+
configuration {
57+
body = null
58+
headers = {}
59+
name = "curl https://www.githubstatus.com/api/v2/status.json"
60+
request_method = "GET"
61+
url = "https://www.githubstatus.com/api/v2/status.json"
62+
}
63+
validations {
64+
actual = null
65+
code = null
66+
comparator = null
67+
expected = null
68+
extractor = "*"
69+
name = "Extract from response body"
70+
source = "{{response.body}}"
71+
type = "extract_json"
72+
value = null
73+
variable = "response"
74+
}
75+
validations {
76+
actual = null
77+
code = "var apiResponse = JSON.parse(custom[\"response\"]);\n\nfunction createGaugeEntry(apiResponse) {\n var value = (apiResponse.status.indicator === \"none\") ? \"0\" : \"1\";\n\n var gaugeEntry = {\n \"gauge\": [\n {\n \"metric\": \"github.status\",\n \"dimensions\": {\n \"description\": apiResponse.status.description,\n \"indicator\": apiResponse.status.indicator\n },\n \"value\": value\n }\n ]\n };\n\n return gaugeEntry;\n}\n\nvar gaugeEntry = createGaugeEntry(apiResponse);\n\ncustom.payload_github = gaugeEntry;"
78+
comparator = null
79+
expected = null
80+
extractor = null
81+
name = "JavaScript run"
82+
source = null
83+
type = "javascript"
84+
value = null
85+
variable = "payload_github"
86+
}
87+
validations {
88+
actual = "{{custom.payload_github}}"
89+
code = null
90+
comparator = "is_not_empty"
91+
expected = null
92+
extractor = null
93+
name = "Assert custom variable payload_github is not empty"
94+
source = null
95+
type = "assert_string"
96+
value = null
97+
variable = null
98+
}
99+
}
100+
requests {
101+
configuration {
102+
body = "{{custom.payload}}"
103+
headers = {
104+
Content-Type = "application/json"
105+
X-SF-Token = "{{env.org_ingest_token}}"
106+
}
107+
name = "https://ingest.us1.signalfx.com/v2/datapoint"
108+
request_method = "POST"
109+
url = "https://ingest.us1.signalfx.com/v2/datapoint"
110+
}
111+
setup {
112+
code = "var gaugePayloads = [\n JSON.parse(custom[\"payload_github\"]),\n JSON.parse(custom[\"payload_cloudflare\"])\n];\n\nfunction combineMultipleGaugeEntries(payloads) {\n var combinedGaugeArray = [];\n \n for (var i = 0; i < payloads.length; i++) {\n combinedGaugeArray = combinedGaugeArray.concat(payloads[i].gauge);\n }\n\n return { \"gauge\": combinedGaugeArray };\n}\n\nvar combinedGaugeEntry = combineMultipleGaugeEntries(gaugePayloads);\n\ncustom.payload = combinedGaugeEntry;"
113+
extractor = null
114+
name = "JavaScript run"
115+
source = null
116+
type = "javascript"
117+
value = null
118+
variable = "payload"
119+
}
120+
validations {
121+
actual = "{{response.code}}"
122+
code = null
123+
comparator = "is_less_than"
124+
expected = "300"
125+
extractor = null
126+
name = "Assert response code is less than 300"
127+
source = null
128+
type = "assert_numeric"
129+
value = null
130+
variable = null
131+
}
132+
}
133+
}
134+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Third-party Status Page API Check to Metric
2+
This example API test calls the OpenAI status endpoint and collects data on ongoing incidents and updates.
3+
This test creates and sends a log event containing that incident data to a Splunk HEC endpoint.
4+
The test and it's configuration are included in this directory:
5+
- [`synthetics_status_to_splunk_hec_api_check.tf`](./synthetics_status_to_splunk_hec_api_check.tf)
6+
- Uses the [Splunk Synthetics Terraform provider](https://registry.terraform.io/providers/splunk/synthetics/latest/docs)
7+
8+
## Synthetic API Test
9+
The synthetic API test will call the OpenAI status page and report any current and ongoing incidents to a Splunk HEC endpoint of your choice. This example is mostly to illustrate ingest arbitrary ingest into Splunk. The test serves a double function of providing external monitoring of the HEC endpoint in question in addition to providing ingest of useful incident data.
10+
11+
12+
### Required Splunk Synthetic Global Variables
13+
The following [global variables](https://docs.splunk.com/observability/en/synthetics/test-config/global-variables.html) are **REQUIRED** to run the included API test.
14+
- `splunk_hec_url`: The url to your hec raw ingest (E.G. `https://hec-inputs-for-my-service.mysplunkinstance.com:443/services/collector/raw`)
15+
- **Terraform apply will fail if this global variable does not exist in your environment!**
16+
- `hec_token`: A provisioned hec token for basic auth (E.G. `Splunk 123412-3123-1234-abcd-1234123412abc`)
17+

0 commit comments

Comments
 (0)