Skip to content

Commit f0b4d23

Browse files
emosbaughsgalsaleh
andauthored
feat(api): adds target kubernetes reporting and app preflight reporting events (#2631)
* Update the installation page to show app preflight checks * f * f * f * initial timeline design * f * f * fix lint * improve unit test * cursor feedback * feat(api): update api reporting events * f * f --------- Co-authored-by: Salah Aldeen Al Saleh <[email protected]>
1 parent d5ebcea commit f0b4d23

File tree

26 files changed

+573
-193
lines changed

26 files changed

+573
-193
lines changed

api/controllers/app/install/controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ func NewInstallController(opts ...InstallControllerOption) (*InstallController,
159159
if controller.appPreflightManager == nil {
160160
controller.appPreflightManager = apppreflightmanager.NewAppPreflightManager(
161161
apppreflightmanager.WithLogger(controller.logger),
162+
apppreflightmanager.WithAppPreflightStore(controller.store.AppPreflightStore()),
162163
)
163164
}
164165

api/controllers/kubernetes/install/controller.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ func NewInstallController(opts ...InstallControllerOption) (*InstallController,
201201
if controller.infraManager == nil {
202202
infraManager, err := infra.NewInfraManager(
203203
infra.WithLogger(controller.logger),
204-
infra.WithInfraStore(controller.store.LinuxInfraStore()),
204+
infra.WithInfraStore(controller.store.KubernetesInfraStore()),
205205
infra.WithRESTClientGetter(controller.restClientGetter),
206206
infra.WithPassword(controller.password),
207207
infra.WithTLSConfig(controller.tlsConfig),
@@ -216,6 +216,8 @@ func NewInstallController(opts ...InstallControllerOption) (*InstallController,
216216
controller.infraManager = infraManager
217217
}
218218

219+
controller.registerReportingHandlers()
220+
219221
return controller, nil
220222
}
221223

api/controllers/kubernetes/install/controller_test.go

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@ import (
55
"testing"
66
"time"
77

8-
"github.com/stretchr/testify/assert"
9-
"github.com/stretchr/testify/mock"
10-
"github.com/stretchr/testify/require"
11-
128
appcontroller "github.com/replicatedhq/embedded-cluster/api/controllers/app/install"
139
appconfig "github.com/replicatedhq/embedded-cluster/api/internal/managers/app/config"
1410
"github.com/replicatedhq/embedded-cluster/api/internal/managers/kubernetes/infra"
@@ -23,6 +19,9 @@ import (
2319
"github.com/replicatedhq/embedded-cluster/pkg/release"
2420
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
2521
"github.com/replicatedhq/kotskinds/multitype"
22+
"github.com/stretchr/testify/assert"
23+
"github.com/stretchr/testify/mock"
24+
"github.com/stretchr/testify/require"
2625
)
2726

2827
func TestGetInstallationConfig(t *testing.T) {
@@ -127,7 +126,7 @@ func TestConfigureInstallation(t *testing.T) {
127126
config types.KubernetesInstallationConfig
128127
currentState statemachine.State
129128
expectedState statemachine.State
130-
setupMock func(*installation.MockInstallationManager, *kubernetesinstallation.MockInstallation, types.KubernetesInstallationConfig)
129+
setupMock func(*installation.MockInstallationManager, *kubernetesinstallation.MockInstallation, types.KubernetesInstallationConfig, *store.MockStore, *metrics.MockReporter)
131130
expectedErr bool
132131
}{
133132
{
@@ -140,7 +139,8 @@ func TestConfigureInstallation(t *testing.T) {
140139
},
141140
currentState: states.StateApplicationConfigured,
142141
expectedState: states.StateInstallationConfigured,
143-
setupMock: func(m *installation.MockInstallationManager, ki *kubernetesinstallation.MockInstallation, config types.KubernetesInstallationConfig) {
142+
143+
setupMock: func(m *installation.MockInstallationManager, ki *kubernetesinstallation.MockInstallation, config types.KubernetesInstallationConfig, st *store.MockStore, mr *metrics.MockReporter) {
144144
mock.InOrder(
145145
m.On("ConfigureInstallation", mock.Anything, ki, config).Return(nil),
146146
)
@@ -152,19 +152,49 @@ func TestConfigureInstallation(t *testing.T) {
152152
config: types.KubernetesInstallationConfig{},
153153
currentState: states.StateApplicationConfigured,
154154
expectedState: states.StateInstallationConfigurationFailed,
155-
setupMock: func(m *installation.MockInstallationManager, ki *kubernetesinstallation.MockInstallation, config types.KubernetesInstallationConfig) {
156-
m.On("ConfigureInstallation", mock.Anything, ki, config).Return(errors.New("validation error"))
155+
setupMock: func(m *installation.MockInstallationManager, ki *kubernetesinstallation.MockInstallation, config types.KubernetesInstallationConfig, st *store.MockStore, mr *metrics.MockReporter) {
156+
mock.InOrder(
157+
m.On("ConfigureInstallation", mock.Anything, ki, config).Return(errors.New("validation error")),
158+
st.KubernetesInstallationMockStore.On("GetStatus").Return(types.Status{Description: "validation error"}, nil),
159+
mr.On("ReportInstallationFailed", mock.Anything, errors.New("validation error")),
160+
)
157161
},
158162
expectedErr: true,
159163
},
160164
{
161-
name: "invalid state transition",
162-
config: types.KubernetesInstallationConfig{
163-
AdminConsolePort: 9000,
165+
name: "set config error on retry from already configured",
166+
config: types.KubernetesInstallationConfig{},
167+
currentState: states.StateInstallationConfigured,
168+
expectedState: states.StateInstallationConfigurationFailed,
169+
setupMock: func(m *installation.MockInstallationManager, ki *kubernetesinstallation.MockInstallation, config types.KubernetesInstallationConfig, st *store.MockStore, mr *metrics.MockReporter) {
170+
mock.InOrder(
171+
m.On("ConfigureInstallation", mock.Anything, ki, config).Return(errors.New("validation error")),
172+
st.KubernetesInstallationMockStore.On("GetStatus").Return(types.Status{Description: "validation error"}, nil),
173+
mr.On("ReportInstallationFailed", mock.Anything, errors.New("validation error")),
174+
)
164175
},
176+
expectedErr: true,
177+
},
178+
{
179+
name: "set config error on retry from that failed to configure",
180+
config: types.KubernetesInstallationConfig{},
181+
currentState: states.StateInstallationConfigurationFailed,
182+
expectedState: states.StateInstallationConfigurationFailed,
183+
setupMock: func(m *installation.MockInstallationManager, ki *kubernetesinstallation.MockInstallation, config types.KubernetesInstallationConfig, st *store.MockStore, mr *metrics.MockReporter) {
184+
mock.InOrder(
185+
m.On("ConfigureInstallation", mock.Anything, ki, config).Return(errors.New("validation error")),
186+
st.KubernetesInstallationMockStore.On("GetStatus").Return(types.Status{Description: "validation error"}, nil),
187+
mr.On("ReportInstallationFailed", mock.Anything, errors.New("validation error")),
188+
)
189+
},
190+
expectedErr: true,
191+
},
192+
{
193+
name: "invalid state transition",
194+
config: types.KubernetesInstallationConfig{},
165195
currentState: states.StateInfrastructureInstalling,
166196
expectedState: states.StateInfrastructureInstalling,
167-
setupMock: func(m *installation.MockInstallationManager, ki *kubernetesinstallation.MockInstallation, config types.KubernetesInstallationConfig) {
197+
setupMock: func(m *installation.MockInstallationManager, ki *kubernetesinstallation.MockInstallation, config types.KubernetesInstallationConfig, st *store.MockStore, mr *metrics.MockReporter) {
168198
},
169199
expectedErr: true,
170200
},
@@ -177,13 +207,17 @@ func TestConfigureInstallation(t *testing.T) {
177207
sm := NewStateMachine(WithCurrentState(tt.currentState))
178208

179209
mockManager := &installation.MockInstallationManager{}
210+
metricsReporter := &metrics.MockReporter{}
211+
mockStore := &store.MockStore{}
180212

181-
tt.setupMock(mockManager, mockInstallation, tt.config)
213+
tt.setupMock(mockManager, mockInstallation, tt.config, mockStore, metricsReporter)
182214

183215
controller, err := NewInstallController(
184216
WithInstallation(mockInstallation),
185217
WithStateMachine(sm),
186218
WithInstallationManager(mockManager),
219+
WithStore(mockStore),
220+
WithMetricsReporter(metricsReporter),
187221
WithReleaseData(getTestReleaseData(&kotsv1beta1.Config{})),
188222
)
189223
require.NoError(t, err)
@@ -193,8 +227,6 @@ func TestConfigureInstallation(t *testing.T) {
193227
assert.Error(t, err)
194228
} else {
195229
assert.NoError(t, err)
196-
197-
assert.NotEqual(t, tt.currentState, sm.CurrentState(), "state should have changed and should not be %s", tt.currentState)
198230
}
199231

200232
assert.Eventually(t, func() bool {
@@ -203,6 +235,10 @@ func TestConfigureInstallation(t *testing.T) {
203235
assert.False(t, sm.IsLockAcquired(), "state machine should not be locked after configuration")
204236

205237
mockManager.AssertExpectations(t)
238+
metricsReporter.AssertExpectations(t)
239+
mockStore.KubernetesInfraMockStore.AssertExpectations(t)
240+
mockStore.KubernetesInstallationMockStore.AssertExpectations(t)
241+
mockStore.AppConfigMockStore.AssertExpectations(t)
206242
mockInstallation.AssertExpectations(t)
207243
})
208244
}
@@ -301,7 +337,6 @@ func TestSetupInfra(t *testing.T) {
301337
mock.InOrder(
302338
fm.On("Install", mock.Anything, ki).Return(nil),
303339
// TODO: we are not yet reporting
304-
// mr.On("ReportInstallationSucceeded", mock.Anything),
305340
)
306341
},
307342
expectedErr: nil,
@@ -313,9 +348,8 @@ func TestSetupInfra(t *testing.T) {
313348
setupMocks: func(ki kubernetesinstallation.Installation, im *installation.MockInstallationManager, fm *infra.MockInfraManager, mr *metrics.MockReporter, st *store.MockStore, am *appconfig.MockAppConfigManager) {
314349
mock.InOrder(
315350
fm.On("Install", mock.Anything, ki).Return(errors.New("install error")),
316-
st.LinuxInfraMockStore.On("GetStatus").Return(types.Status{Description: "install error"}, nil),
317-
// TODO: we are not yet reporting
318-
// mr.On("ReportInstallationFailed", mock.Anything, errors.New("install error")),
351+
st.KubernetesInfraMockStore.On("GetStatus").Return(types.Status{Description: "install error"}, nil),
352+
mr.On("ReportInstallationFailed", mock.Anything, errors.New("install error")),
319353
)
320354
},
321355
expectedErr: nil,
@@ -327,7 +361,7 @@ func TestSetupInfra(t *testing.T) {
327361
setupMocks: func(ki kubernetesinstallation.Installation, im *installation.MockInstallationManager, fm *infra.MockInfraManager, mr *metrics.MockReporter, st *store.MockStore, am *appconfig.MockAppConfigManager) {
328362
mock.InOrder(
329363
fm.On("Install", mock.Anything, ki).Return(errors.New("install error")),
330-
st.LinuxInfraMockStore.On("GetStatus").Return(nil, assert.AnError),
364+
st.KubernetesInfraMockStore.On("GetStatus").Return(nil, assert.AnError),
331365
)
332366
},
333367
expectedErr: nil,
@@ -339,9 +373,8 @@ func TestSetupInfra(t *testing.T) {
339373
setupMocks: func(ki kubernetesinstallation.Installation, im *installation.MockInstallationManager, fm *infra.MockInfraManager, mr *metrics.MockReporter, st *store.MockStore, am *appconfig.MockAppConfigManager) {
340374
mock.InOrder(
341375
fm.On("Install", mock.Anything, ki).Panic("this is a panic"),
342-
st.LinuxInfraMockStore.On("GetStatus").Return(types.Status{Description: "this is a panic"}, nil),
343-
// TODO: we are not yet reporting
344-
// mr.On("ReportInstallationFailed", mock.Anything, errors.New("this is a panic")),
376+
st.KubernetesInfraMockStore.On("GetStatus").Return(types.Status{Description: "this is a panic"}, nil),
377+
mr.On("ReportInstallationFailed", mock.Anything, errors.New("this is a panic")),
345378
)
346379
},
347380
expectedErr: nil,
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package install
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
8+
"github.com/replicatedhq/embedded-cluster/api/internal/statemachine"
9+
states "github.com/replicatedhq/embedded-cluster/api/internal/states/install"
10+
"github.com/replicatedhq/embedded-cluster/api/types"
11+
)
12+
13+
func (c *InstallController) registerReportingHandlers() {
14+
c.stateMachine.RegisterEventHandler(states.StateHostConfigurationFailed, c.reportInstallFailed)
15+
c.stateMachine.RegisterEventHandler(states.StateInstallationConfigurationFailed, c.reportInstallFailed)
16+
c.stateMachine.RegisterEventHandler(states.StateInfrastructureInstallFailed, c.reportInstallFailed)
17+
c.stateMachine.RegisterEventHandler(states.StateAppInstallFailed, c.reportInstallFailed)
18+
c.stateMachine.RegisterEventHandler(states.StateSucceeded, c.reportInstallSucceeded)
19+
20+
// report preflight failures and bypassed
21+
c.stateMachine.RegisterEventHandler(states.StateAppPreflightsFailed, c.reportAppPreflightsFailed)
22+
c.stateMachine.RegisterEventHandler(states.StateAppPreflightsFailedBypassed, c.reportAppPreflightsBypassed)
23+
c.stateMachine.RegisterEventHandler(states.StateAppPreflightsSucceeded, c.reportAppPreflightsSucceeded)
24+
}
25+
26+
func (c *InstallController) reportInstallSucceeded(ctx context.Context, _, _ statemachine.State) {
27+
c.logger.Info("Reporting metrics event install succeeded")
28+
c.metricsReporter.ReportInstallationSucceeded(ctx)
29+
}
30+
31+
func (c *InstallController) reportInstallFailed(ctx context.Context, _, toState statemachine.State) {
32+
var status types.Status
33+
var err error
34+
35+
switch toState {
36+
case states.StateInstallationConfigurationFailed:
37+
status, err = c.store.KubernetesInstallationStore().GetStatus()
38+
if err != nil {
39+
err = fmt.Errorf("get status from installation store: %w", err)
40+
}
41+
case states.StateInfrastructureInstallFailed:
42+
status, err = c.store.KubernetesInfraStore().GetStatus()
43+
if err != nil {
44+
err = fmt.Errorf("get status from infra store: %w", err)
45+
}
46+
case states.StateAppInstallFailed:
47+
status, err = c.store.AppInstallStore().GetStatus()
48+
if err != nil {
49+
err = fmt.Errorf("get status from app install store: %w", err)
50+
}
51+
}
52+
if err != nil {
53+
c.logger.WithError(err).Error("failed to report failed install")
54+
return
55+
}
56+
57+
c.logger.Info("Reporting metrics event install failed")
58+
c.metricsReporter.ReportInstallationFailed(ctx, errors.New(status.Description))
59+
}
60+
61+
func (c *InstallController) reportAppPreflightsFailed(ctx context.Context, _, _ statemachine.State) {
62+
output, err := c.store.AppPreflightStore().GetOutput()
63+
if err != nil {
64+
err = fmt.Errorf("get output from app preflight store: %w", err)
65+
c.logger.WithError(err).Error("failed to report app preflights failed")
66+
return
67+
}
68+
c.logger.Info("Reporting metrics event app preflights failed")
69+
c.metricsReporter.ReportAppPreflightsFailed(ctx, output)
70+
}
71+
72+
func (c *InstallController) reportAppPreflightsBypassed(ctx context.Context, _, _ statemachine.State) {
73+
output, err := c.store.AppPreflightStore().GetOutput()
74+
if err != nil {
75+
err = fmt.Errorf("get output from app preflight store: %w", err)
76+
c.logger.WithError(err).Error("failed to report app preflights bypassed")
77+
return
78+
}
79+
c.logger.Info("Reporting metrics event app preflights bypassed")
80+
c.metricsReporter.ReportAppPreflightsBypassed(ctx, output)
81+
}
82+
83+
func (c *InstallController) reportAppPreflightsSucceeded(ctx context.Context, _, _ statemachine.State) {
84+
c.logger.Info("Reporting metrics event app preflights succeeded")
85+
c.metricsReporter.ReportAppPreflightsSucceeded(ctx)
86+
}

api/controllers/kubernetes/install/statemachine_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ func TestStateMachineTransitions(t *testing.T) {
143143
startState: states.StateAppInstallFailed,
144144
validTransitions: []statemachine.State{},
145145
},
146+
{
147+
name: `State "Succeeded" can not transition to any other state`,
148+
startState: states.StateSucceeded,
149+
validTransitions: []statemachine.State{},
150+
},
146151
}
147152

148153
for _, tt := range tests {

api/controllers/linux/install/controller_test.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@ import (
55
"testing"
66
"time"
77

8-
"github.com/stretchr/testify/assert"
9-
"github.com/stretchr/testify/mock"
10-
"github.com/stretchr/testify/require"
11-
128
appcontroller "github.com/replicatedhq/embedded-cluster/api/controllers/app/install"
139
appconfig "github.com/replicatedhq/embedded-cluster/api/internal/managers/app/config"
1410
apppreflightmanager "github.com/replicatedhq/embedded-cluster/api/internal/managers/app/preflight"
@@ -27,6 +23,9 @@ import (
2723
kotsv1beta1 "github.com/replicatedhq/kotskinds/apis/kots/v1beta1"
2824
"github.com/replicatedhq/kotskinds/multitype"
2925
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
26+
"github.com/stretchr/testify/assert"
27+
"github.com/stretchr/testify/mock"
28+
"github.com/stretchr/testify/require"
3029
)
3130

3231
var failedPreflightOutput = &types.PreflightsOutput{
@@ -266,8 +265,8 @@ func TestConfigureInstallation(t *testing.T) {
266265
st.LinuxInstallationMockStore.On("SetStatus", mock.MatchedBy(func(status types.Status) bool {
267266
return status.State == types.StateFailed && status.Description == "write: set config error"
268267
})).Return(nil),
269-
st.LinuxInstallationMockStore.On("GetStatus").Return(types.Status{Description: "validate: validation error"}, nil),
270-
mr.On("ReportInstallationFailed", mock.Anything, errors.New("validate: validation error")),
268+
st.LinuxInstallationMockStore.On("GetStatus").Return(types.Status{Description: "write: set config error"}, nil),
269+
mr.On("ReportInstallationFailed", mock.Anything, errors.New("write: set config error")),
271270
)
272271
},
273272
expectedErr: true,
@@ -598,7 +597,7 @@ func TestRunHostPreflights(t *testing.T) {
598597
})).Return(nil),
599598
pm.On("GetHostPreflightOutput", mock.Anything).Return(failedPreflightOutput, nil),
600599
st.LinuxPreflightMockStore.On("GetOutput").Return(failedPreflightOutput, nil),
601-
mr.On("ReportPreflightsFailed", mock.Anything, failedPreflightOutput).Return(nil),
600+
mr.On("ReportHostPreflightsFailed", mock.Anything, failedPreflightOutput).Return(nil),
602601
)
603602
},
604603
expectedErr: false,
@@ -631,7 +630,7 @@ func TestRunHostPreflights(t *testing.T) {
631630
})).Return(nil),
632631
pm.On("GetHostPreflightOutput", mock.Anything).Return(failedPreflightOutput, nil),
633632
st.LinuxPreflightMockStore.On("GetOutput").Return(failedPreflightOutput, nil),
634-
mr.On("ReportPreflightsFailed", mock.Anything, failedPreflightOutput).Return(nil),
633+
mr.On("ReportHostPreflightsFailed", mock.Anything, failedPreflightOutput).Return(nil),
635634
)
636635
},
637636
expectedErr: false,
@@ -648,7 +647,7 @@ func TestRunHostPreflights(t *testing.T) {
648647
})).Return(nil),
649648
pm.On("GetHostPreflightOutput", mock.Anything).Return(failedPreflightOutput, nil),
650649
st.LinuxPreflightMockStore.On("GetOutput").Return(failedPreflightOutput, nil),
651-
mr.On("ReportPreflightsFailed", mock.Anything, failedPreflightOutput).Return(nil),
650+
mr.On("ReportHostPreflightsFailed", mock.Anything, failedPreflightOutput).Return(nil),
652651
)
653652
},
654653
expectedErr: false,
@@ -665,7 +664,7 @@ func TestRunHostPreflights(t *testing.T) {
665664
})).Return(nil),
666665
pm.On("GetHostPreflightOutput", mock.Anything).Return(failedPreflightOutput, nil),
667666
st.LinuxPreflightMockStore.On("GetOutput").Return(failedPreflightOutput, nil),
668-
mr.On("ReportPreflightsFailed", mock.Anything, failedPreflightOutput).Return(nil),
667+
mr.On("ReportHostPreflightsFailed", mock.Anything, failedPreflightOutput).Return(nil),
669668
)
670669
},
671670
expectedErr: false,
@@ -1085,7 +1084,7 @@ func TestSetupInfra(t *testing.T) {
10851084
setupMocks: func(rc runtimeconfig.RuntimeConfig, pm *preflight.MockHostPreflightManager, im *installation.MockInstallationManager, fm *infra.MockInfraManager, am *appconfig.MockAppConfigManager, mr *metrics.MockReporter, st *store.MockStore) {
10861085
mock.InOrder(
10871086
st.LinuxPreflightMockStore.On("GetOutput").Return(failedPreflightOutput, nil),
1088-
mr.On("ReportPreflightsBypassed", mock.Anything, failedPreflightOutput),
1087+
mr.On("ReportHostPreflightsBypassed", mock.Anything, failedPreflightOutput),
10891088
fm.On("Install", mock.Anything, rc).Return(nil),
10901089
)
10911090
},

0 commit comments

Comments
 (0)