Skip to content

Commit 35c358a

Browse files
Rework domain/controllerconfig package
The controller config package in domain was wrong. We removed required fields from the controller config and we coerced on the way in and on the way out, each with different outcomes. The following fixes that by not validating on the way in, instead we just serialize (encode) all the config values to strings. Then on the way out we just coerce them when required. This ensures that the controller config map always has the right values at the right time.
1 parent 1457f61 commit 35c358a

File tree

14 files changed

+339
-166
lines changed

14 files changed

+339
-166
lines changed

apiserver/facades/agent/agent/agent.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import (
2424
"github.com/juju/juju/state"
2525
)
2626

27-
// ControllerConfigGetter is the interface that gets ControllerConfig form DB.
28-
type ControllerConfigGetter interface {
27+
// ControllerConfigService is the interface that gets ControllerConfig form DB.
28+
type ControllerConfigService interface {
2929
ControllerConfig(context.Context) (controller.Config, error)
3030
}
3131

@@ -37,19 +37,19 @@ type AgentAPI struct {
3737
*common.ControllerConfigAPI
3838
cloudspec.CloudSpecer
3939

40-
credentialService CredentialService
41-
controllerConfigGetter ControllerConfigGetter
42-
st *state.State
43-
auth facade.Authorizer
44-
resources facade.Resources
40+
credentialService CredentialService
41+
controllerConfigService ControllerConfigService
42+
st *state.State
43+
auth facade.Authorizer
44+
resources facade.Resources
4545
}
4646

4747
// NewAgentAPI returns an agent API facade.
4848
func NewAgentAPI(
4949
auth facade.Authorizer,
5050
resources facade.Resources,
5151
st *state.State,
52-
controllerConfigGetter ControllerConfigGetter,
52+
controllerConfigService ControllerConfigService,
5353
externalController common.ExternalControllerService,
5454
credentialService common.CredentialService,
5555
) (*AgentAPI, error) {
@@ -77,11 +77,11 @@ func NewAgentAPI(
7777
cloudspec.MakeCloudSpecCredentialContentWatcherForModel(st, credentialService),
7878
common.AuthFuncForTag(model.ModelTag()),
7979
),
80-
credentialService: credentialService,
81-
controllerConfigGetter: controllerConfigGetter,
82-
st: st,
83-
auth: auth,
84-
resources: resources,
80+
credentialService: credentialService,
81+
controllerConfigService: controllerConfigService,
82+
st: st,
83+
auth: auth,
84+
resources: resources,
8585
}, nil
8686
}
8787

@@ -137,7 +137,7 @@ func (api *AgentAPI) StateServingInfo(ctx context.Context) (result params.StateS
137137
return params.StateServingInfo{}, errors.Trace(err)
138138
}
139139
// ControllerAPIPort comes from the controller config.
140-
config, err := api.controllerConfigGetter.ControllerConfig(ctx)
140+
config, err := api.controllerConfigService.ControllerConfig(ctx)
141141
if err != nil {
142142
return params.StateServingInfo{}, errors.Trace(err)
143143
}

controller/config_test.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"fmt"
1010
"io"
1111
"net/http"
12-
stdtesting "testing"
1312
"time"
1413

1514
"github.com/juju/collections/set"
@@ -25,10 +24,6 @@ import (
2524
"github.com/juju/juju/testing"
2625
)
2726

28-
func Test(t *stdtesting.T) {
29-
gc.TestingT(t)
30-
}
31-
3227
type ConfigSuite struct {
3328
testing.FakeJujuXDGDataHomeSuite
3429
}

controller/package_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2023 Canonical Ltd.
2+
// Licensed under the AGPLv3, see LICENCE file for details.
3+
4+
package controller_test
5+
6+
import (
7+
stdtesting "testing"
8+
9+
gc "gopkg.in/check.v1"
10+
)
11+
12+
func Test(t *stdtesting.T) {
13+
gc.TestingT(t)
14+
}

controller/serialize.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2023 Canonical Ltd.
2+
// Licensed under the AGPLv3, see LICENCE file for details.
3+
4+
package controller
5+
6+
import (
7+
"fmt"
8+
"time"
9+
10+
"github.com/juju/errors"
11+
)
12+
13+
// EncodeToString encodes the given controller config into a map of strings.
14+
func EncodeToString(cfg Config) (map[string]string, error) {
15+
result := make(map[string]string, len(cfg))
16+
for key, v := range cfg {
17+
switch v := v.(type) {
18+
case string:
19+
result[key] = v
20+
case bool:
21+
result[key] = fmt.Sprintf("%t", v)
22+
case int, int8, int16, int32, int64:
23+
result[key] = fmt.Sprintf("%d", v)
24+
case uint, uint8, uint16, uint32, uint64:
25+
result[key] = fmt.Sprintf("%d", v)
26+
case float32, float64:
27+
result[key] = fmt.Sprintf("%f", v)
28+
case time.Duration:
29+
result[key] = v.String()
30+
case time.Time:
31+
result[key] = v.Format(time.RFC3339Nano)
32+
default:
33+
return nil, errors.Errorf("unable to serialize controller config key %q: unknown type %T", key, v)
34+
}
35+
}
36+
return result, nil
37+
}

controller/serialize_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2023 Canonical Ltd.
2+
// Licensed under the AGPLv3, see LICENCE file for details.
3+
4+
package controller_test
5+
6+
import (
7+
"github.com/juju/testing"
8+
jc "github.com/juju/testing/checkers"
9+
gc "gopkg.in/check.v1"
10+
11+
"github.com/juju/juju/controller"
12+
jujutesting "github.com/juju/juju/testing"
13+
)
14+
15+
type EncodeToStringSuite struct {
16+
testing.IsolationSuite
17+
}
18+
19+
var _ = gc.Suite(&EncodeToStringSuite{})
20+
21+
func (s *EncodeToStringSuite) TestEncodeToString(c *gc.C) {
22+
cfg := jujutesting.FakeControllerConfig()
23+
24+
encoded, err := controller.EncodeToString(cfg)
25+
c.Assert(err, jc.ErrorIsNil)
26+
27+
c.Assert(encoded, gc.DeepEquals, map[string]string{
28+
"controller-uuid": jujutesting.ControllerTag.Id(),
29+
"ca-cert": jujutesting.CACert,
30+
"state-port": "1234",
31+
"api-port": "17777",
32+
"set-numa-control-policy": "false",
33+
"model-logfile-max-backups": "1",
34+
"model-logfile-max-size": "1M",
35+
"model-logs-size": "1M",
36+
"max-txn-log-size": "10M",
37+
"auditing-enabled": "false",
38+
"audit-log-capture-args": "true",
39+
"audit-log-max-size": "200M",
40+
"audit-log-max-backups": "5",
41+
"query-tracing-threshold": "1s",
42+
})
43+
}

domain/controllerconfig/bootstrap/bootstrap.go

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,16 @@ import (
1818
// into the database.
1919
func InsertInitialControllerConfig(cfg controller.Config) func(context.Context, database.TxnRunner) error {
2020
return func(ctx context.Context, db database.TxnRunner) error {
21-
fields, _, err := controller.ConfigSchema.ValidationSchema()
21+
values, err := controller.EncodeToString(cfg)
2222
if err != nil {
2323
return errors.Trace(err)
2424
}
2525

26-
values := make(map[string]any)
27-
for k, v := range cfg {
28-
if field, ok := fields[k]; ok {
29-
if v, err = field.Coerce(v, []string{k}); err != nil {
30-
return errors.Trace(err)
31-
}
32-
}
33-
values[k] = v
34-
}
26+
query := "INSERT INTO controller_config (key, value) VALUES (?, ?)"
3527

3628
return errors.Trace(db.StdTxn(ctx, func(ctx context.Context, tx *sql.Tx) error {
3729
for k, v := range values {
38-
q := "INSERT INTO controller_config (key, value) VALUES (?, ?)"
39-
if _, err := tx.ExecContext(ctx, q, k, v); err != nil {
30+
if _, err := tx.ExecContext(ctx, query, k, v); err != nil {
4031
if jujudatabase.IsErrConstraintPrimaryKey(errors.Cause(err)) {
4132
return errors.AlreadyExistsf("controller configuration key %q", k)
4233
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2023 Canonical Ltd.
2+
// Licensed under the AGPLv3, see LICENCE file for details.
3+
4+
package controllerconfig
5+
6+
import (
7+
ctx "context"
8+
"time"
9+
10+
"github.com/juju/collections/set"
11+
jc "github.com/juju/testing/checkers"
12+
gc "gopkg.in/check.v1"
13+
14+
"github.com/juju/juju/controller"
15+
"github.com/juju/juju/domain/controllerconfig/bootstrap"
16+
"github.com/juju/juju/domain/controllerconfig/service"
17+
domainstate "github.com/juju/juju/domain/controllerconfig/state"
18+
schematesting "github.com/juju/juju/domain/schema/testing"
19+
jujutesting "github.com/juju/juju/testing"
20+
)
21+
22+
type controllerconfigSuite struct {
23+
schematesting.ControllerSuite
24+
}
25+
26+
var _ = gc.Suite(&controllerconfigSuite{})
27+
28+
func (s *controllerconfigSuite) TestControllerConfigRoundTrips(c *gc.C) {
29+
st := domainstate.NewState(s.TxnRunnerFactory())
30+
srv := service.NewService(st, nil)
31+
32+
cfgMap := map[string]any{
33+
controller.AuditingEnabled: true,
34+
controller.AuditLogCaptureArgs: false,
35+
controller.AuditLogMaxBackups: 10,
36+
controller.PublicDNSAddress: "controller.test.com:1234",
37+
controller.APIPortOpenDelay: "100ms",
38+
controller.MigrationMinionWaitMax: "101ms",
39+
controller.PruneTxnSleepTime: "102ms",
40+
controller.QueryTracingThreshold: "103ms",
41+
controller.MaxDebugLogDuration: "104ms",
42+
}
43+
cfgIn, err := controller.NewConfig(
44+
jujutesting.ControllerTag.Id(),
45+
jujutesting.CACert,
46+
cfgMap,
47+
)
48+
c.Assert(err, jc.ErrorIsNil)
49+
50+
err = bootstrap.InsertInitialControllerConfig(cfgIn)(ctx.Background(), s.TxnRunner())
51+
c.Assert(err, jc.ErrorIsNil)
52+
53+
cfgOut, err := srv.ControllerConfig(ctx.Background())
54+
c.Assert(err, jc.ErrorIsNil)
55+
56+
selected := filterConfig(cfgOut)
57+
58+
err = srv.UpdateControllerConfig(ctx.Background(), selected, nil)
59+
c.Assert(err, jc.ErrorIsNil)
60+
61+
cfgOut, err = srv.ControllerConfig(ctx.Background())
62+
c.Assert(err, jc.ErrorIsNil)
63+
64+
c.Assert(cfgOut.AuditingEnabled(), jc.IsTrue)
65+
c.Assert(cfgOut.AuditLogCaptureArgs(), jc.IsFalse)
66+
c.Assert(cfgOut.AuditLogMaxBackups(), gc.Equals, 10)
67+
c.Assert(cfgOut.PublicDNSAddress(), gc.Equals, "controller.test.com:1234")
68+
c.Assert(cfgOut.APIPortOpenDelay(), gc.Equals, 100*time.Millisecond)
69+
c.Assert(cfgOut.MigrationMinionWaitMax(), gc.Equals, 101*time.Millisecond)
70+
c.Assert(cfgOut.PruneTxnSleepTime(), gc.Equals, 102*time.Millisecond)
71+
c.Assert(cfgOut.QueryTracingThreshold(), gc.Equals, 103*time.Millisecond)
72+
c.Assert(cfgOut.MaxDebugLogDuration(), gc.Equals, 104*time.Millisecond)
73+
}
74+
75+
func keys(m map[string]any) set.Strings {
76+
var result []string
77+
for k := range m {
78+
result = append(result, k)
79+
}
80+
return set.NewStrings(result...)
81+
}
82+
83+
func filterConfig(m map[string]any) map[string]any {
84+
k := keys(m).Difference(controller.AllowedUpdateConfigAttributes)
85+
for _, key := range k.Values() {
86+
delete(m, key)
87+
}
88+
return m
89+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2023 Canonical Ltd.
2+
// Licensed under the AGPLv3, see LICENCE file for details.
3+
4+
package controllerconfig
5+
6+
import (
7+
"testing"
8+
9+
gc "gopkg.in/check.v1"
10+
)
11+
12+
func TestPackage(t *testing.T) {
13+
gc.TestingT(t)
14+
}

domain/controllerconfig/service/controllerconfig_test.go

Lines changed: 0 additions & 53 deletions
This file was deleted.

domain/controllerconfig/service/package_mock_test.go

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)