Skip to content

Commit

Permalink
Fix the count for the rules per security group metric (#22)
Browse files Browse the repository at this point in the history
* Fix the count for the rules per security group metric

* Updated the readme
  • Loading branch information
idgenchev authored Jun 2, 2021
1 parent 8e49eab commit d518b5e
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 51 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ There are 7 metrics exposed:

1. Rules per security group
```
aws_rules_per_security_group_limit_total{region="eu-west-1",resource="sg-000000000000"} 60
aws_rules_per_security_group_used_total{region="eu-west-1",resource="sg-000000000000"} 3
aws_inbound_rules_per_security_group_limit_total{region="eu-west-1",resource="sg-0000000000000"} 200
aws_inbound_rules_per_security_group_used_total{region="eu-west-1",resource="sg-0000000000000"} 198
aws_outbound_rules_per_security_group_limit_total{region="eu-west-1",resource="sg-00000000000000"} 200
aws_outbound_rules_per_security_group_used_total{region="eu-west-1",resource="sg-00000000000000"} 7
```

2. Security groups per network interface
Expand Down
42 changes: 24 additions & 18 deletions pkg/service_exporter/service_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ var log = logging.WithFields(logging.Fields{})

// Metric holds usage and limit desc and values
type Metric struct {
usageDesc *prometheus.Desc
limitDesc *prometheus.Desc
usage float64
limit float64
resourceID string
usageDesc *prometheus.Desc
limitDesc *prometheus.Desc
usage float64
limit float64
}

func metricKey(quota servicequotas.QuotaUsage) string {
return fmt.Sprintf("%s%s", quota.Name, quota.Identifier())
}

// ServiceQuotasExporter AWS service quotas and usage prometheus
Expand Down Expand Up @@ -67,12 +72,12 @@ func (e *ServiceQuotasExporter) updateMetrics() {
}

for _, quota := range quotas {
resourceID := quota.Identifier()
log.Infof("Refreshing metrics for resource (%s)", resourceID)
if resourceMetric, ok := e.metrics[resourceID]; ok {
key := metricKey(quota)
log.Infof("Refreshing metrics for resource (%s)", quota.Identifier())
if resourceMetric, ok := e.metrics[key]; ok {
resourceMetric.usage = quota.Usage
resourceMetric.limit = quota.Quota
e.metrics[resourceID] = resourceMetric
e.metrics[key] = resourceMetric
}
}
}
Expand All @@ -85,8 +90,8 @@ func (e *ServiceQuotasExporter) createQuotasAndDescriptions() {

for _, quota := range quotas {
// check so we don't report the same metric more than once
resourceID := quota.Identifier()
if _, ok := e.metrics[resourceID]; ok {
key := metricKey(quota)
if _, ok := e.metrics[key]; ok {
continue
}

Expand All @@ -97,12 +102,13 @@ func (e *ServiceQuotasExporter) createQuotasAndDescriptions() {
limitDesc := newDesc(e.metricsRegion, quota.Name, "limit_total", limitHelp, []string{"resource"})

resourceMetric := Metric{
usageDesc: usageDesc,
limitDesc: limitDesc,
usage: quota.Usage,
limit: quota.Quota,
resourceID: quota.Identifier(),
usageDesc: usageDesc,
limitDesc: limitDesc,
usage: quota.Usage,
limit: quota.Quota,
}
e.metrics[resourceID] = resourceMetric
e.metrics[key] = resourceMetric
}

close(e.waitForMetrics)
Expand All @@ -120,9 +126,9 @@ func (e *ServiceQuotasExporter) Describe(ch chan<- *prometheus.Desc) {

// Collect implements the collect function for prometheus collectors
func (e *ServiceQuotasExporter) Collect(ch chan<- prometheus.Metric) {
for resourceID, metric := range e.metrics {
ch <- prometheus.MustNewConstMetric(metric.limitDesc, prometheus.GaugeValue, metric.limit, resourceID)
ch <- prometheus.MustNewConstMetric(metric.usageDesc, prometheus.GaugeValue, metric.usage, resourceID)
for _, metric := range e.metrics {
ch <- prometheus.MustNewConstMetric(metric.limitDesc, prometheus.GaugeValue, metric.limit, metric.resourceID)
ch <- prometheus.MustNewConstMetric(metric.usageDesc, prometheus.GaugeValue, metric.usage, metric.resourceID)
}
}

Expand Down
22 changes: 12 additions & 10 deletions pkg/service_exporter/service_exporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,17 +86,19 @@ func TestCreateQuotasAndDescriptions(t *testing.T) {
secondUsageDesc := newDesc(region, secondQ.Name, "used_total", "Used amount of desc2", []string{"resource"})
secondLimitDesc := newDesc(region, secondQ.Name, "limit_total", "Limit of desc2", []string{"resource"})
expectedMetrics := map[string]Metric{
"i-asdasd1": Metric{
usageDesc: firstUsageDesc,
limitDesc: firstLimitDesc,
usage: 5,
limit: 10,
"Name1i-asdasd1": Metric{
resourceID: "i-asdasd1",
usageDesc: firstUsageDesc,
limitDesc: firstLimitDesc,
usage: 5,
limit: 10,
},
"i-asdasd2": Metric{
usageDesc: secondUsageDesc,
limitDesc: secondLimitDesc,
usage: 1,
limit: 8,
"Name2i-asdasd2": Metric{
resourceID: "i-asdasd2",
usageDesc: secondUsageDesc,
limitDesc: secondLimitDesc,
usage: 1,
limit: 8,
},
}

Expand Down
49 changes: 33 additions & 16 deletions pkg/service_quotas/ec2_limits.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ import (
// Not all quota limits here are reported under "ec2", but all of the
// usage checks are using the ec2 service
const (
rulesPerSecGrpName = "rules_per_security_group"
rulesPerSecGrpDesc = "rules per security group"
inboundRulesPerSecGrpName = "inbound_rules_per_security_group"
inboundRulesPerSecGrpDesc = "inbound rules per security group"

outboundRulesPerSecGrpName = "outbound_rules_per_security_group"
outboundRulesPerSecGrpDesc = "outbound rules per security group"

secGroupsPerENIName = "security_groups_per_network_interface"
secGroupsPerENIDesc = "security groups per network interface"
Expand Down Expand Up @@ -43,13 +46,39 @@ type RulesPerSecurityGroupUsageCheck struct {
func (c *RulesPerSecurityGroupUsageCheck) Usage() ([]QuotaUsage, error) {
quotaUsages := []QuotaUsage{}

securityGroups := []*ec2.SecurityGroup{}
params := &ec2.DescribeSecurityGroupsInput{}
err := c.client.DescribeSecurityGroupsPages(params,
func(page *ec2.DescribeSecurityGroupsOutput, lastPage bool) bool {
if page != nil {
for _, group := range page.SecurityGroups {
securityGroups = append(securityGroups, group)
var inboundRules int = 0
var outboundRules int = 0

for _, rule := range group.IpPermissions {
inboundRules += len(rule.IpRanges)
inboundRules += len(rule.UserIdGroupPairs)
}

inboundUsage := QuotaUsage{
Name: inboundRulesPerSecGrpName,
ResourceName: group.GroupId,
Description: inboundRulesPerSecGrpDesc,
Usage: float64(inboundRules),
}

for _, rule := range group.IpPermissionsEgress {
outboundRules += len(rule.IpRanges)
inboundRules += len(rule.UserIdGroupPairs)
}

outboundUsage := QuotaUsage{
Name: outboundRulesPerSecGrpName,
ResourceName: group.GroupId,
Description: outboundRulesPerSecGrpDesc,
Usage: float64(outboundRules),
}

quotaUsages = append(quotaUsages, []QuotaUsage{inboundUsage, outboundUsage}...)
}
}
return !lastPage
Expand All @@ -59,18 +88,6 @@ func (c *RulesPerSecurityGroupUsageCheck) Usage() ([]QuotaUsage, error) {
return nil, errors.Wrapf(ErrFailedToGetUsage, "%w", err)
}

for _, securityGroup := range securityGroups {
inboundRules := len(securityGroup.IpPermissions)
outboundRules := len(securityGroup.IpPermissionsEgress)
quotaUsage := QuotaUsage{
Name: rulesPerSecGrpName,
ResourceName: securityGroup.GroupId,
Description: rulesPerSecGrpDesc,
Usage: float64(inboundRules + outboundRules),
}
quotaUsages = append(quotaUsages, quotaUsage)
}

return quotaUsages, nil
}

Expand Down
45 changes: 40 additions & 5 deletions pkg/service_quotas/ec2_limits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,28 +69,63 @@ func TestRulesPerSecurityGroupUsage(t *testing.T) {
{
FromPort: aws.Int64(0),
ToPort: aws.Int64(0),
UserIdGroupPairs: []*ec2.UserIdGroupPair{
{
Description: aws.String("Allow workers to communicate with the control plane."),
GroupId: aws.String("sg-0afb91d177e53ae1d"),
UserId: aws.String("740679791268"),
},
},
IpRanges: []*ec2.IpRange{
{
CidrIp: aws.String("10.0.0.10/32"),
Description: aws.String("Rule A"),
},
{
CidrIp: aws.String("10.0.0.5/32"),
Description: aws.String("Rule B"),
},
},
},
},
IpPermissionsEgress: []*ec2.IpPermission{
{
FromPort: aws.Int64(0),
ToPort: aws.Int64(0),
IpRanges: []*ec2.IpRange{
{
CidrIp: aws.String("0.0.0.0/0"),
Description: aws.String("Rule A"),
},
},
},
},
},
},
expectedUsage: []QuotaUsage{
{
Name: rulesPerSecGrpName,
Name: inboundRulesPerSecGrpName,
ResourceName: aws.String("somegroupid"),
Description: rulesPerSecGrpDesc,
Description: inboundRulesPerSecGrpDesc,
Usage: 0,
},
{
Name: rulesPerSecGrpName,
Name: outboundRulesPerSecGrpName,
ResourceName: aws.String("somegroupid"),
Description: outboundRulesPerSecGrpDesc,
Usage: 0,
},
{
Name: inboundRulesPerSecGrpName,
ResourceName: aws.String("groupwithrules"),
Description: rulesPerSecGrpDesc,
Usage: 2,
Description: inboundRulesPerSecGrpDesc,
Usage: 3,
},
{
Name: outboundRulesPerSecGrpName,
ResourceName: aws.String("groupwithrules"),
Description: outboundRulesPerSecGrpDesc,
Usage: 1,
},
},
},
Expand Down

0 comments on commit d518b5e

Please sign in to comment.