Skip to content

Commit

Permalink
Merge pull request juju#17002 from manadart/dqlite-config-id
Browse files Browse the repository at this point in the history
juju#17002

The controller _charm_ needs to set configuration for the controller at an appropriate path. To do this, it needs to know the controller's ID, which may not be the same as the charm's ID.

Here we add a path the controller agent configuration socket that returns this ID. This streamlines the determination for both machines, and Kubernetes where we can not access the jujud process information from another container.

## QA steps

- Bootstrap on LXD and/or K8s:
- Where the charm is running, issue :
```
sudo curl -i -X GET http://localhost/agent-id --unix-socket /var/lib/juju/configchange.socket
```
- Observe the result:
```
HTTP/1.1 200 OK
Content-Type: application/text
Date: Mon, 04 Mar 2024 13:20:10 GMT
Content-Length: 1

0
```

## Documentation changes

Not at this time.

## Links

**Jira card:** [JUJU-5306](https://warthogs.atlassian.net/browse/JUJU-5306)



[JUJU-5306]: https://warthogs.atlassian.net/browse/JUJU-5306?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
  • Loading branch information
jujubot authored Mar 4, 2024
2 parents 4bfe4ab + c4b0bf3 commit 7010bcb
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 15 deletions.
1 change: 1 addition & 0 deletions cmd/jujud-controller/agent/machine/manifolds.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ func commonManifolds(config ManifoldsConfig) dependency.Manifolds {
// Controller agent config manifold watches the controller
// agent config and bounces if it changes.
controllerAgentConfigName: ifController(controlleragentconfig.Manifold(controlleragentconfig.ManifoldConfig{
AgentName: agentName,
Clock: config.Clock,
Logger: loggo.GetLogger("juju.worker.controlleragentconfig"),
NewSocketListener: controlleragentconfig.NewSocketListener,
Expand Down
8 changes: 6 additions & 2 deletions cmd/jujud-controller/agent/safemode/manifolds.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
coreagent "github.com/juju/juju/agent"
"github.com/juju/juju/agent/engine"
"github.com/juju/juju/cmd/jujud-controller/util"
"github.com/juju/juju/core/paths"
"github.com/juju/juju/internal/worker/agent"
"github.com/juju/juju/internal/worker/controlleragentconfig"
"github.com/juju/juju/internal/worker/dbaccessor"
Expand Down Expand Up @@ -99,8 +100,11 @@ func commonManifolds(config ManifoldsConfig) dependency.Manifolds {
// Controller agent config manifold watches the controller
// agent config and bounces if it changes.
controllerAgentConfigName: ifController(controlleragentconfig.Manifold(controlleragentconfig.ManifoldConfig{
Clock: config.Clock,
Logger: loggo.GetLogger("juju.worker.controlleragentconfig"),
AgentName: agentName,
Clock: config.Clock,
Logger: loggo.GetLogger("juju.worker.controlleragentconfig"),
NewSocketListener: controlleragentconfig.NewSocketListener,
SocketName: paths.ConfigChangeSocket(paths.OSUnixLike),
})),

// The stateconfigwatcher manifold watches the machine agent's
Expand Down
2 changes: 1 addition & 1 deletion internal/socketlistener/package_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2024 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package socketlistener_test
package socketlistener

import (
"testing"
Expand Down
18 changes: 16 additions & 2 deletions internal/worker/controlleragentconfig/manifold.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/juju/worker/v4"
"github.com/juju/worker/v4/dependency"

"github.com/juju/juju/agent"
"github.com/juju/juju/internal/socketlistener"
)

Expand All @@ -35,8 +36,9 @@ type Logger interface {
// ManifoldConfig defines the configuration for the agent controller config
// manifold.
type ManifoldConfig struct {
Logger Logger
Clock clock.Clock
AgentName string
Logger Logger
Clock clock.Clock
// SocketName is the socket file descriptor.
SocketName string
// NewSocketListener is the function that creates a new socket listener.
Expand All @@ -45,6 +47,9 @@ type ManifoldConfig struct {

// Validate validates the manifold configuration.
func (cfg ManifoldConfig) Validate() error {
if cfg.AgentName == "" {
return errors.NotValidf("empty AgentName")
}
if cfg.Logger == nil {
return errors.NotValidf("nil Logger")
}
Expand All @@ -63,13 +68,22 @@ func (cfg ManifoldConfig) Validate() error {
// Manifold returns a dependency manifold that runs the trace worker.
func Manifold(config ManifoldConfig) dependency.Manifold {
return dependency.Manifold{
Inputs: []string{
config.AgentName,
},
Output: configOutput,
Start: func(ctx context.Context, getter dependency.Getter) (worker.Worker, error) {
if err := config.Validate(); err != nil {
return nil, errors.Trace(err)
}

var thisAgent agent.Agent
if err := getter.Get(config.AgentName, &thisAgent); err != nil {
return nil, errors.Trace(err)
}

w, err := NewWorker(WorkerConfig{
ControllerID: thisAgent.CurrentConfig().Tag().Id(),
Logger: config.Logger,
Clock: config.Clock,
NewSocketListener: config.NewSocketListener,
Expand Down
46 changes: 42 additions & 4 deletions internal/worker/controlleragentconfig/manifold_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,41 @@ import (

"github.com/juju/clock"
"github.com/juju/errors"
"github.com/juju/names/v5"
jc "github.com/juju/testing/checkers"
"github.com/juju/worker/v4/dependency"
dependencytesting "github.com/juju/worker/v4/dependency/testing"
"github.com/juju/worker/v4/workertest"
gc "gopkg.in/check.v1"

"github.com/juju/juju/agent"
)

type manifoldSuite struct {
baseSuite

agent *mockAgent
}

var _ = gc.Suite(&manifoldSuite{})

func (s *manifoldSuite) SetUpTest(c *gc.C) {
s.baseSuite.SetUpTest(c)

s.agent = new(mockAgent)
s.agent.conf.tag = names.NewMachineTag("99")
}

func (s *manifoldSuite) TestValidateConfig(c *gc.C) {
defer s.setupMocks(c).Finish()

cfg := s.getConfig()
c.Check(cfg.Validate(), jc.ErrorIsNil)

cfg = s.getConfig()
cfg.AgentName = ""
c.Check(cfg.Validate(), jc.ErrorIs, errors.NotValid)

cfg = s.getConfig()
cfg.Logger = nil
c.Check(cfg.Validate(), jc.ErrorIs, errors.NotValid)
Expand All @@ -46,6 +62,7 @@ func (s *manifoldSuite) TestValidateConfig(c *gc.C) {

func (s *manifoldSuite) getConfig() ManifoldConfig {
return ManifoldConfig{
AgentName: "agent",
Logger: s.logger,
Clock: clock.WallClock,
NewSocketListener: NewSocketListener,
Expand All @@ -54,14 +71,14 @@ func (s *manifoldSuite) getConfig() ManifoldConfig {
}

func (s *manifoldSuite) newContext() dependency.Getter {
resources := map[string]any{}
resources := map[string]any{
"agent": s.agent,
}
return dependencytesting.StubGetter(resources)
}

var expectedInputs = []string{}

func (s *manifoldSuite) TestInputs(c *gc.C) {
c.Assert(Manifold(s.getConfig()).Inputs, jc.SameContents, expectedInputs)
c.Assert(Manifold(s.getConfig()).Inputs, jc.SameContents, []string{"agent"})
}

func (s *manifoldSuite) TestStart(c *gc.C) {
Expand All @@ -84,3 +101,24 @@ func (s *manifoldSuite) TestOutput(c *gc.C) {
c.Assert(man.Output(w, &watcher), jc.ErrorIsNil)
c.Assert(watcher, gc.NotNil)
}

type mockAgent struct {
agent.Agent
conf mockConfig
}

func (ma *mockAgent) CurrentConfig() agent.Config {
return &ma.conf
}

type mockConfig struct {
agent.ConfigSetter
tag names.Tag
}

func (mc *mockConfig) Tag() names.Tag {
if mc.tag == nil {
return names.NewMachineTag("99")
}
return mc.tag
}
16 changes: 16 additions & 0 deletions internal/worker/controlleragentconfig/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ const (
// WorkerConfig encapsulates the configuration options for the
// agent controller config worker.
type WorkerConfig struct {
// ControllerID is the ID of this controller.
ControllerID string
// Logger writes log messages.
Logger Logger
// Clock is needed for worker.NewRunner.
Clock clock.Clock
Expand Down Expand Up @@ -136,6 +139,8 @@ func newWorker(cfg WorkerConfig, internalStates chan string) (*configWorker, err
func (w *configWorker) registerHandlers(r *mux.Router) {
r.HandleFunc("/reload", w.reloadHandler).
Methods(http.MethodPost)
r.HandleFunc("/agent-id", w.idHandler).
Methods(http.MethodGet)
}

// reloadHandler sends a signal to the configWorker when a config reload is
Expand All @@ -151,6 +156,17 @@ func (w *configWorker) reloadHandler(resp http.ResponseWriter, req *http.Request
}
}

// idHandler simply returns this agent's ID.
// It is used by the *unit* to get the *controller's* ID.
func (w *configWorker) idHandler(resp http.ResponseWriter, req *http.Request) {
resp.Header().Set("Content-Type", "application/text")

_, err := resp.Write([]byte(w.cfg.ControllerID))
if err != nil {
w.cfg.Logger.Errorf("error writing HTTP response: %v", err)
}
}

// Kill is part of the worker.Worker interface.
func (w *configWorker) Kill() {
w.catacomb.Kill(nil)
Expand Down
35 changes: 29 additions & 6 deletions internal/worker/controlleragentconfig/worker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package controlleragentconfig

import (
"context"
"io"
"net"
"net/http"
"path"
Expand Down Expand Up @@ -38,6 +39,27 @@ func (s *workerSuite) TestStartup(c *gc.C) {
workertest.CleanKill(c, w)
}

func (s *workerSuite) TestIDRequest(c *gc.C) {
defer s.setupMocks(c).Finish()

w, socket, states := s.newWorker(c)
defer workertest.DirtyKill(c, w)

s.ensureStartup(c, states)

resp, err := newRequest(c, socket, "/agent-id", http.MethodGet)
c.Assert(err, jc.ErrorIsNil)
defer func() { _ = resp.Body.Close() }()

c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)

content, err := io.ReadAll(resp.Body)
c.Assert(err, jc.ErrorIsNil)
c.Check(string(content), gc.Equals, "99")

workertest.CleanKill(c, w)
}

func (s *workerSuite) TestReloadRequest(c *gc.C) {
defer s.setupMocks(c).Finish()

Expand Down Expand Up @@ -342,6 +364,7 @@ func (s *workerSuite) newWorker(c *gc.C) (*configWorker, string, chan string) {
socket := path.Join(tmpDir, "test.socket")

w, err := newWorker(WorkerConfig{
ControllerID: "99",
Logger: s.logger,
Clock: clock.WallClock,
SocketName: socket,
Expand Down Expand Up @@ -370,18 +393,18 @@ func (s *workerSuite) ensureReload(c *gc.C, states chan string) {
}

func (s *workerSuite) requestReload(c *gc.C, socket string) {
resp, err := newRequest(c, socket, "/reload")
resp, err := newRequest(c, socket, "/reload", http.MethodPost)
c.Assert(err, jc.ErrorIsNil)
c.Assert(resp.StatusCode, gc.Equals, http.StatusNoContent)
}

func (s *workerSuite) ensureReloadRequestRefused(c *gc.C, socket string) {
_, err := newRequest(c, socket, "/reload")
_, err := newRequest(c, socket, "/reload", http.MethodPost)
c.Assert(err, jc.ErrorIs, syscall.ECONNREFUSED)
}

func (s *workerSuite) ensureEndpointNotFound(c *gc.C, socket, method string) {
resp, err := newRequest(c, socket, method)
resp, err := newRequest(c, socket, method, http.MethodPost)
c.Assert(err, jc.ErrorIsNil)
c.Assert(resp.StatusCode, gc.Equals, http.StatusNotFound)
}
Expand All @@ -394,10 +417,10 @@ func ensureDone(c *gc.C, watcher ConfigWatcher) {
}
}

func newRequest(c *gc.C, socket, method string) (*http.Response, error) {
serverURL := "http://localhost:8080" + method
func newRequest(c *gc.C, socket, path, method string) (*http.Response, error) {
serverURL := "http://localhost:8080" + path
req, err := http.NewRequest(
http.MethodPost,
method,
serverURL,
nil,
)
Expand Down

0 comments on commit 7010bcb

Please sign in to comment.