Skip to content

fix(aws-cloudwatch): DashboardNotFoundError shape, FILL/IF/SEARCH metric math, non-goals - #1537

Open
Satyam-Trivedi-ZS wants to merge 4 commits into
stackshy:developmentfrom
Satyam-Trivedi-ZS:fix/aws-cloudwatch-parity-1520
Open

Satyam-Trivedi-ZS wants to merge 4 commits into
stackshy:developmentfrom
Satyam-Trivedi-ZS:fix/aws-cloudwatch-parity-1520

Conversation

@Satyam-Trivedi-ZS

Copy link
Copy Markdown
Collaborator

Summary

Part of #1520 (AWS CloudWatch real-cloud parity gaps).

  • Missing dashboards now map to types.DashboardNotFoundError in the SDK (rpc-v2-cbor and JSON).
  • Metric math now evaluates FILL, IF, SEARCH, and the comparison and logical operators. Before, these returned no data.
  • GetMetricWidgetImage and the newer AnomalyDetectorId surface are written up as non-goals.

Per-item status

Item Status Evidence
GetDashboard/DeleteDashboards error code FIXED (partly a real gap) The issue says the real code is DashboardNotFoundError. That is the shape name. Its wire code is ResourceNotFound with HTTP 404 (botocore cloudwatch/2010-08-01/service-2.json: DashboardNotFoundError.error = {code: ResourceNotFound, httpStatusCode: 404}; the GetDashboard API reference lists ResourceNotFound, 404). The query/XML code was already correct. The actual gap was in rpc-v2-cbor and JSON: they put __type: ResourceNotFound, but aws-sdk-go-v2 cloudwatch v1.56.2 matches the shape name (case "DashboardNotFoundError" in deserializers.go), so errors.As never found types.DashboardNotFoundError.
Metric math FILL / IF / SEARCH / ANOMALY_DETECTION_BAND FIXED for FILL, IF and SEARCH. ALREADY-FIXED for ANOMALY_DETECTION_BAND ANOMALY_DETECTION_BAND(m1[,k]) already returned the lower and upper rows (server/aws/cloudwatch/core_metric_data.go metricDataRows, test TestGetMetricDataAnomalyBand in anomaly_detectors_test.go). FILL, IF and SEARCH failed to parse, so they returned no data.
AnomalyDetectorId response + AnomalyDetectorIds filter; band row labels UNVERIFIABLE (not changed, documented) The API reference documents these members (PutAnomalyDetector, DescribeAnomalyDetectors, DeleteAnomalyDetector). The pinned aws-sdk-go-v2 cloudwatch v1.56.2 and botocore 1.42.97 do not have them, so no client here can send or read them, and the ID format is not documented. The docs also do not give the band row labels. Recorded in docs/coverage/nongoals/monitoring.md.
GetMetricWidgetImage SKIPPED (documented non-goal) The operation returns a rendered PNG graph (GetMetricWidgetImageOutput.MetricWidgetImage, base64 PNG). That is presentation output, not control-plane state, and cloudemu has no renderer. Returning a fake image would mislead callers, so it is recorded as a non-goal in docs/coverage/nongoals/monitoring.md instead.

Changes

Dashboard errors (server/aws/cloudwatch, wire translation only):

  • wireError gets an optional shape. dashboardErr turns a store NotFound into code ResourceNotFound, shape DashboardNotFoundError, status 404.
  • The CBOR and JSON writers put the shape in __type and ResourceNotFound;Sender in X-Amzn-Query-Error, the way an awsQueryCompatible service does. ErrorCode() therefore stays ResourceNotFound, and the CLI still shows ResourceNotFound.
  • The query/XML path is unchanged.

Metric math (services/monitoring/metricmath). Only providers/aws/cloudwatch and server/aws/cloudwatch import this package; there is no Azure/GCP consumer. Semantics come from Use metric math and the search expression syntax / examples:

  • Comparison operators == != < <= > >= and logical operators AND/&&, OR/||. When a timestamp exists in only one series, the other series counts as 0 there, as the doc's table shows.
  • IF(cond, a[, b]) follows the documented per-point rules. All three IF tables from the guide are unit-tested verbatim.
  • FILL(x, S | TS | REPEAT | LINEAR) fills the periods of the query range [StartTime, EndTime), aligned the same way as the metric reads. FILL(TS[], ...) works over SEARCH results.
  • SEARCH(' {Namespace,Dim...} term ', 'Stat'[, period]) covers:
    • metric schema (exact dimension-name set), partial token matching, exact "..." matching, Name=value designators, and AND/OR/NOT with parentheses
    • the documented tokenization: camel case, digits, delimiters, and the case-sensitive composite tokens
    • search scope: only metrics with data in the past two weeks (the ListMetrics set)
  • Each SEARCH match becomes its own GetMetricData result row with the query's Id.
  • The evaluator gains WithRange, WithSearch and ResolveAll. The provider's math alarms and anomaly-detector state also pass the range, so FILL works in alarms.

Docs: new docs/coverage/nongoals/monitoring.md, plus the regenerated docs/coverage (go generate ./...). No driver interface changed.

Provider Coverage

  • AWS
  • Azure — n/a (CloudWatch metric math and dashboards are AWS-only)
  • GCP — n/a
  • OCI — n/a

Checklist

  • All tests pass (go test ./...)
  • Linter: 0 new issues. The touched packages report the same 10 pre-existing findings as clean upstream/development, and this branch removes one dupl (details below).
  • Every provider the change applies to implements the same behavior
  • Integration tests added to cloudemu_test.go — covered by aws-sdk-go-v2 wire tests in server/aws/cloudwatch
  • Unit tests added to provider/service test files
  • Regenerated docs (go generate ./...) and committed the result

Test Plan

Fail-before / pass-after (verified by running the new tests against the pre-fix sources):

  • TestSDKDashboardNotFoundShape (aws-sdk-go-v2). Before the fix: error *smithy.OperationError ... is not *types.DashboardNotFoundError. After: errors.As succeeds, ErrorCode()=="ResourceNotFound", HTTP 404, and a failed DeleteDashboards leaves the other named dashboard in place.
  • TestSDKGetMetricDataFILLAndIF and TestSDKGetMetricDataSEARCH (aws-sdk-go-v2). Before the fix every row was empty (zero values = [], want [0 10 0 30 0] ...). After, the rows match the documented values.
  • Unit tests in services/monitoring/metricmath/functions_test.go: TestIFExpressions (the guide's tables), TestComparisonAndLogicalOperators (the guide's operator table), TestFILL*, TestSEARCH (the tokenization and partial/exact cases from the syntax page), TestSEARCHReadsEachMetric, TestFILLOverSEARCH, TestFunctionReferences.
  • TestJSONProtocolErrors now expects __type: DashboardNotFoundError with X-Amzn-Query-Error: ResourceNotFound;Sender.
  • Two older tests asserted that FILL is unsupported (TestReferences, TestMathAlarmUnsupportedExpression). They now use RATE, which is still unsupported.

Checks run:

  • go build ./...
  • go vet on the touched packages
  • go test -p 2 ./... (full). One run passed with exit 0. A later run on a 98%-full disk failed to build ~50 untouched server/azure/* packages and TestK8sDataPlanePersistsAcrossCrash. All of them pass when re-run (go test -p 2 ./server/azure/..., go test -run TestK8sDataPlanePersistsAcrossCrash ./cmd/cloudemu), so these were environmental.
  • golangci-lint run --timeout=15m --concurrency 2 ./...: the repo-wide run reports about 400 pre-existing findings in untouched services, the same as upstream/development (CI runs lint as advisory). On the touched packages (./services/monitoring/... ./server/aws/cloudwatch/... ./providers/aws/cloudwatch/..., uncapped):
    • this branch reports 10 findings
    • a clean upstream/development worktree reports the same 10, plus 1 dupl (dashboards.go vs metric_streams.go) that this branch removes
    • so there are 0 new issues, and every finding in new or changed code is fixed

Real-user test: cloudemu serve -aws-port 4610 ... (in the background) driven with AWS CLI v1 (botocore 1.42.97), with --endpoint-url http://127.0.0.1:4610 --region us-east-1:

$ aws cloudwatch get-dashboard --dashboard-name missing
An error occurred (ResourceNotFound) when calling the GetDashboard operation: dashboard "missing" not found   (exit 255)
$ aws cloudwatch put-dashboard --dashboard-name keep --dashboard-body '{"widgets":[]}'          -> {"DashboardValidationMessages": []}
$ aws cloudwatch delete-dashboards --dashboard-names keep missing
An error occurred (ResourceNotFound) when calling the DeleteDashboards operation ...           (exit 255)
$ aws cloudwatch get-dashboard --dashboard-name keep --query DashboardName                       -> "keep" (all-or-nothing delete)
$ aws cloudwatch put-metric-data --namespace CliApp --metric-data MetricName=Errors,Value=10,Timestamp=T+1m MetricName=Errors,Value=30,Timestamp=T+3m
$ aws cloudwatch put-metric-data --namespace CliApp --metric-data "MetricName=Latency,Value=5,Timestamp=T+1m,Dimensions=[{Name=Host,Value=a}]" "MetricName=Latency,Value=9,Timestamp=T+1m,Dimensions=[{Name=Host,Value=b}]"
$ aws cloudwatch get-metric-data --start-time T --end-time T+5m --scan-by TimestampAscending --metric-data-queries file://q.json \
    --query 'MetricDataResults[].[Id,Label,join(`,`,Values[].to_string(@))]' --output text
zero    zero      0.0,10.0,0.0,30.0,0.0      # FILL(m1, 0)
repeat  repeat    10.0,10.0,30.0,30.0        # FILL(m1, REPEAT)
linear  linear    10.0,20.0,30.0             # FILL(m1, LINEAR)
high    high      0.0,1.0                    # IF(m1 > 15, 1, 0)
s1      a Latency 5.0                        # SEARCH('{CliApp,Host} MetricName="Latency"', 'Maximum', 60)
s1      b Latency 9.0

Known limits (documented in the non-goals file):

  • No source documents the SEARCH row labels or the band row labels; the label formats here are the emulator's choice.
  • FILL with REPEAT/LINEAR leaves the edge gaps empty.
  • Other functions (SUM, METRICS, RATE, ...) still return no data.
  • Existing arithmetic still drops a point that only one series has, while the guide says such a point counts as 0. That behavior is unchanged here.

Related Issues

Part of #1520

🤖 Generated with Claude Code

Satyam-Trivedi-ZS and others added 4 commits October 10, 2026 21:11
…dashboards

GetDashboard and DeleteDashboards list DashboardNotFoundError (awsQuery code
ResourceNotFound, HTTP 404) in the CloudWatch model. The rpc-v2-cbor and JSON
codecs wrote __type ResourceNotFound, so aws-sdk-go-v2 fell back to a generic
API error instead of types.DashboardNotFoundError. Write the shape name in
__type and the query code in X-Amzn-Query-Error; the query/XML code stays
ResourceNotFound, which is already correct.

Part of stackshy#1520

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ors in metric math

FILL, IF and SEARCH were stored but parsed as unsupported, so GetMetricData
and metric-math alarms returned no data for them. Implement them following
the CloudWatch user guide (Use metric math, search expression syntax):

- comparison (== != < <= > >=) and logical (AND/&&, OR/||) operators, with a
  point missing from one series read as 0
- IF(cond, a[, b]) with the documented per-point rules and tables
- FILL(x, S|TS|REPEAT|LINEAR) over the periods of the query range
- SEARCH(' {schema} term ', 'Stat'[, period]) over metrics with data in the
  past two weeks; each found metric is its own GetMetricData result row

Part of stackshy#1520

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-goals

GetMetricWidgetImage renders a PNG graph, which the emulator does not do.
Record it, the metric math functions that are evaluated, the labels the AWS
docs leave unstated, and the newer AnomalyDetectorId surface as non-goals.

Part of stackshy#1520

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Pointer params for the 96-byte result, operator and keyword constants,
smaller parser and search lexer functions, and drop the nolint:dupl on
deleteMetricStream that the dashboard change made unused.

Part of stackshy#1520

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@NitinKumar004

Copy link
Copy Markdown
Collaborator
Screenshot 2026-10-11 at 12 48 22 PM remove claude attribution please

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants