Skip to content

Commit 109c45d

Browse files
easyCZroboquat
authored andcommitted
[usage] Setup controller and reconciler
1 parent bfcb0a9 commit 109c45d

File tree

6 files changed

+141
-2
lines changed

6 files changed

+141
-2
lines changed

components/usage/cmd/run.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ package cmd
77
import (
88
"github.com/gitpod-io/gitpod/common-go/baseserver"
99
"github.com/gitpod-io/gitpod/common-go/log"
10+
"github.com/gitpod-io/gitpod/usage/pkg/controller"
1011
"github.com/gitpod-io/gitpod/usage/pkg/db"
1112
"github.com/spf13/cobra"
1213
"net"
1314
"os"
15+
"time"
1416
)
1517

1618
func init() {
@@ -29,8 +31,6 @@ func run() *cobra.Command {
2931
Run: func(cmd *cobra.Command, args []string) {
3032
log.Init(ServiceName, Version, true, verbose)
3133

32-
log.Info("Hello world usage server")
33-
3434
_, err := db.Connect(db.ConnectionParams{
3535
User: os.Getenv("DB_USERNAME"),
3636
Password: os.Getenv("DB_PASSWORD"),
@@ -41,6 +41,17 @@ func run() *cobra.Command {
4141
log.WithError(err).Fatal("Failed to establish database connection.")
4242
}
4343

44+
ctrl, err := controller.New(1*time.Minute, controller.ReconcilerFunc(controller.HelloWorldReconciler))
45+
if err != nil {
46+
log.WithError(err).Fatal("Failed to initialize usage controller.")
47+
}
48+
49+
err = ctrl.Start()
50+
if err != nil {
51+
log.WithError(err).Fatal("Failed to start usage controller.")
52+
}
53+
defer ctrl.Stop()
54+
4455
srv, err := baseserver.New("usage")
4556
if err != nil {
4657
log.WithError(err).Fatal("Failed to initialize server.")

components/usage/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ require (
5858
github.com/gitpod-io/gitpod/common-go v0.0.0-00010101000000-000000000000
5959
github.com/go-sql-driver/mysql v1.6.0
6060
github.com/relvacode/iso8601 v1.1.0
61+
github.com/robfig/cron v1.2.0
6162
github.com/spf13/cobra v1.4.0
6263
github.com/stretchr/testify v1.7.0
6364
gorm.io/datatypes v1.0.6

components/usage/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0
299299
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
300300
github.com/relvacode/iso8601 v1.1.0 h1:2nV8sp0eOjpoKQ2vD3xSDygsjAx37NHG2UlZiCkDH4I=
301301
github.com/relvacode/iso8601 v1.1.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I=
302+
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
303+
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
302304
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
303305
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
304306
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package controller
6+
7+
import (
8+
"fmt"
9+
"github.com/gitpod-io/gitpod/common-go/log"
10+
"github.com/robfig/cron"
11+
"sync"
12+
"time"
13+
)
14+
15+
func New(schedule time.Duration, reconciler Reconciler) (*Controller, error) {
16+
return &Controller{
17+
schedule: schedule,
18+
reconciler: reconciler,
19+
scheduler: cron.NewWithLocation(time.UTC),
20+
}, nil
21+
}
22+
23+
type Controller struct {
24+
schedule time.Duration
25+
reconciler Reconciler
26+
27+
scheduler *cron.Cron
28+
29+
runningJobs sync.WaitGroup
30+
}
31+
32+
func (c *Controller) Start() error {
33+
log.Info("Starting usage controller.")
34+
35+
err := c.scheduler.AddFunc(fmt.Sprintf("@every %s", c.schedule.String()), cron.FuncJob(func() {
36+
log.Info("Starting usage reconciliation.")
37+
38+
c.runningJobs.Add(1)
39+
defer c.runningJobs.Done()
40+
41+
err := c.reconciler.Reconcile()
42+
if err != nil {
43+
log.WithError(err).Errorf("Reconciliation run failed.")
44+
} else {
45+
log.Info("Completed usage reconciliation run without errors.")
46+
}
47+
}))
48+
if err != nil {
49+
return fmt.Errorf("failed to add function to scheduler: %w", err)
50+
}
51+
52+
c.scheduler.Start()
53+
54+
return nil
55+
}
56+
57+
// Stop terminates the Controller and awaits for all running jobs to complete.
58+
func (c *Controller) Stop() {
59+
log.Info("Stopping usage controller.")
60+
// Stop any new jobs from running
61+
c.scheduler.Stop()
62+
63+
log.Info("Awaiting existing reconciliation runs to complete..")
64+
// Wait for existing jobs to finish
65+
c.runningJobs.Wait()
66+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package controller
6+
7+
import (
8+
"github.com/stretchr/testify/require"
9+
"testing"
10+
"time"
11+
)
12+
13+
func TestController(t *testing.T) {
14+
schedule := time.Second
15+
triggered := false
16+
17+
ctrl, err := New(schedule, ReconcilerFunc(func() error {
18+
triggered = true
19+
return nil
20+
}))
21+
require.NoError(t, err)
22+
23+
require.NoError(t, ctrl.Start())
24+
time.Sleep(schedule + 20*time.Millisecond)
25+
require.True(t, triggered, "must trigger reconciler function")
26+
ctrl.Stop()
27+
}
28+
29+
func TestController_GracefullyHandlesPanic(t *testing.T) {
30+
ctrl, err := New(20*time.Millisecond, ReconcilerFunc(func() error {
31+
panic("pls help")
32+
}))
33+
require.NoError(t, err)
34+
35+
require.NoError(t, ctrl.Start())
36+
ctrl.Stop()
37+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package controller
6+
7+
import "github.com/gitpod-io/gitpod/common-go/log"
8+
9+
type Reconciler interface {
10+
Reconcile() error
11+
}
12+
13+
type ReconcilerFunc func() error
14+
15+
func (f ReconcilerFunc) Reconcile() error {
16+
return f()
17+
}
18+
19+
func HelloWorldReconciler() error {
20+
log.Info("Hello world reconciler!")
21+
return nil
22+
}

0 commit comments

Comments
 (0)