Skip to content

Commit b292164

Browse files
authored
Merge pull request #629 from weaveworks/cleanup
Add endpoints to deactivate and restore config
2 parents 73a2946 + 0ff7de4 commit b292164

File tree

8 files changed

+181
-17
lines changed

8 files changed

+181
-17
lines changed

pkg/configs/api/api.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ func (a *API) RegisterRoutes(r *mux.Router) {
6161
{"get_alertmanager_config", "GET", "/api/prom/configs/alertmanager", a.getConfig},
6262
{"set_alertmanager_config", "POST", "/api/prom/configs/alertmanager", a.setConfig},
6363
{"validate_alertmanager_config", "POST", "/api/prom/configs/alertmanager/validate", a.validateAlertmanagerConfig},
64+
{"deactivate_config", "DELETE", "/api/prom/configs/deactivate", a.deactivateConfig},
65+
{"restore_config", "POST", "/api/prom/configs/restore", a.restoreConfig},
6466
// Internal APIs.
6567
{"private_get_rules", "GET", "/private/api/prom/configs/rules", a.getConfigs},
6668
{"private_get_alertmanager_config", "GET", "/private/api/prom/configs/alertmanager", a.getConfigs},
@@ -217,3 +219,47 @@ func (a *API) getConfigs(w http.ResponseWriter, r *http.Request) {
217219
return
218220
}
219221
}
222+
223+
func (a *API) deactivateConfig(w http.ResponseWriter, r *http.Request) {
224+
userID, _, err := user.ExtractOrgIDFromHTTPRequest(r)
225+
if err != nil {
226+
http.Error(w, err.Error(), http.StatusUnauthorized)
227+
return
228+
}
229+
logger := util.WithContext(r.Context(), util.Logger)
230+
231+
if err := a.db.DeactivateConfig(userID); err != nil {
232+
if err == sql.ErrNoRows {
233+
level.Info(logger).Log("msg", "deactivate config - no configuration", "userID", userID)
234+
http.Error(w, "No configuration", http.StatusNotFound)
235+
return
236+
}
237+
level.Error(logger).Log("msg", "error deactivating config", "err", err)
238+
http.Error(w, err.Error(), http.StatusInternalServerError)
239+
return
240+
}
241+
level.Info(logger).Log("msg", "config deactivated", "userID", userID)
242+
w.WriteHeader(http.StatusOK)
243+
}
244+
245+
func (a *API) restoreConfig(w http.ResponseWriter, r *http.Request) {
246+
userID, _, err := user.ExtractOrgIDFromHTTPRequest(r)
247+
if err != nil {
248+
http.Error(w, err.Error(), http.StatusUnauthorized)
249+
return
250+
}
251+
logger := util.WithContext(r.Context(), util.Logger)
252+
253+
if err := a.db.RestoreConfig(userID); err != nil {
254+
if err == sql.ErrNoRows {
255+
level.Info(logger).Log("msg", "restore config - no configuration", "userID", userID)
256+
http.Error(w, "No configuration", http.StatusNotFound)
257+
return
258+
}
259+
level.Error(logger).Log("msg", "error restoring config", "err", err)
260+
http.Error(w, err.Error(), http.StatusInternalServerError)
261+
return
262+
}
263+
level.Info(logger).Log("msg", "config restored", "userID", userID)
264+
w.WriteHeader(http.StatusOK)
265+
}

pkg/configs/configs.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package configs
22

33
import (
44
"fmt"
5+
"time"
56

67
"github.com/prometheus/prometheus/promql"
78
"github.com/prometheus/prometheus/rules"
@@ -26,8 +27,9 @@ type Config struct {
2627
// _version_ of a configuration a unique ID and guarantees that later versions
2728
// have greater IDs.
2829
type View struct {
29-
ID ID `json:"id"`
30-
Config Config `json:"config"`
30+
ID ID `json:"id"`
31+
Config Config `json:"config"`
32+
DeletedAt time.Time `json:"deleted_at"`
3133
}
3234

3335
// GetVersionedRulesConfig specializes the view to just the rules config.
@@ -36,8 +38,9 @@ func (v View) GetVersionedRulesConfig() *VersionedRulesConfig {
3638
return nil
3739
}
3840
return &VersionedRulesConfig{
39-
ID: v.ID,
40-
Config: v.Config.RulesFiles,
41+
ID: v.ID,
42+
Config: v.Config.RulesFiles,
43+
DeletedAt: v.DeletedAt,
4144
}
4245
}
4346

@@ -93,6 +96,7 @@ func (c RulesConfig) Parse() ([]rules.Rule, error) {
9396
// VersionedRulesConfig is a RulesConfig together with a version.
9497
// `data Versioned a = Versioned { id :: ID , config :: a }`
9598
type VersionedRulesConfig struct {
96-
ID ID `json:"id"`
97-
Config RulesConfig `json:"config"`
99+
ID ID `json:"id"`
100+
Config RulesConfig `json:"config"`
101+
DeletedAt time.Time `json:"deleted_at"`
98102
}

pkg/configs/db/db.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ type DB interface {
4848
GetAllConfigs() (map[string]configs.View, error)
4949
GetConfigs(since configs.ID) (map[string]configs.View, error)
5050

51+
DeactivateConfig(userID string) error
52+
RestoreConfig(userID string) error
53+
5154
Close() error
5255
}
5356

pkg/configs/db/memory/memory.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package memory
22

33
import (
44
"database/sql"
5+
"time"
56

67
"github.com/weaveworks/cortex/pkg/configs"
78
)
@@ -52,6 +53,31 @@ func (d *DB) GetConfigs(since configs.ID) (map[string]configs.View, error) {
5253
return cfgs, nil
5354
}
5455

56+
// SetDeletedAtConfig sets a deletedAt for configuration
57+
// by adding a single new row with deleted_at set
58+
// the same as SetConfig is actually insert
59+
func (d *DB) SetDeletedAtConfig(userID string, deletedAt time.Time) error {
60+
cv, err := d.GetConfig(userID)
61+
if err != nil {
62+
return err
63+
}
64+
cv.DeletedAt = deletedAt
65+
cv.ID = configs.ID(d.id)
66+
d.cfgs[userID] = cv
67+
d.id++
68+
return nil
69+
}
70+
71+
// DeactivateConfig deactivates configuration for a user by creating new configuration with DeletedAt set to now
72+
func (d *DB) DeactivateConfig(userID string) error {
73+
return d.SetDeletedAtConfig(userID, time.Now())
74+
}
75+
76+
// RestoreConfig restores deactivated configuration for a user by creating new configuration with empty DeletedAt
77+
func (d *DB) RestoreConfig(userID string) error {
78+
return d.SetDeletedAtConfig(userID, time.Time{})
79+
}
80+
5581
// Close finishes using the db. Noop.
5682
func (d *DB) Close() error {
5783
return nil

pkg/configs/db/postgres/postgres.go

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ package postgres
33
import (
44
"database/sql"
55
"encoding/json"
6-
"errors"
6+
"time"
77

88
"github.com/Masterminds/squirrel"
99
"github.com/go-kit/kit/log/level"
1010
_ "github.com/lib/pq" // Import the postgres sql driver
1111
_ "github.com/mattes/migrate/driver/postgres" // Import the postgres migrations driver
1212
"github.com/mattes/migrate/migrate"
13+
"github.com/pkg/errors"
1314
"github.com/weaveworks/cortex/pkg/configs"
1415
"github.com/weaveworks/cortex/pkg/util"
1516
)
@@ -19,11 +20,12 @@ const (
1920
// schema so this isn't needed.
2021
entityType = "org"
2122
subsystem = "cortex"
23+
// timeout waiting for database connection to be established
24+
dbTimeout = 5 * time.Minute
2225
)
2326

2427
var (
25-
activeConfig = squirrel.Eq{
26-
"deleted_at": nil,
28+
allConfigs = squirrel.Eq{
2729
"owner_type": entityType,
2830
"subsystem": subsystem,
2931
}
@@ -42,8 +44,32 @@ type dbProxy interface {
4244
Prepare(query string) (*sql.Stmt, error)
4345
}
4446

47+
// dbWait waits for database connection to be established
48+
func dbWait(db *sql.DB) error {
49+
deadline := time.Now().Add(dbTimeout)
50+
var err error
51+
for tries := 0; time.Now().Before(deadline); tries++ {
52+
err = db.Ping()
53+
if err == nil {
54+
return nil
55+
}
56+
level.Warn(util.Logger).Log("msg", "db connection not established, retrying...", "error", err)
57+
time.Sleep(time.Second << uint(tries))
58+
}
59+
return errors.Wrapf(err, "db connection not established after %s", dbTimeout)
60+
}
61+
4562
// New creates a new postgres DB
4663
func New(uri, migrationsDir string) (DB, error) {
64+
db, err := sql.Open("postgres", uri)
65+
if err != nil {
66+
return DB{}, errors.Wrap(err, "cannot open postgres db")
67+
}
68+
69+
if err := dbWait(db); err != nil {
70+
return DB{}, errors.Wrap(err, "cannot establish db connection")
71+
}
72+
4773
if migrationsDir != "" {
4874
level.Info(util.Logger).Log("msg", "running database migrations...")
4975
if errs, ok := migrate.UpSync(uri, migrationsDir); !ok {
@@ -53,7 +79,7 @@ func New(uri, migrationsDir string) (DB, error) {
5379
return DB{}, errors.New("database migrations failed")
5480
}
5581
}
56-
db, err := sql.Open("postgres", uri)
82+
5783
return DB{
5884
dbProxy: db,
5985
StatementBuilderType: statementBuilder(db),
@@ -97,7 +123,7 @@ func (d DB) GetConfig(userID string) (configs.View, error) {
97123
var cfgBytes []byte
98124
err := d.Select("id", "config").
99125
From("configs").
100-
Where(squirrel.And{activeConfig, squirrel.Eq{"owner_id": userID}}).
126+
Where(squirrel.And{allConfigs, squirrel.Eq{"owner_id": userID}}).
101127
OrderBy("id DESC").
102128
Limit(1).
103129
QueryRow().Scan(&cfgView.ID, &cfgBytes)
@@ -123,13 +149,13 @@ func (d DB) SetConfig(userID string, cfg configs.Config) error {
123149

124150
// GetAllConfigs gets all of the configs.
125151
func (d DB) GetAllConfigs() (map[string]configs.View, error) {
126-
return d.findConfigs(activeConfig)
152+
return d.findConfigs(allConfigs)
127153
}
128154

129155
// GetConfigs gets all of the configs that have changed recently.
130156
func (d DB) GetConfigs(since configs.ID) (map[string]configs.View, error) {
131157
return d.findConfigs(squirrel.And{
132-
activeConfig,
158+
allConfigs,
133159
squirrel.Gt{"id": since},
134160
})
135161
}
@@ -214,17 +240,50 @@ func (d DB) findRulesConfigs(filter squirrel.Sqlizer) (map[string]configs.Versio
214240

215241
// GetAllRulesConfigs gets all alertmanager configs for all users.
216242
func (d DB) GetAllRulesConfigs() (map[string]configs.VersionedRulesConfig, error) {
217-
return d.findRulesConfigs(activeConfig)
243+
return d.findRulesConfigs(allConfigs)
218244
}
219245

220246
// GetRulesConfigs gets all the alertmanager configs that have changed since a given config.
221247
func (d DB) GetRulesConfigs(since configs.ID) (map[string]configs.VersionedRulesConfig, error) {
222248
return d.findRulesConfigs(squirrel.And{
223-
activeConfig,
249+
allConfigs,
224250
squirrel.Gt{"id": since},
225251
})
226252
}
227253

254+
// SetDeletedAtConfig sets a deletedAt for configuration
255+
// by adding a single new row with deleted_at set
256+
// the same as SetConfig is actually insert
257+
func (d DB) SetDeletedAtConfig(userID string, deletedAt time.Time, cfg configs.Config) error {
258+
cfgBytes, err := json.Marshal(cfg)
259+
if err != nil {
260+
return err
261+
}
262+
_, err = d.Insert("configs").
263+
Columns("owner_id", "owner_type", "subsystem", "deleted_at", "config").
264+
Values(userID, entityType, subsystem, deletedAt, cfgBytes).
265+
Exec()
266+
return err
267+
}
268+
269+
// DeactivateConfig deactivates a configuration.
270+
func (d DB) DeactivateConfig(userID string) error {
271+
cfg, err := d.GetConfig(userID)
272+
if err != nil {
273+
return err
274+
}
275+
return d.SetDeletedAtConfig(userID, time.Now(), cfg.Config)
276+
}
277+
278+
// RestoreConfig restores configuration.
279+
func (d DB) RestoreConfig(userID string) error {
280+
cfg, err := d.GetConfig(userID)
281+
if err != nil {
282+
return err
283+
}
284+
return d.SetDeletedAtConfig(userID, time.Time{}, cfg.Config)
285+
}
286+
228287
// Transaction runs the given function in a postgres transaction. If fn returns
229288
// an error the txn will be rolled back.
230289
func (d DB) Transaction(f func(DB) error) error {

pkg/configs/db/timed.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,18 @@ func (t timed) GetConfigs(since configs.ID) (cfgs map[string]configs.View, err e
6969
return
7070
}
7171

72+
func (t timed) DeactivateConfig(userID string) (err error) {
73+
return t.timeRequest("DeactivateConfig", func(_ context.Context) error {
74+
return t.d.DeactivateConfig(userID)
75+
})
76+
}
77+
78+
func (t timed) RestoreConfig(userID string) (err error) {
79+
return t.timeRequest("RestoreConfig", func(_ context.Context) error {
80+
return t.d.RestoreConfig(userID)
81+
})
82+
}
83+
7284
func (t timed) Close() error {
7385
return t.timeRequest("Close", func(_ context.Context) error {
7486
return t.d.Close()

pkg/configs/db/traced.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ func (t traced) GetConfigs(since configs.ID) (cfgs map[string]configs.View, err
3737
return t.d.GetConfigs(since)
3838
}
3939

40+
func (t traced) DeactivateConfig(userID string) (err error) {
41+
defer func() { t.trace("DeactivateConfig", userID, err) }()
42+
return t.d.DeactivateConfig(userID)
43+
}
44+
45+
func (t traced) RestoreConfig(userID string) (err error) {
46+
defer func() { t.trace("RestoreConfig", userID, err) }()
47+
return t.d.RestoreConfig(userID)
48+
}
49+
4050
func (t traced) Close() (err error) {
4151
defer func() { t.trace("Close", err) }()
4252
return t.d.Close()

pkg/ruler/scheduler.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,14 @@ func (s *scheduler) addNewConfigs(now time.Time, cfgs map[string]configs.Version
192192
level.Warn(util.Logger).Log("msg", "scheduler: invalid Cortex configuration", "user_id", userID, "err", err)
193193
continue
194194
}
195-
196195
level.Info(util.Logger).Log("msg", "scheduler: updating rules for user", "user_id", userID, "num_rules", len(rules))
197196
s.Lock()
198-
s.cfgs[userID] = rules
197+
// if deleted remove from map
198+
if !config.DeletedAt.IsZero() {
199+
delete(s.cfgs, userID)
200+
} else {
201+
s.cfgs[userID] = rules
202+
}
199203
s.Unlock()
200204
s.addWorkItem(workItem{userID, rules, now})
201205
}

0 commit comments

Comments
 (0)