Skip to content

Commit 249d0b6

Browse files
Add IsStandalone() to agent info, report management mode to gRPC (#4911)
* init commit * update runtime_comm * fix startup bug * fix tests, log
1 parent 6c20730 commit 249d0b6

File tree

7 files changed

+201
-21
lines changed

7 files changed

+201
-21
lines changed

internal/pkg/agent/application/coordinator/diagnostics_test.go

+5
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,7 @@ type fakeAgentInfo struct {
631631
snapshot bool
632632
version string
633633
unprivileged bool
634+
isStandalone bool
634635
}
635636

636637
func (a fakeAgentInfo) AgentID() string {
@@ -661,5 +662,9 @@ func (a fakeAgentInfo) Unprivileged() bool {
661662
return a.unprivileged
662663
}
663664

665+
func (a fakeAgentInfo) IsStandalone() bool {
666+
return a.isStandalone
667+
}
668+
664669
func (a fakeAgentInfo) ReloadID(ctx context.Context) error { panic("implement me") }
665670
func (a fakeAgentInfo) SetLogLevel(ctx context.Context, level string) error { panic("implement me") }

internal/pkg/agent/application/info/agent_id.go

+35-20
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ type ioStore interface {
4242

4343
// updateLogLevel updates log level and persists it to disk.
4444
func updateLogLevel(ctx context.Context, level string) error {
45-
ai, err := loadAgentInfoWithBackoff(ctx, false, defaultLogLevel, false)
45+
ai, _, err := loadAgentInfoWithBackoff(ctx, false, defaultLogLevel, false)
4646
if err != nil {
4747
return err
4848
}
@@ -71,51 +71,65 @@ func generateAgentID() (string, error) {
7171
return uid.String(), nil
7272
}
7373

74-
func getInfoFromStore(s ioStore, logLevel string) (*persistentAgentInfo, error) {
74+
// getInfoFromStore uses the IO store to return the config from agent.* fields in the config,
75+
// as well as a bool indicating if agent is running in standalone mode.
76+
func getInfoFromStore(s ioStore, logLevel string) (*persistentAgentInfo, bool, error) {
7577
agentConfigFile := paths.AgentConfigFile()
7678
reader, err := s.Load()
7779
if err != nil {
78-
return nil, fmt.Errorf("failed to load from ioStore: %w", err)
80+
return nil, false, fmt.Errorf("failed to load from ioStore: %w", err)
7981
}
8082

8183
// reader is closed by this function
8284
cfg, err := config.NewConfigFrom(reader)
8385
if err != nil {
84-
return nil, errors.New(err,
86+
return nil, false, errors.New(err,
8587
fmt.Sprintf("fail to read configuration %s for the agent", agentConfigFile),
8688
errors.TypeFilesystem,
8789
errors.M(errors.MetaKeyPath, agentConfigFile))
8890
}
8991

9092
configMap, err := cfg.ToMapStr()
9193
if err != nil {
92-
return nil, errors.New(err,
94+
return nil, false, errors.New(err,
9395
"failed to unpack stored config to map",
9496
errors.TypeFilesystem)
9597
}
9698

99+
// check fleet config. This behavior emulates configuration.IsStandalone
100+
fleetmode, fleetExists := configMap["fleet"]
101+
isStandalone := true
102+
if fleetExists {
103+
fleetCfg, ok := fleetmode.(map[string]interface{})
104+
if ok {
105+
if fleetCfg["enabled"] == true {
106+
isStandalone = false
107+
}
108+
}
109+
}
110+
97111
agentInfoSubMap, found := configMap[agentInfoKey]
98112
if !found {
99113
return &persistentAgentInfo{
100114
LogLevel: logLevel,
101115
MonitoringHTTP: monitoringConfig.DefaultConfig().HTTP,
102-
}, nil
116+
}, isStandalone, nil
103117
}
104118

105119
cc, err := config.NewConfigFrom(agentInfoSubMap)
106120
if err != nil {
107-
return nil, errors.New(err, "failed to create config from agent info submap")
121+
return nil, false, errors.New(err, "failed to create config from agent info submap")
108122
}
109123

110124
pid := &persistentAgentInfo{
111125
LogLevel: logLevel,
112126
MonitoringHTTP: monitoringConfig.DefaultConfig().HTTP,
113127
}
114128
if err := cc.Unpack(&pid); err != nil {
115-
return nil, errors.New(err, "failed to unpack stored config to map")
129+
return nil, false, errors.New(err, "failed to unpack stored config to map")
116130
}
117131

118-
return pid, nil
132+
return pid, isStandalone, nil
119133
}
120134

121135
func updateAgentInfo(s ioStore, agentInfo *persistentAgentInfo) error {
@@ -177,53 +191,54 @@ func yamlToReader(in interface{}) (io.Reader, error) {
177191
return bytes.NewReader(data), nil
178192
}
179193

180-
func loadAgentInfoWithBackoff(ctx context.Context, forceUpdate bool, logLevel string, createAgentID bool) (*persistentAgentInfo, error) {
194+
func loadAgentInfoWithBackoff(ctx context.Context, forceUpdate bool, logLevel string, createAgentID bool) (*persistentAgentInfo, bool, error) {
181195
var err error
182196
var ai *persistentAgentInfo
197+
var isStandalone bool
183198

184199
signal := make(chan struct{})
185200
backExp := backoff.NewExpBackoff(signal, 100*time.Millisecond, 3*time.Second)
186201

187202
for i := 0; i <= maxRetriesloadAgentInfo; i++ {
188203
backExp.Wait()
189-
ai, err = loadAgentInfo(ctx, forceUpdate, logLevel, createAgentID)
204+
ai, isStandalone, err = loadAgentInfo(ctx, forceUpdate, logLevel, createAgentID)
190205
if !errors.Is(err, filelock.ErrAppAlreadyRunning) {
191206
break
192207
}
193208
}
194209

195210
close(signal)
196-
return ai, err
211+
return ai, isStandalone, err
197212
}
198213

199-
func loadAgentInfo(ctx context.Context, forceUpdate bool, logLevel string, createAgentID bool) (*persistentAgentInfo, error) {
214+
func loadAgentInfo(ctx context.Context, forceUpdate bool, logLevel string, createAgentID bool) (*persistentAgentInfo, bool, error) {
200215
idLock := paths.AgentConfigFileLock()
201216
if err := idLock.TryLock(); err != nil {
202-
return nil, err
217+
return nil, false, err
203218
}
204219
//nolint:errcheck // keeping the same behavior, and making linter happy
205220
defer idLock.Unlock()
206221

207222
agentConfigFile := paths.AgentConfigFile()
208223
diskStore, err := storage.NewEncryptedDiskStore(ctx, agentConfigFile)
209224
if err != nil {
210-
return nil, fmt.Errorf("error instantiating encrypted disk store: %w", err)
225+
return nil, false, fmt.Errorf("error instantiating encrypted disk store: %w", err)
211226
}
212227

213-
agentInfo, err := getInfoFromStore(diskStore, logLevel)
228+
agentInfo, isStandalone, err := getInfoFromStore(diskStore, logLevel)
214229
if err != nil {
215-
return nil, fmt.Errorf("could not get agent info from store: %w", err)
230+
return nil, false, fmt.Errorf("could not get agent info from store: %w", err)
216231
}
217232

218233
if agentInfo != nil && !forceUpdate && (agentInfo.ID != "" || !createAgentID) {
219-
return agentInfo, nil
234+
return agentInfo, isStandalone, nil
220235
}
221236

222237
if err := updateID(agentInfo, diskStore); err != nil {
223-
return nil, fmt.Errorf("could not update agent ID on disk store: %w", err)
238+
return nil, false, fmt.Errorf("could not update agent ID on disk store: %w", err)
224239
}
225240

226-
return agentInfo, nil
241+
return agentInfo, isStandalone, nil
227242
}
228243

229244
func updateID(agentInfo *persistentAgentInfo, s ioStore) error {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License;
3+
// you may not use this file except in compliance with the Elastic License.
4+
5+
package info
6+
7+
import (
8+
"bytes"
9+
"context"
10+
"path/filepath"
11+
"runtime"
12+
"testing"
13+
"time"
14+
15+
"github.com/stretchr/testify/require"
16+
"gopkg.in/yaml.v3"
17+
18+
"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
19+
"github.com/elastic/elastic-agent/internal/pkg/agent/application/secret"
20+
"github.com/elastic/elastic-agent/internal/pkg/agent/storage"
21+
"github.com/elastic/elastic-agent/internal/pkg/agent/vault"
22+
)
23+
24+
func TestAgentIDStandaloneWorks(t *testing.T) {
25+
if runtime.GOOS == "darwin" {
26+
// vault requres extra perms on mac
27+
t.Skip()
28+
}
29+
// create a new encrypted disk store
30+
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
31+
defer cancel()
32+
33+
tmpPath := t.TempDir()
34+
paths.SetConfig(tmpPath)
35+
36+
vaultPath := filepath.Join(tmpPath, "vault")
37+
err := secret.CreateAgentSecret(ctx, vault.WithVaultPath(vaultPath))
38+
require.NoError(t, err)
39+
40+
setID := "test-id"
41+
testCfg := map[string]interface{}{
42+
"agent": map[string]interface{}{
43+
"id": setID,
44+
},
45+
}
46+
saveToStateStore(t, tmpPath, testCfg)
47+
48+
got, err := NewAgentInfo(ctx, false)
49+
require.NoError(t, err)
50+
t.Logf("got: %#v", got)
51+
52+
// check the ID to make sure we've opened the fleet config properly
53+
require.Equal(t, setID, got.agentID)
54+
55+
// no fleet config, should be standalone
56+
require.True(t, got.isStandalone)
57+
58+
// update fleet config, this time in managed mode
59+
testCfg = map[string]interface{}{
60+
"agent": map[string]interface{}{
61+
"id": setID,
62+
},
63+
"fleet": map[string]interface{}{
64+
"enabled": true,
65+
},
66+
}
67+
saveToStateStore(t, tmpPath, testCfg)
68+
69+
got, err = NewAgentInfo(ctx, false)
70+
require.NoError(t, err)
71+
t.Logf("got: %#v", got)
72+
require.False(t, got.isStandalone)
73+
74+
}
75+
76+
func saveToStateStore(t *testing.T, tmpPath string, in map[string]interface{}) {
77+
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
78+
defer cancel()
79+
80+
encPath := filepath.Join(tmpPath, "fleet.enc")
81+
store, err := storage.NewEncryptedDiskStore(ctx, encPath)
82+
require.NoError(t, err)
83+
84+
rawYml, err := yaml.Marshal(in)
85+
require.NoError(t, err)
86+
87+
reader := bytes.NewReader(rawYml)
88+
89+
err = store.Save(reader)
90+
require.NoError(t, err)
91+
}

internal/pkg/agent/application/info/agent_info.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,17 @@ type Agent interface {
4040

4141
// Unprivileged returns true when this Agent is running unprivileged.
4242
Unprivileged() bool
43+
44+
// IsStandalone returns true is the agent is running in standalone mode, i.e, without fleet
45+
IsStandalone() bool
4346
}
4447

4548
// AgentInfo is a collection of information about agent.
4649
type AgentInfo struct {
4750
agentID string
4851
logLevel string
4952
unprivileged bool
53+
isStandalone bool
5054

5155
// esHeaders will be injected into the headers field of any elasticsearch
5256
// output created by this agent (see component.toIntermediate).
@@ -60,7 +64,7 @@ type AgentInfo struct {
6064
// If agent config file does not exist it gets created.
6165
// Initiates log level to predefined value.
6266
func NewAgentInfoWithLog(ctx context.Context, level string, createAgentID bool) (*AgentInfo, error) {
63-
agentInfo, err := loadAgentInfoWithBackoff(ctx, false, level, createAgentID)
67+
agentInfo, isStandalone, err := loadAgentInfoWithBackoff(ctx, false, level, createAgentID)
6468
if err != nil {
6569
return nil, err
6670
}
@@ -74,6 +78,7 @@ func NewAgentInfoWithLog(ctx context.Context, level string, createAgentID bool)
7478
logLevel: agentInfo.LogLevel,
7579
unprivileged: !isRoot,
7680
esHeaders: agentInfo.Headers,
81+
isStandalone: isStandalone,
7782
}, nil
7883
}
7984

@@ -144,3 +149,7 @@ func (i *AgentInfo) Headers() map[string]string {
144149
func (i *AgentInfo) Unprivileged() bool {
145150
return i.unprivileged
146151
}
152+
153+
func (i *AgentInfo) IsStandalone() bool {
154+
return i.isStandalone
155+
}

pkg/component/runtime/runtime_comm.go

+10
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ func (c *runtimeComm) WriteStartUpInfo(w io.Writer, services ...client.Service)
133133
Id: c.agentInfo.AgentID(),
134134
Version: c.agentInfo.Version(),
135135
Snapshot: c.agentInfo.Snapshot(),
136+
Mode: protoAgentMode(c.agentInfo),
136137
},
137138
}
138139
infoBytes, err := protobuf.Marshal(startupInfo)
@@ -155,6 +156,7 @@ func (c *runtimeComm) CheckinExpected(
155156
Id: c.agentInfo.AgentID(),
156157
Version: c.agentInfo.Version(),
157158
Snapshot: c.agentInfo.Snapshot(),
159+
Mode: protoAgentMode(c.agentInfo),
158160
}
159161
} else {
160162
expected.AgentInfo = nil
@@ -433,3 +435,11 @@ func sendExpectedChunked(server proto.ElasticAgent_CheckinV2Server, msg *proto.C
433435
}
434436
return nil
435437
}
438+
439+
// protoAgentMode converts the agent info mode bool to the AgentManagedMode enum
440+
func protoAgentMode(agent info.Agent) proto.AgentManagedMode {
441+
if agent.IsStandalone() {
442+
return proto.AgentManagedMode_STANDALONE
443+
}
444+
return proto.AgentManagedMode_MANAGED
445+
}

pkg/component/runtime/runtime_comm_test.go

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type agentInfoMock struct {
2121
snapshot bool
2222
version string
2323
unprivileged bool
24+
isStandalone bool
2425
}
2526

2627
func (a agentInfoMock) AgentID() string {
@@ -38,6 +39,10 @@ func (a agentInfoMock) Unprivileged() bool {
3839
return a.unprivileged
3940
}
4041

42+
func (a agentInfoMock) IsStandalone() bool {
43+
return a.isStandalone
44+
}
45+
4146
func (a agentInfoMock) Headers() map[string]string { panic("implement me") }
4247
func (a agentInfoMock) LogLevel() string { panic("implement me") }
4348
func (a agentInfoMock) RawLogLevel() string { panic("implement me") }

0 commit comments

Comments
 (0)