Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cloudwatch): validate parameters for a metric dimensions (closes #3116) #14365

Merged
merged 7 commits into from
May 7, 2021
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-cloudwatch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ const metric = new Metric({
}
});
```
### Metric dimensions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to add it in the README, documentation on the property is sufficient. The README is more suited for features/code samples.


The maximum number of dimensions a Metric can have is 10. The length of a dimension name and value must be
between 1-255 characters in length.

### Metric Math

Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-cloudwatch/lib/metric-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ export interface IMetric {
/**
* Turn this metric object into an alarm configuration
*
* @deprecated Use `toMetricsConfig()` instead.
* @deprecated Use `toMetricConfig()` instead.
*/
toAlarmConfig(): MetricAlarmConfig;

/**
* Turn this metric object into a graph configuration
*
* @deprecated Use `toMetricsConfig()` instead.
* @deprecated Use `toMetricConfig()` instead.
*/
toGraphConfig(): MetricGraphConfig;
}
Expand Down
24 changes: 23 additions & 1 deletion packages/@aws-cdk/aws-cloudwatch/lib/metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,9 @@ export class Metric implements IMetric {
if (periodSec !== 1 && periodSec !== 5 && periodSec !== 10 && periodSec !== 30 && periodSec % 60 !== 0) {
throw new Error(`'period' must be 1, 5, 10, 30, or a multiple of 60 seconds, received ${periodSec}`);
}

if (props.dimensions) {
this.validateDimensions(props.dimensions);
}
this.dimensions = props.dimensions;
this.namespace = props.namespace;
this.metricName = props.metricName;
Expand Down Expand Up @@ -395,6 +397,26 @@ export class Metric implements IMetric {

return list;
}

//rules from here: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cw-dimension.html
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add this link to the dimension property documentation, so it would show up in the development time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be removed from here if it's on the prop itslef. If you want to keep it, add as a method documentation.

private validateDimensions(dims: DimensionHash): void {
if (Object.keys(dims)?.length > 10) {
throw new Error('The maximum number of dimensions is 10');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new Error('The maximum number of dimensions is 10');
throw new Error(`The maximum number of dimensions is 10, received ${dims.size}`);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it says that dims.size is undefined, so I've used length of Object.keys(dims)

}

Object.keys(dims).sort().map(key => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the sort?

if (dims[key] === undefined || dims[key] === null) {
throw new Error(`Dimension value of '${dims[key]}' is invalid`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see in the docs that undefined is not allowed, assuming you verified it in deployment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran the code sample in the original issue (#3116), and it failed. I think it is because of the undefined value

};
if (key.length < 1 || key.length > 255) {
throw new Error(`Dimension name must be at least 1 and no more than 255 characters; received ${key}`);
};

if (dims[key].length < 1 || dims[key].length > 255) {
throw new Error(`Dimension value must be at least 1 and no more than 255 characters; received ${dims[key]}`);
};
});
}
}

function asString(x?: unknown): string | undefined {
Expand Down
77 changes: 77 additions & 0 deletions packages/@aws-cdk/aws-cloudwatch/test/test.metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,81 @@ export = {

test.done();
},

'cannot use null dimension value'(test: Test) {
test.throws(() => {
new Metric({
namespace: 'Test',
metricName: 'ACount',
period: cdk.Duration.minutes(10),
dimensions: {
DimensionWithNull: null,
},
});
}, /Dimension value of 'null' is invalid/);

test.done();
},

'cannot use undefined dimension value'(test: Test) {
test.throws(() => {
new Metric({
namespace: 'Test',
metricName: 'ACount',
period: cdk.Duration.minutes(10),
dimensions: {
DimensionWithUndefined: undefined,
},
});
}, /Dimension value of 'undefined' is invalid/);

test.done();
},

'cannot use long dimension values'(test: Test) {
const invalidDimensionValue = 'SomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValue\
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets try something like this to make it more readable:

const arr = new Array(256);
const invalidDimensionValue = arr.fill('A', 0).join('');

SomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValue\
SomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValue\
SomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValue\
SomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValue\
SomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValueSomeLongValue';

test.throws(() => {
new Metric({
namespace: 'Test',
metricName: 'ACount',
period: cdk.Duration.minutes(10),
dimensions: {
DimensionWithLongValue: invalidDimensionValue,
},
});
}, `Dimension value must be at least 1 and no more than 255 characters; received ${invalidDimensionValue}`);

test.done();
},

'throws error when there are more than 10 dimensions'(test: Test) {
test.throws(() => {
new Metric({
namespace: 'Test',
metricName: 'ACount',
period: cdk.Duration.minutes(10),
dimensions: {
dimensionA: 'value1',
dimensionB: 'value2',
dimensionC: 'value3',
dimensionD: 'value4',
dimensionE: 'value5',
dimensionF: 'value6',
dimensionG: 'value7',
dimensionH: 'value8',
dimensionI: 'value9',
dimensionJ: 'value10',
dimensionK: 'value11',
},
} );
}, /The maximum number of dimensions is 10/);

test.done();
},
};