diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index edcf2af70c757..e3735c1cb3a3c 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -1619,6 +1619,7 @@ func (h *Handler) ping(w http.ResponseWriter, r *http.Request, p httprouter.Para } group := r.URL.Query().Get(webclient.AgentUpdateGroupParameter) + updaterID := r.URL.Query().Get(webclient.AgentUpdateIDParameter) return webclient.PingResponse{ Auth: authSettings, @@ -1627,7 +1628,7 @@ func (h *Handler) ping(w http.ResponseWriter, r *http.Request, p httprouter.Para MinClientVersion: teleport.MinClientSemVer().String(), ClusterName: h.auth.clusterName, AutomaticUpgrades: pr.ServerFeatures.GetAutomaticUpgrades(), - AutoUpdate: h.automaticUpdateSettings184(r.Context(), group, "" /* updater UUID */), + AutoUpdate: h.automaticUpdateSettings184(r.Context(), group, updaterID), Edition: modules.GetModules().BuildType(), FIPS: modules.IsBoringBinary(), }, nil @@ -1666,6 +1667,14 @@ func (h *Handler) find(w http.ResponseWriter, r *http.Request, p httprouter.Para if err != nil { return nil, trace.Wrap(err) } + + // Now we modulate the autoupdate answer on a per-request basis. + // We don't want to cache one answer per updater UUID, so we take the + // cached result and just override what we must. + updaterID := r.URL.Query().Get(webclient.AgentUpdateIDParameter) + if updaterID != "" { + resp.AutoUpdate = h.automaticUpdateSettings184(r.Context(), group, updaterID) + } return resp, nil } diff --git a/lib/web/apiserver_ping_test.go b/lib/web/apiserver_ping_test.go index 5103964c09157..c62fe6a6959f9 100644 --- a/lib/web/apiserver_ping_test.go +++ b/lib/web/apiserver_ping_test.go @@ -25,6 +25,7 @@ import ( "net/url" "testing" + "github.com/google/uuid" "github.com/gravitational/roundtrip" "github.com/gravitational/trace" "github.com/stretchr/testify/assert" @@ -34,6 +35,7 @@ import ( "github.com/gravitational/teleport/api/client/webclient" "github.com/gravitational/teleport/api/constants" autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/autoupdate" "github.com/gravitational/teleport/lib/client" @@ -283,22 +285,22 @@ func TestPing_minimalAPI(t *testing.T) { // TestPing_autoUpdateResources tests that find endpoint return correct data related to auto updates. func TestPing_autoUpdateResources(t *testing.T) { - env := newWebPack(t, 1, func(cfg *proxyConfig) { - cfg.minimalHandler = true - }) + env := newWebPack(t, 1) proxy := env.proxies[0] ctx := t.Context() - req, err := http.NewRequest(http.MethodGet, proxy.newClient(t).Endpoint("webapi", "find"), nil) - require.NoError(t, err) - req.Host = proxy.handler.handler.cfg.ProxyPublicAddrs[0].Host() + testGroup := "test-group" + testUpdaterID := uuid.NewString() + // Note: we are not using webclient.Ping() here because we don't have a valid certificate for 127.0.0.1 and + // The webclient doesn't support being passed custom transport. + clt := proxy.newClient(t) tests := []struct { name string config *autoupdatev1pb.AutoUpdateConfigSpec version *autoupdatev1pb.AutoUpdateVersionSpec - rollout *autoupdatev1pb.AutoUpdateAgentRolloutSpec + rollout *autoupdatev1pb.AutoUpdateAgentRollout cleanup bool expected webclient.AutoUpdateSettings }{ @@ -330,12 +332,17 @@ func TestPing_autoUpdateResources(t *testing.T) { }, { name: "enable agent auto update, immediate schedule", - rollout: &autoupdatev1pb.AutoUpdateAgentRolloutSpec{ - AutoupdateMode: autoupdate.AgentsUpdateModeEnabled, - Strategy: autoupdate.AgentsStrategyHaltOnError, - Schedule: autoupdate.AgentsScheduleImmediate, - StartVersion: "1.2.3", - TargetVersion: "1.2.4", + rollout: &autoupdatev1pb.AutoUpdateAgentRollout{ + Metadata: &headerv1.Metadata{ + Name: types.MetaNameAutoUpdateAgentRollout, + }, + Spec: &autoupdatev1pb.AutoUpdateAgentRolloutSpec{ + AutoupdateMode: autoupdate.AgentsUpdateModeEnabled, + Strategy: autoupdate.AgentsStrategyHaltOnError, + Schedule: autoupdate.AgentsScheduleImmediate, + StartVersion: "1.2.3", + TargetVersion: "1.2.4", + }, }, expected: webclient.AutoUpdateSettings{ ToolsVersion: api.Version, @@ -348,12 +355,17 @@ func TestPing_autoUpdateResources(t *testing.T) { }, { name: "agent rollout present but AU mode is disabled", - rollout: &autoupdatev1pb.AutoUpdateAgentRolloutSpec{ - AutoupdateMode: autoupdate.AgentsUpdateModeDisabled, - Strategy: autoupdate.AgentsStrategyHaltOnError, - Schedule: autoupdate.AgentsScheduleImmediate, - StartVersion: "1.2.3", - TargetVersion: "1.2.4", + rollout: &autoupdatev1pb.AutoUpdateAgentRollout{ + Metadata: &headerv1.Metadata{ + Name: types.MetaNameAutoUpdateAgentRollout, + }, + Spec: &autoupdatev1pb.AutoUpdateAgentRolloutSpec{ + AutoupdateMode: autoupdate.AgentsUpdateModeDisabled, + Strategy: autoupdate.AgentsStrategyHaltOnError, + Schedule: autoupdate.AgentsScheduleImmediate, + StartVersion: "1.2.3", + TargetVersion: "1.2.4", + }, }, expected: webclient.AutoUpdateSettings{ ToolsVersion: api.Version, @@ -432,6 +444,171 @@ func TestPing_autoUpdateResources(t *testing.T) { AgentAutoUpdate: false, AgentVersion: api.Version, }, + cleanup: true, + }, + { + name: "group must be updated", + rollout: &autoupdatev1pb.AutoUpdateAgentRollout{ + Metadata: &headerv1.Metadata{ + Name: types.MetaNameAutoUpdateAgentRollout, + }, + Spec: &autoupdatev1pb.AutoUpdateAgentRolloutSpec{ + AutoupdateMode: autoupdate.AgentsUpdateModeEnabled, + Strategy: autoupdate.AgentsStrategyHaltOnError, + Schedule: autoupdate.AgentsScheduleRegular, + StartVersion: "1.2.3", + TargetVersion: "1.2.4", + }, + Status: &autoupdatev1pb.AutoUpdateAgentRolloutStatus{ + Groups: []*autoupdatev1pb.AutoUpdateAgentRolloutStatusGroup{ + { + Name: testGroup, + State: autoupdatev1pb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE, + ConfigDays: []string{"*"}, + }, + { + Name: "unstarted", + State: autoupdatev1pb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_UNSTARTED, + ConfigDays: []string{"*"}, + }, + }, + }, + }, + expected: webclient.AutoUpdateSettings{ + ToolsVersion: api.Version, + ToolsAutoUpdate: false, + AgentVersion: "1.2.4", + AgentAutoUpdate: true, + AgentUpdateJitterSeconds: DefaultAgentUpdateJitterSeconds, + }, + cleanup: true, + }, + { + name: "group must not be updated", + rollout: &autoupdatev1pb.AutoUpdateAgentRollout{ + Metadata: &headerv1.Metadata{ + Name: types.MetaNameAutoUpdateAgentRollout, + }, + Spec: &autoupdatev1pb.AutoUpdateAgentRolloutSpec{ + AutoupdateMode: autoupdate.AgentsUpdateModeEnabled, + Strategy: autoupdate.AgentsStrategyHaltOnError, + Schedule: autoupdate.AgentsScheduleRegular, + StartVersion: "1.2.3", + TargetVersion: "1.2.4", + }, + Status: &autoupdatev1pb.AutoUpdateAgentRolloutStatus{ + Groups: []*autoupdatev1pb.AutoUpdateAgentRolloutStatusGroup{ + { + Name: "done", + State: autoupdatev1pb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE, + ConfigDays: []string{"*"}, + }, + { + Name: testGroup, + State: autoupdatev1pb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_UNSTARTED, + ConfigDays: []string{"*"}, + }, + }, + }, + }, + expected: webclient.AutoUpdateSettings{ + ToolsVersion: api.Version, + ToolsAutoUpdate: false, + AgentVersion: "1.2.3", + AgentAutoUpdate: false, + AgentUpdateJitterSeconds: DefaultAgentUpdateJitterSeconds, + }, + cleanup: true, + }, + { + name: "canary must be updated", + rollout: &autoupdatev1pb.AutoUpdateAgentRollout{ + Metadata: &headerv1.Metadata{ + Name: types.MetaNameAutoUpdateAgentRollout, + }, + Spec: &autoupdatev1pb.AutoUpdateAgentRolloutSpec{ + AutoupdateMode: autoupdate.AgentsUpdateModeEnabled, + Strategy: autoupdate.AgentsStrategyHaltOnError, + Schedule: autoupdate.AgentsScheduleRegular, + StartVersion: "1.2.3", + TargetVersion: "1.2.4", + }, + Status: &autoupdatev1pb.AutoUpdateAgentRolloutStatus{ + Groups: []*autoupdatev1pb.AutoUpdateAgentRolloutStatusGroup{ + { + Name: testGroup, + State: autoupdatev1pb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_CANARY, + ConfigDays: []string{"*"}, + Canaries: []*autoupdatev1pb.Canary{ + { + UpdaterId: testUpdaterID, + HostId: uuid.NewString(), + Hostname: "test-host", + Success: false, + }, + }, + }, + { + Name: "unstarted", + State: autoupdatev1pb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_UNSTARTED, + ConfigDays: []string{"*"}, + }, + }, + }, + }, + expected: webclient.AutoUpdateSettings{ + ToolsVersion: api.Version, + ToolsAutoUpdate: false, + AgentVersion: "1.2.4", + AgentAutoUpdate: true, + AgentUpdateJitterSeconds: DefaultAgentUpdateJitterSeconds, + }, + cleanup: true, + }, + { + name: "canary must not be updated", + rollout: &autoupdatev1pb.AutoUpdateAgentRollout{ + Metadata: &headerv1.Metadata{ + Name: types.MetaNameAutoUpdateAgentRollout, + }, + Spec: &autoupdatev1pb.AutoUpdateAgentRolloutSpec{ + AutoupdateMode: autoupdate.AgentsUpdateModeEnabled, + Strategy: autoupdate.AgentsStrategyHaltOnError, + Schedule: autoupdate.AgentsScheduleRegular, + StartVersion: "1.2.3", + TargetVersion: "1.2.4", + }, + Status: &autoupdatev1pb.AutoUpdateAgentRolloutStatus{ + Groups: []*autoupdatev1pb.AutoUpdateAgentRolloutStatusGroup{ + { + Name: testGroup, + State: autoupdatev1pb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_CANARY, + Canaries: []*autoupdatev1pb.Canary{ + { + UpdaterId: uuid.NewString(), + HostId: uuid.NewString(), + Hostname: "test-host", + Success: false, + }, + }, + ConfigDays: []string{"*"}, + }, + { + Name: "unstarted", + State: autoupdatev1pb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_UNSTARTED, + ConfigDays: []string{"*"}, + }, + }, + }, + }, + expected: webclient.AutoUpdateSettings{ + ToolsVersion: api.Version, + ToolsAutoUpdate: false, + AgentVersion: "1.2.3", + AgentAutoUpdate: false, + AgentUpdateJitterSeconds: DefaultAgentUpdateJitterSeconds, + }, + cleanup: true, }, } for _, tc := range tests { @@ -449,9 +626,7 @@ func TestPing_autoUpdateResources(t *testing.T) { require.NoError(t, err) } if tc.rollout != nil { - rollout, err := autoupdate.NewAutoUpdateAgentRollout(tc.rollout) - require.NoError(t, err) - _, err = env.server.Auth().UpsertAutoUpdateAgentRollout(ctx, rollout) + _, err := env.server.Auth().UpsertAutoUpdateAgentRollout(ctx, tc.rollout) require.NoError(t, err) } @@ -460,12 +635,14 @@ func TestPing_autoUpdateResources(t *testing.T) { proxy.clock.Advance(2 * findEndpointCacheTTL) } - resp, err := client.NewInsecureWebClient().Do(req) + resp, err := clt.Get(ctx, clt.Endpoint("webapi", "ping"), url.Values{ + webclient.AgentUpdateIDParameter: []string{testUpdaterID}, + webclient.AgentUpdateGroupParameter: []string{testGroup}, + }) require.NoError(t, err) pr := &webclient.PingResponse{} - require.NoError(t, json.NewDecoder(resp.Body).Decode(pr)) - require.NoError(t, resp.Body.Close()) + require.NoError(t, json.NewDecoder(resp.Reader()).Decode(pr)) assert.Equal(t, tc.expected, pr.AutoUpdate) diff --git a/lib/web/autoupdate_common.go b/lib/web/autoupdate_common.go index c469aefb52034..0d55a62842f7c 100644 --- a/lib/web/autoupdate_common.go +++ b/lib/web/autoupdate_common.go @@ -125,11 +125,28 @@ func getVersionFromRollout( case autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE, autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_DONE: return version.EnsureSemver(rollout.GetSpec().GetTargetVersion()) + case autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_CANARY: + if updaterIsCanary(group, updaterUUID) { + return version.EnsureSemver(rollout.GetSpec().GetTargetVersion()) + } + return version.EnsureSemver(rollout.GetSpec().GetStartVersion()) default: return nil, trace.NotImplemented("unsupported group state %q", group.GetState()) } } +func updaterIsCanary(group *autoupdatepb.AutoUpdateAgentRolloutStatusGroup, updaterUUID string) bool { + if updaterUUID == "" { + return false + } + for _, canary := range group.GetCanaries() { + if canary.UpdaterId == updaterUUID { + return true + } + } + return false +} + // getTriggerFromRollout returns the version we should serve to the agent based // on the RFD184 agent rollout, the agent group name, and its UUID. // This logic is pretty complex and described in RFD 184. @@ -166,6 +183,8 @@ func getTriggerFromRollout(rollout *autoupdatepb.AutoUpdateAgentRollout, groupNa return true, nil case autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_DONE: return rollout.GetSpec().GetStrategy() == autoupdate.AgentsStrategyHaltOnError, nil + case autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_CANARY: + return updaterIsCanary(group, updaterUUID), nil default: return false, trace.NotImplemented("Unsupported group state %q", group.GetState()) } diff --git a/lib/web/autoupdate_common_test.go b/lib/web/autoupdate_common_test.go index b84070d115f1d..b65d0ba36a3ce 100644 --- a/lib/web/autoupdate_common_test.go +++ b/lib/web/autoupdate_common_test.go @@ -23,11 +23,13 @@ import ( "fmt" "net/http" "net/http/httptest" + "strconv" "strings" "testing" "time" "github.com/coreos/go-semver/semver" + "github.com/google/uuid" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" "github.com/stretchr/testify/assert" @@ -365,6 +367,7 @@ func TestGetVersionFromRollout(t *testing.T) { autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_DONE: testVersionHigh, autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE: testVersionHigh, autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ROLLEDBACK: testVersionHigh, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_CANARY: testVersionHigh, } activeDoneOnly := map[autoupdatepb.AutoUpdateAgentGroupState]string{ @@ -372,6 +375,15 @@ func TestGetVersionFromRollout(t *testing.T) { autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_DONE: testVersionHigh, autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE: testVersionHigh, autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ROLLEDBACK: testVersionLow, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_CANARY: testVersionLow, + } + + activeCanaryDone := map[autoupdatepb.AutoUpdateAgentGroupState]string{ + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_UNSTARTED: testVersionLow, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_DONE: testVersionHigh, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE: testVersionHigh, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ROLLEDBACK: testVersionLow, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_CANARY: testVersionHigh, } tests := map[string]map[string]map[autoupdatepb.AutoUpdateAgentGroupState]string{ @@ -394,7 +406,6 @@ func TestGetVersionFromRollout(t *testing.T) { t.Run(fmt.Sprintf("%s/%s/%s", mode, schedule, state), func(t *testing.T) { expectedSemVersion, err := version.EnsureSemver(expectedVersion) require.NoError(t, err) - rollout := &autoupdatepb.AutoUpdateAgentRollout{ Spec: &autoupdatepb.AutoUpdateAgentRolloutSpec{ StartVersion: testVersionLow, @@ -420,6 +431,63 @@ func TestGetVersionFromRollout(t *testing.T) { } } } + + canaryTestCases := map[bool]map[autoupdatepb.AutoUpdateAgentGroupState]string{ + true: activeCanaryDone, + false: activeDoneOnly, + } + + for canaryMatching, statesCases := range canaryTestCases { + const ( + schedule = autoupdate.AgentsScheduleRegular + mode = autoupdate.AgentsUpdateModeEnabled + ) + + for state, expectedVersion := range statesCases { + t.Run(fmt.Sprintf("canary(%s)/%s", strconv.FormatBool(canaryMatching), state), func(t *testing.T) { + expectedSemVersion, err := version.EnsureSemver(expectedVersion) + require.NoError(t, err) + + rollout := &autoupdatepb.AutoUpdateAgentRollout{ + Spec: &autoupdatepb.AutoUpdateAgentRolloutSpec{ + StartVersion: testVersionLow, + TargetVersion: testVersionHigh, + Schedule: schedule, + AutoupdateMode: mode, + // Strategy does not affect which version are served + Strategy: autoupdate.AgentsStrategyTimeBased, + }, + Status: &autoupdatepb.AutoUpdateAgentRolloutStatus{ + Groups: []*autoupdatepb.AutoUpdateAgentRolloutStatusGroup{ + { + Name: groupName, + State: state, + Canaries: []*autoupdatepb.Canary{ + { + UpdaterId: uuid.NewString(), + HostId: uuid.NewString(), + Hostname: "test-host", + Success: false, + }, + }, + }, + }, + }, + } + var updaterID string + if canaryMatching { + updaterID = rollout.GetStatus().GetGroups()[0].GetCanaries()[0].GetUpdaterId() + } else { + updaterID = uuid.NewString() + } + version, err := getVersionFromRollout(rollout, groupName, updaterID) + require.NoError(t, err) + require.Equal(t, expectedSemVersion, version) + }) + + } + + } } func TestGetTriggerFromRollout(t *testing.T) { @@ -433,12 +501,14 @@ func TestGetTriggerFromRollout(t *testing.T) { autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_DONE: false, autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE: false, autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ROLLEDBACK: false, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_CANARY: false, } alwaysUpdate := map[autoupdatepb.AutoUpdateAgentGroupState]bool{ autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_UNSTARTED: true, autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_DONE: true, autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE: true, autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ROLLEDBACK: true, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_CANARY: true, } tests := map[string]map[string]map[string]map[autoupdatepb.AutoUpdateAgentGroupState]bool{ @@ -470,6 +540,7 @@ func TestGetTriggerFromRollout(t *testing.T) { autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_DONE: false, autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE: true, autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ROLLEDBACK: true, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_CANARY: false, }, }, autoupdate.AgentsStrategyHaltOnError: { @@ -479,6 +550,7 @@ func TestGetTriggerFromRollout(t *testing.T) { autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_DONE: true, autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE: true, autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ROLLEDBACK: true, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_CANARY: false, }, }, }, @@ -513,6 +585,92 @@ func TestGetTriggerFromRollout(t *testing.T) { } } } + + canaryTestCases := map[bool]map[string]map[autoupdatepb.AutoUpdateAgentGroupState]bool{ + true: { + autoupdate.AgentsStrategyTimeBased: { + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_UNSTARTED: false, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_DONE: false, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE: true, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ROLLEDBACK: true, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_CANARY: true, + }, + autoupdate.AgentsStrategyHaltOnError: { + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_UNSTARTED: false, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_DONE: true, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE: true, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ROLLEDBACK: true, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_CANARY: true, + }, + }, + false: { + autoupdate.AgentsStrategyTimeBased: { + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_UNSTARTED: false, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_DONE: false, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE: true, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ROLLEDBACK: true, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_CANARY: false, + }, + autoupdate.AgentsStrategyHaltOnError: { + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_UNSTARTED: false, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_DONE: true, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ACTIVE: true, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_ROLLEDBACK: true, + autoupdatepb.AutoUpdateAgentGroupState_AUTO_UPDATE_AGENT_GROUP_STATE_CANARY: false, + }, + }, + } + + for canaryMatching, strategyCases := range canaryTestCases { + const ( + schedule = autoupdate.AgentsScheduleRegular + mode = autoupdate.AgentsUpdateModeEnabled + ) + + for strategy, statesCases := range strategyCases { + for state, expectedTrigger := range statesCases { + t.Run(fmt.Sprintf("canary(%s)/%s/%s", strconv.FormatBool(canaryMatching), strategy, state), func(t *testing.T) { + rollout := &autoupdatepb.AutoUpdateAgentRollout{ + Spec: &autoupdatepb.AutoUpdateAgentRolloutSpec{ + StartVersion: testVersionLow, + TargetVersion: testVersionHigh, + Schedule: schedule, + AutoupdateMode: mode, + Strategy: strategy, + }, + Status: &autoupdatepb.AutoUpdateAgentRolloutStatus{ + Groups: []*autoupdatepb.AutoUpdateAgentRolloutStatusGroup{ + { + Name: groupName, + State: state, + Canaries: []*autoupdatepb.Canary{ + { + UpdaterId: uuid.NewString(), + HostId: uuid.NewString(), + Hostname: "test-host", + Success: false, + }, + }, + }, + }, + }, + } + var updaterID string + if canaryMatching { + updaterID = rollout.GetStatus().GetGroups()[0].GetCanaries()[0].GetUpdaterId() + } else { + updaterID = uuid.NewString() + } + trigger, err := getTriggerFromRollout(rollout, groupName, updaterID) + require.NoError(t, err) + require.Equal(t, expectedTrigger, trigger) + }) + + } + + } + + } } func TestGetGroup(t *testing.T) {