Skip to content

Commit

Permalink
当月の請求金額: sakuracloud_bill_amount (#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
yamamoto-febc authored Sep 2, 2022
1 parent 8520954 commit f0450ad
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 2 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,14 @@ $ docker run -p 9542:9542 -e SAKURACLOUD_ACCESS_TOKEN=<YOUR-TOKEN> -e SAKURACLOU
### Flags

| Flag / Environment Variable | Required | Default | Description |
|------------------------------------------------| -------- | ---------- | ------------------------- |
|------------------------------------------------| -------- | ---------- |-----------------------------------------------------------------|
| `--token` / `SAKURACLOUD_ACCESS_TOKEN` || | API Key(Token) |
| `--secret` / `SAKURACLOUD_ACCESS_TOKEN_SECRET` || | API Key(Secret) |
| `--ratelimit`/ `SAKURACLOUD_RATE_LIMIT` | | `5` | API request rate limit(maximum:10) |
| `--webaddr` / `WEB_ADDR` | | `:9542` | Exporter's listen address |
| `--webpath`/ `WEB_PATH` | | `/metrics` | Metrics request path |
| `--no-collector.auto-backup` | | `false` | Disable the AutoBackup collector |
| `--no-collector.bill` | | `false` | Disable the Bill collector |
| `--no-collector.coupon` | | `false` | Disable the Coupon collector |
| `--no-collector.database` | | `false` | Disable the Database collector |
| `--no-collector.esme` | | `false` | Disable the ESME collector |
Expand All @@ -64,7 +65,7 @@ $ docker run -p 9542:9542 -e SAKURACLOUD_ACCESS_TOKEN=<YOUR-TOKEN> -e SAKURACLOU
| `--no-collector.sim` | | `false` | Disable the SIM collector |
| `--no-collector.vpc-router` | | `false` | Disable the VPCRouter collector |
| `--no-collector.zone` | | `false` | Disable the Zone collector |
| `--no-collector.webaccel` | | `false` | Disable the WebAccel collector |
| `--no-collector.webaccel` | | `false` | Disable the WebAccel collector |


#### Flags for debug
Expand All @@ -84,6 +85,7 @@ The exporter returns the following metrics:
| Resource Type | Metric Name Prefix |
|---------------------------------|------------------------------|
| [AutoBackup](#autobackup) | sakuracloud_auto_backup_* |
| [Bill](#bill) | sakuracloud_bill_* |
| [Coupon](#coupon) | sakuracloud_coupon_* |
| [Database](#database) | sakuracloud_database_* |
| [ESME](#esme) | sakuracloud_esme_* |
Expand All @@ -110,6 +112,12 @@ The exporter returns the following metrics:
| sakuracloud_auto_backup_last_time | Last backup time in seconds since epoch (1970) | `id`, `name`, `disk_id` |
| sakuracloud_auto_backup_archive_info | A metric with a constant '1' value labeled by backuped archive information | `id`, `name`, `disk_id`, `archive_id`, `archive_name`, `archive_tags`, `archive_description` |

#### Bill

| Metric | Description | Labels |
|--------------------------|-----------------------------|-------------|
| sakuracloud_bill_amount | Amount billed for the month | `member_id` |

#### Coupon

| Metric | Description | Labels |
Expand Down
85 changes: 85 additions & 0 deletions collector/bill.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2019-2022 The sakuracloud_exporter Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package collector

import (
"context"

"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/prometheus/client_golang/prometheus"
"github.com/sacloud/sakuracloud_exporter/platform"
)

// BillCollector collects metrics about the account.
type BillCollector struct {
ctx context.Context
logger log.Logger
errors *prometheus.CounterVec
client platform.BillClient

Amount *prometheus.Desc
}

// NewBillCollector returns a new BillCollector.
func NewBillCollector(ctx context.Context, logger log.Logger, errors *prometheus.CounterVec, client platform.BillClient) *BillCollector {
errors.WithLabelValues("bill").Add(0)

labels := []string{"member_id"}

return &BillCollector{
ctx: ctx,
logger: logger,
errors: errors,
client: client,

Amount: prometheus.NewDesc(
"sakuracloud_bill_amount",
"Amount billed for the month",
labels, nil,
),
}
}

// Describe sends the super-set of all possible descriptors of metrics
// collected by this Collector.
func (c *BillCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- c.Amount
}

// Collect is called by the Prometheus registry when collecting metrics.
func (c *BillCollector) Collect(ch chan<- prometheus.Metric) {
bill, err := c.client.Read(c.ctx)
if err != nil {
c.errors.WithLabelValues("bill").Add(1)
level.Warn(c.logger).Log( // nolint
"msg", "can't get bill",
"err", err,
)
return
}

if bill != nil {
labels := []string{bill.MemberID}

// Amount
ch <- prometheus.MustNewConstMetric(
c.Amount,
prometheus.GaugeValue,
float64(bill.Amount),
labels...,
)
}
}
110 changes: 110 additions & 0 deletions collector/bill_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2019-2022 The sakuracloud_exporter Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package collector

import (
"context"
"errors"
"testing"
"time"

"github.com/prometheus/client_golang/prometheus"
"github.com/sacloud/iaas-api-go"
"github.com/sacloud/sakuracloud_exporter/platform"
"github.com/stretchr/testify/require"
)

type dummyBillClient struct {
bill *iaas.Bill
err error
}

func (d *dummyBillClient) Read(ctx context.Context) (*iaas.Bill, error) {
return d.bill, d.err
}

func TestBillCollector_Describe(t *testing.T) {
initLoggerAndErrors()
c := NewBillCollector(context.Background(), testLogger, testErrors, &dummyBillClient{})

descs := collectDescs(c)
require.Len(t, descs, len([]*prometheus.Desc{
c.Amount,
}))
}

func TestBillCollector_Collect(t *testing.T) {
initLoggerAndErrors()
c := NewBillCollector(context.Background(), testLogger, testErrors, nil)

cases := []struct {
name string
in platform.BillClient
wantLogs []string
wantErrCounter float64
wantMetrics []*collectedMetric
}{
{
name: "collector returns error",
in: &dummyBillClient{
err: errors.New("dummy"),
},
wantLogs: []string{`level=warn msg="can't get bill" err=dummy`},
wantErrCounter: 1,
wantMetrics: nil,
},
{
name: "empty result",
in: &dummyBillClient{},
wantMetrics: nil,
},
{
name: "a bill",
in: &dummyBillClient{
bill: &iaas.Bill{
ID: 101,
Amount: 1234,
Date: time.Now(),
MemberID: "memberID",
Paid: false,
PayLimit: time.Now(),
PaymentClassID: 0,
},
},
wantMetrics: []*collectedMetric{
{
// Discount
desc: c.Amount,
metric: createGaugeMetric(1234, map[string]string{
"member_id": "memberID",
}),
},
},
},
}

for _, tc := range cases {
initLoggerAndErrors()
c.logger = testLogger
c.errors = testErrors
c.client = tc.in

collected, err := collectMetrics(c, "bill")
require.NoError(t, err)
require.Equal(t, tc.wantLogs, collected.logged)
require.Equal(t, tc.wantErrCounter, *collected.errors.Counter.Value)
requireMetricsEqual(t, tc.wantMetrics, collected.collected)
}
}
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Config struct {
RateLimit int `arg:"env:SAKURACLOUD_RATE_LIMIT" help:"Rate limit per second for SakuraCloud API calls"`

NoCollectorAutoBackup bool `arg:"--no-collector.auto-backup" help:"Disable the AutoBackup collector"`
NoCollectorBill bool `arg:"--no-collector.bill" help:"Disable the Bill collector"`
NoCollectorCoupon bool `arg:"--no-collector.coupon" help:"Disable the Coupon collector"`
NoCollectorDatabase bool `arg:"--no-collector.database" help:"Disable the Database collector"`
NoCollectorESME bool `arg:"--no-collector.esme" help:"Disable the ESME collector"`
Expand Down
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ func main() {
if !c.NoCollectorAutoBackup {
r.MustRegister(collector.NewAutoBackupCollector(ctx, logger, errs, client.AutoBackup))
}
if !c.NoCollectorBill {
r.MustRegister(collector.NewBillCollector(ctx, logger, errs, client.Bill))
}
if !c.NoCollectorCoupon {
r.MustRegister(collector.NewCouponCollector(ctx, logger, errs, client.Coupon))
}
Expand Down
78 changes: 78 additions & 0 deletions platform/bill.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2019-2022 The sakuracloud_exporter Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package platform

import (
"context"
"errors"
"fmt"
"sync"

"github.com/sacloud/iaas-api-go"
"github.com/sacloud/iaas-api-go/types"
)

// BillClient calls SakuraCloud bill API
type BillClient interface {
Read(context.Context) (*iaas.Bill, error)
}

func getBillClient(caller iaas.APICaller) BillClient {
return &billClient{caller: caller}
}

type billClient struct {
caller iaas.APICaller
accountID types.ID
once sync.Once
}

func (c *billClient) Read(ctx context.Context) (*iaas.Bill, error) {
var err error
c.once.Do(func() {
var auth *iaas.AuthStatus

authStatusOp := iaas.NewAuthStatusOp(c.caller)
auth, err = authStatusOp.Read(ctx)
if err != nil {
return
}
if !auth.ExternalPermission.PermittedBill() {
err = fmt.Errorf("account doesn't have permissions to use the Billing API")
}
c.accountID = auth.AccountID
})
if err != nil {
return nil, err
}
if c.accountID.IsEmpty() {
return nil, errors.New("getting AccountID is failed. please check your API Key settings")
}

billOp := iaas.NewBillOp(c.caller)
searched, err := billOp.ByContract(ctx, c.accountID)
if err != nil {
return nil, err
}

var bill *iaas.Bill
for i := range searched.Bills {
b := searched.Bills[i]
if i == 0 || bill.Date.Before(b.Date) {
bill = b
}
}
return bill, nil
}
2 changes: 2 additions & 0 deletions platform/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
type Client struct {
authStatus authStatusClient
AutoBackup AutoBackupClient
Bill BillClient
Coupon CouponClient
Database DatabaseClient
ESME ESMEClient
Expand Down Expand Up @@ -83,6 +84,7 @@ func NewSakuraCloudClient(c config.Config, version string) *Client {
return &Client{
authStatus: getAuthStatusClient(caller),
AutoBackup: getAutoBackupClient(caller, c.Zones),
Bill: getBillClient(caller),
Coupon: getCouponClient(caller),
Database: getDatabaseClient(caller, c.Zones),
ESME: getESMEClient(caller),
Expand Down

0 comments on commit f0450ad

Please sign in to comment.