diff --git a/api/controller/crossmodelrelations/crossmodelrelations.go b/api/controller/crossmodelrelations/crossmodelrelations.go index 6142e8d8528..efa65dd0658 100644 --- a/api/controller/crossmodelrelations/crossmodelrelations.go +++ b/api/controller/crossmodelrelations/crossmodelrelations.go @@ -14,7 +14,6 @@ import ( "github.com/juju/juju/api/base" apiwatcher "github.com/juju/juju/api/watcher" - apiservererrors "github.com/juju/juju/apiserver/errors" "github.com/juju/juju/core/watcher" "github.com/juju/juju/rpc/params" ) @@ -498,7 +497,7 @@ func (c *Client) WatchConsumedSecretsChanges(ctx context.Context, applicationTok // Reset the results struct before each api call. results = params.SecretRevisionWatchResults{} if err := c.facade.FacadeCall(context.TODO(), "WatchConsumedSecretsChanges", args, &results); err != nil { - return errors.Trace(err) + return params.TranslateWellKnownError(err) } if len(results.Results) != 1 { return errors.Errorf("expected 1 result, got %d", len(results.Results)) @@ -528,7 +527,7 @@ func (c *Client) WatchConsumedSecretsChanges(ctx context.Context, applicationTok result = results.Results[0] } if result.Error != nil { - return nil, apiservererrors.RestoreError(result.Error) + return nil, params.TranslateWellKnownError(result.Error) } w := apiwatcher.NewSecretsRevisionWatcher(c.facade.RawAPICaller(), result) diff --git a/api/controller/crossmodelrelations/crossmodelrelations_test.go b/api/controller/crossmodelrelations/crossmodelrelations_test.go index ed6c646a916..9de8b7f393b 100644 --- a/api/controller/crossmodelrelations/crossmodelrelations_test.go +++ b/api/controller/crossmodelrelations/crossmodelrelations_test.go @@ -722,9 +722,9 @@ func (s *CrossModelRelationsSuite) TestWatchConsumedSecretsChanges(c *gc.C) { mac, err := jujutesting.NewMacaroon("id") c.Assert(err, jc.ErrorIsNil) var callCount int - apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + apiCaller := testing.BestVersionCaller{APICallerFunc: testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { c.Check(objType, gc.Equals, "CrossModelRelations") - c.Check(version, gc.Equals, 0) + c.Check(version, gc.Equals, 3) c.Check(id, gc.Equals, "") c.Check(arg, jc.DeepEquals, params.WatchRemoteSecretChangesArgs{Args: []params.WatchRemoteSecretChangesArg{{ ApplicationToken: appToken, RelationToken: relToken, Macaroons: macaroon.Slice{mac}, @@ -739,7 +739,7 @@ func (s *CrossModelRelationsSuite) TestWatchConsumedSecretsChanges(c *gc.C) { } callCount++ return nil - }) + }), BestVersion: 3} client := crossmodelrelations.NewClientWithCache(apiCaller, s.cache) _, err = client.WatchConsumedSecretsChanges(context.Background(), appToken, relToken, mac) c.Check(err, gc.ErrorMatches, "FAIL") @@ -758,7 +758,7 @@ func (s *CrossModelRelationsSuite) TestWatchConsumedSecretsChangesDischargeRequi callCount int dischargeMac macaroon.Slice ) - apiCaller := testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { + apiCaller := testing.BestVersionCaller{APICallerFunc: testing.APICallerFunc(func(objType string, version int, id, request string, arg, result interface{}) error { var resultErr *params.Error switch callCount { case 2, 3: //Watcher Next, Stop @@ -781,7 +781,7 @@ func (s *CrossModelRelationsSuite) TestWatchConsumedSecretsChangesDischargeRequi s.fillResponse(c, result, resp) callCount++ return nil - }) + }), BestVersion: 3} acquirer := &mockDischargeAcquirer{} callerWithBakery := testing.APICallerWithBakery(apiCaller, acquirer) client := crossmodelrelations.NewClientWithCache(callerWithBakery, s.cache) diff --git a/apiserver/facades/client/application/deployrepository.go b/apiserver/facades/client/application/deployrepository.go index 8ab363dd75d..d3739c64ac2 100644 --- a/apiserver/facades/client/application/deployrepository.go +++ b/apiserver/facades/client/application/deployrepository.go @@ -410,8 +410,9 @@ func (v *deployFromRepositoryValidator) validate(ctx context.Context, arg params bindings, err := v.newStateBindings(v.state, arg.EndpointBindings) if err != nil { errs = append(errs, err) + } else { + dt.endpoints = bindings.Map() } - dt.endpoints = bindings.Map() } // resolve and validate resources resources, pendingResourceUploads, resolveResErr := v.resolveResources(ctx, dt.charmURL, dt.origin, dt.resources, resolvedCharm.Meta().Resources) diff --git a/apiserver/facades/client/application/deployrepository_test.go b/apiserver/facades/client/application/deployrepository_test.go index 47905751f92..a12d613ac51 100644 --- a/apiserver/facades/client/application/deployrepository_test.go +++ b/apiserver/facades/client/application/deployrepository_test.go @@ -243,6 +243,55 @@ func (s *validatorSuite) TestValidateEndpointBindingSuccess(c *gc.C) { }) } +func (s *validatorSuite) TestValidateEndpointBindingFail(c *gc.C) { + defer s.setupMocks(c).Finish() + s.expectSimpleValidate() + // resolveCharm + curl := charm.MustParseURL("testcharm") + resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4") + origin := corecharm.Origin{ + Source: "charm-hub", + Channel: &charm.Channel{Risk: "stable"}, + Platform: corecharm.Platform{Architecture: "amd64"}, + } + resolvedOrigin := corecharm.Origin{ + Source: "charm-hub", + Type: "charm", + Channel: &charm.Channel{Track: "default", Risk: "stable"}, + Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"}, + Revision: intptr(4), + } + // getCharm + charmID := corecharm.CharmID{URL: curl, Origin: origin} + resolvedData := getResolvedData(resultURL, resolvedOrigin) + s.repo.EXPECT().ResolveForDeploy(gomock.Any(), charmID).Return(resolvedData, nil) + s.repo.EXPECT().ResolveResources(gomock.Any(), nil, corecharm.CharmID{URL: resultURL, Origin: resolvedOrigin}).Return(nil, nil) + s.model.EXPECT().UUID().Return("") + + // state bindings + endpointMap := map[string]string{"to": "from"} + s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil) + s.state.EXPECT().Charm(gomock.Any()).Return(nil, errors.NotFoundf("charm")) + + s.repoFactory.EXPECT().GetCharmRepository(gomock.Any(), gomock.Any()).Return(s.repo, nil).AnyTimes() + v := &deployFromRepositoryValidator{ + model: s.model, + state: s.state, + repoFactory: s.repoFactory, + newStateBindings: func(st any, givenMap map[string]string) (Bindings, error) { + return nil, errors.NotFoundf("space") + }, + } + + arg := params.DeployFromRepositoryArg{ + CharmName: "testcharm", + EndpointBindings: endpointMap, + } + _, errs := v.validate(context.Background(), arg) + c.Assert(errs, gc.HasLen, 1) + c.Assert(errs[0], jc.ErrorIs, errors.NotFound) +} + func (s *validatorSuite) expectSimpleValidate() { // createOrigin s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil) diff --git a/apiserver/facades/client/keymanager/keymanager.go b/apiserver/facades/client/keymanager/keymanager.go index ca910f7c515..23f7623a744 100644 --- a/apiserver/facades/client/keymanager/keymanager.go +++ b/apiserver/facades/client/keymanager/keymanager.go @@ -144,6 +144,7 @@ func (api *KeyManagerAPI) currentKeyDataForAdd(ctx context.Context) (keys []stri fingerprint, _, err := ssh.KeyFingerprint(key) if err != nil { api.logger.Warningf("ignoring invalid ssh key %q: %v", key, err) + continue } fingerprints.Add(fingerprint) } @@ -293,25 +294,33 @@ func (api *KeyManagerAPI) ImportKeys(ctx context.Context, arg params.ModifyUserS return params.ErrorResults{Results: results}, nil } +type keyDataForDelete struct { + allKeys []string + byFingerprint map[string]string + byComment map[string]string + invalidKeys map[string]string +} + // currentKeyDataForDelete gathers data used when deleting ssh keys. -func (api *KeyManagerAPI) currentKeyDataForDelete(ctx context.Context) ( - currentKeys []string, byFingerprint map[string]string, byComment map[string]string, err error) { +func (api *KeyManagerAPI) currentKeyDataForDelete(ctx context.Context) (keyDataForDelete, error) { cfg, err := api.model.ModelConfig(ctx) if err != nil { - return nil, nil, nil, fmt.Errorf("reading current key data: %v", err) + return keyDataForDelete{}, fmt.Errorf("reading current key data: %v", err) } // For now, authorised keys are global, common to all users. - currentKeys = ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()) + currentKeys := ssh.SplitAuthorisedKeys(cfg.AuthorizedKeys()) // Make two maps that index keys by fingerprint and by comment for fast // lookup of keys to delete which may be given as either. - byFingerprint = make(map[string]string) - byComment = make(map[string]string) + byFingerprint := make(map[string]string) + byComment := make(map[string]string) + invalidKeys := make(map[string]string) for _, key := range currentKeys { fingerprint, comment, err := ssh.KeyFingerprint(key) if err != nil { - api.logger.Debugf("keeping unrecognised existing ssh key %q: %v", key, err) + api.logger.Debugf("invalid existing ssh key %q: %v", key, err) + invalidKeys[key] = key continue } byFingerprint[fingerprint] = key @@ -319,7 +328,13 @@ func (api *KeyManagerAPI) currentKeyDataForDelete(ctx context.Context) ( byComment[comment] = key } } - return currentKeys, byFingerprint, byComment, nil + data := keyDataForDelete{ + allKeys: currentKeys, + byFingerprint: byFingerprint, + byComment: byComment, + invalidKeys: invalidKeys, + } + return data, nil } // DeleteKeys deletes the authorised ssh keys for the specified user. @@ -334,7 +349,7 @@ func (api *KeyManagerAPI) DeleteKeys(ctx context.Context, arg params.ModifyUserS return params.ErrorResults{}, nil } - allKeys, byFingerprint, byComment, err := api.currentKeyDataForDelete(ctx) + keyData, err := api.currentKeyDataForDelete(ctx) if err != nil { return params.ErrorResults{}, apiservererrors.ServerError(fmt.Errorf("reading current key data: %v", err)) } @@ -344,13 +359,13 @@ func (api *KeyManagerAPI) DeleteKeys(ctx context.Context, arg params.ModifyUserS results := transform.Slice(arg.Keys, func(keyId string) params.ErrorResult { // Is given keyId a fingerprint? - key, ok := byFingerprint[keyId] + key, ok := keyData.byFingerprint[keyId] if ok { keysToDelete.Add(key) return params.ErrorResult{} } // Not a fingerprint, is it a comment? - key, ok = byComment[keyId] + key, ok = keyData.byComment[keyId] if ok { if jujuKeyCommentIdentifiers.Contains(keyId) { return params.ErrorResult{Error: apiservererrors.ServerError(fmt.Errorf("may not delete internal key: %s", keyId))} @@ -358,13 +373,19 @@ func (api *KeyManagerAPI) DeleteKeys(ctx context.Context, arg params.ModifyUserS keysToDelete.Add(key) return params.ErrorResult{} } - return params.ErrorResult{Error: apiservererrors.ServerError(fmt.Errorf("invalid ssh key: %s", keyId))} + // Allow invalid keys to be deleted by writing out key verbatim. + key, ok = keyData.invalidKeys[keyId] + if ok { + keysToDelete.Add(key) + return params.ErrorResult{} + } + return params.ErrorResult{Error: apiservererrors.ServerError(fmt.Errorf("key not found: %s", keyId))} }) var keysToWrite []string // Add back only the keys that are not deleted, preserving the order. - for _, key := range allKeys { + for _, key := range keyData.allKeys { if !keysToDelete.Contains(key) { keysToWrite = append(keysToWrite, key) } diff --git a/apiserver/facades/client/keymanager/keymanager_test.go b/apiserver/facades/client/keymanager/keymanager_test.go index c6c4c394ae2..2e203ebbd47 100644 --- a/apiserver/facades/client/keymanager/keymanager_test.go +++ b/apiserver/facades/client/keymanager/keymanager_test.go @@ -119,6 +119,7 @@ func (s *keyManagerSuite) assertAddKeys(c *gc.C) { s.setAuthorizedKeys(c, key1, key2, "bad key") newKey := sshtesting.ValidKeyThree.Key + " newuser@host" + newLineKey := sshtesting.ValidKeyFour.Key + " line1\nline2" newAttrs := map[string]interface{}{ config.AuthorizedKeysKey: strings.Join([]string{key1, key2, "bad key", newKey}, "\n"), @@ -127,7 +128,7 @@ func (s *keyManagerSuite) assertAddKeys(c *gc.C) { args := params.ModifyUserSSHKeys{ User: names.NewUserTag("admin").Name(), - Keys: []string{key2, newKey, newKey, "invalid-key"}, + Keys: []string{key2, newKey, newKey, "invalid-key", newLineKey}, } results, err := s.api.AddKeys(context.Background(), args) c.Assert(err, jc.ErrorIsNil) @@ -138,6 +139,7 @@ func (s *keyManagerSuite) assertAddKeys(c *gc.C) { {Error: nil}, {Error: apiservertesting.ServerError(fmt.Sprintf("duplicate ssh key: %s", newKey))}, {Error: apiservertesting.ServerError("invalid ssh key: invalid-key")}, + {Error: apiservertesting.ServerError(fmt.Sprintf("invalid ssh key: %s", newLineKey))}, }, }) } @@ -207,24 +209,25 @@ func (s *keyManagerSuite) TestAddJujuSystemKey(c *gc.C) { func (s *keyManagerSuite) assertDeleteKeys(c *gc.C) { key1 := sshtesting.ValidKeyOne.Key + " user@host" key2 := sshtesting.ValidKeyTwo.Key - s.setAuthorizedKeys(c, key1, key2, "bad key") + s.setAuthorizedKeys(c, key1, key2, "bad key 1", "bad key 2") newAttrs := map[string]interface{}{ - config.AuthorizedKeysKey: strings.Join([]string{key1, "bad key"}, "\n"), + config.AuthorizedKeysKey: strings.Join([]string{key1, "bad key 1"}, "\n"), } s.model.EXPECT().UpdateModelConfig(gomock.Any(), newAttrs, nil) args := params.ModifyUserSSHKeys{ User: names.NewUserTag("admin").String(), - Keys: []string{sshtesting.ValidKeyTwo.Fingerprint, sshtesting.ValidKeyThree.Fingerprint, "invalid-key"}, + Keys: []string{sshtesting.ValidKeyTwo.Fingerprint, sshtesting.ValidKeyThree.Fingerprint, "invalid-key", "bad key 2"}, } results, err := s.api.DeleteKeys(context.Background(), args) c.Assert(err, jc.ErrorIsNil) c.Assert(results, gc.DeepEquals, params.ErrorResults{ Results: []params.ErrorResult{ {Error: nil}, - {Error: apiservertesting.ServerError("invalid ssh key: " + sshtesting.ValidKeyThree.Fingerprint)}, - {Error: apiservertesting.ServerError("invalid ssh key: invalid-key")}, + {Error: apiservertesting.ServerError("key not found: " + sshtesting.ValidKeyThree.Fingerprint)}, + {Error: apiservertesting.ServerError("key not found: invalid-key")}, + {Error: nil}, }, }) } diff --git a/apiserver/facades/client/modelupgrader/upgrader.go b/apiserver/facades/client/modelupgrader/upgrader.go index 60d6746089a..f3342259b45 100644 --- a/apiserver/facades/client/modelupgrader/upgrader.go +++ b/apiserver/facades/client/modelupgrader/upgrader.go @@ -166,15 +166,10 @@ func (m *ModelUpgraderAPI) UpgradeModel(ctx stdcontext.Context, arg params.Upgra return result, errors.Trace(err) } - m.logger.Debugf("deciding target version for model upgrade, from %q to %q for stream %q", currentVersion, targetVersion, arg.AgentStream) - args := common.FindAgentsParams{ - AgentStream: arg.AgentStream, - ControllerCfg: controllerCfg, - ModelType: model.Type(), - } // For non controller models, we use the exact controller // model version to upgrade to, unless an explicit target // has been specified. + useControllerVersion := false if !model.IsControllerModel() { ctrlModel, err := m.statePool.ControllerModel() if err != nil { @@ -184,26 +179,35 @@ func (m *ModelUpgraderAPI) UpgradeModel(ctx stdcontext.Context, arg params.Upgra if err != nil { return result, errors.Trace(err) } - if targetVersion == version.Zero { + if targetVersion == version.Zero || targetVersion.Compare(vers) == 0 { targetVersion = vers - } else if vers.Compare(targetVersion.ToPatch()) > 0 { - return result, errors.Errorf("cannot upgrade to a version %q greater than that of the controller %q", args.Number, vers) + useControllerVersion = true + } else if vers.Compare(targetVersion.ToPatch()) < 0 { + return result, errors.Errorf("cannot upgrade to a version %q greater than that of the controller %q", targetVersion, vers) } } - if targetVersion == version.Zero { - args.MajorVersion = currentVersion.Major - args.MinorVersion = currentVersion.Minor - } else { - args.Number = targetVersion - } - targetVersion, err = m.decideVersion(ctx, currentVersion, args) - if errors.Is(errors.Cause(err), errors.NotFound) || errors.Is(errors.Cause(err), errors.AlreadyExists) { - result.Error = apiservererrors.ServerError(err) - return result, nil - } + if !useControllerVersion { + m.logger.Debugf("deciding target version for model upgrade, from %q to %q for stream %q", currentVersion, targetVersion, arg.AgentStream) + args := common.FindAgentsParams{ + AgentStream: arg.AgentStream, + ControllerCfg: controllerCfg, + ModelType: model.Type(), + } + if targetVersion == version.Zero { + args.MajorVersion = currentVersion.Major + args.MinorVersion = currentVersion.Minor + } else { + args.Number = targetVersion + } + targetVersion, err = m.decideVersion(ctx, currentVersion, args) + if errors.Is(errors.Cause(err), errors.NotFound) || errors.Is(errors.Cause(err), errors.AlreadyExists) { + result.Error = apiservererrors.ServerError(err) + return result, nil + } - if err != nil { - return result, errors.Trace(err) + if err != nil { + return result, errors.Trace(err) + } } // Before changing the agent version to trigger an upgrade or downgrade, diff --git a/apiserver/facades/client/modelupgrader/upgrader_test.go b/apiserver/facades/client/modelupgrader/upgrader_test.go index 14cde3e1016..e781b01a541 100644 --- a/apiserver/facades/client/modelupgrader/upgrader_test.go +++ b/apiserver/facades/client/modelupgrader/upgrader_test.go @@ -543,7 +543,7 @@ cannot upgrade to "3.9.99" due to issues with these models: - LXD version has to be at least "5.0.0", but current version is only "4.0.0"`[1:]) } -func (s *modelUpgradeSuite) assertUpgradeModelJuju3(c *gc.C, dryRun bool) { +func (s *modelUpgradeSuite) assertUpgradeModelJuju3(c *gc.C, ctrlModelVers string, dryRun bool) { ctrl := s.setupMocks(c) defer ctrl.Finish() @@ -572,18 +572,19 @@ func (s *modelUpgradeSuite) assertUpgradeModelJuju3(c *gc.C, dryRun bool) { st.EXPECT().ControllerConfig().Return(controllerCfg, nil) model.EXPECT().Life().Return(state.Alive) model.EXPECT().AgentVersion().Return(version.MustParse("2.9.1"), nil) - model.EXPECT().Type().Return(state.ModelTypeIAAS) - model.EXPECT().IsControllerModel().Return(false) + model.EXPECT().IsControllerModel().Return(false).AnyTimes() s.statePool.EXPECT().ControllerModel().Return(ctrlModel, nil) - ctrlModel.EXPECT().AgentVersion().Return(version.MustParse("3.9.99"), nil) - s.toolsFinder.EXPECT().FindAgents(gomock.Any(), common.FindAgentsParams{ - Number: version.MustParse("3.9.99"), - ControllerCfg: controllerCfg, ModelType: state.ModelTypeIAAS}).Return( - []*coretools.Tools{ - {Version: version.MustParseBinary("3.9.99-ubuntu-amd64")}, - }, nil, - ) - model.EXPECT().IsControllerModel().Return(false).Times(2) + ctrlModel.EXPECT().AgentVersion().Return(version.MustParse(ctrlModelVers), nil) + if ctrlModelVers != "3.9.99" { + model.EXPECT().Type().Return(state.ModelTypeIAAS) + s.toolsFinder.EXPECT().FindAgents(gomock.Any(), common.FindAgentsParams{ + Number: version.MustParse("3.9.99"), + ControllerCfg: controllerCfg, ModelType: state.ModelTypeIAAS}).Return( + []*coretools.Tools{ + {Version: version.MustParseBinary("3.9.99-ubuntu-amd64")}, + }, nil, + ) + } // - check no upgrade series in process. st.EXPECT().HasUpgradeSeriesLocks().Return(false, nil) @@ -615,11 +616,15 @@ func (s *modelUpgradeSuite) assertUpgradeModelJuju3(c *gc.C, dryRun bool) { } func (s *modelUpgradeSuite) TestUpgradeModelJuju3(c *gc.C) { - s.assertUpgradeModelJuju3(c, false) + s.assertUpgradeModelJuju3(c, "3.10.0", false) +} + +func (s *modelUpgradeSuite) TestUpgradeModelJuju3SameAsController(c *gc.C) { + s.assertUpgradeModelJuju3(c, "3.9.99", false) } func (s *modelUpgradeSuite) TestUpgradeModelJuju3DryRun(c *gc.C) { - s.assertUpgradeModelJuju3(c, true) + s.assertUpgradeModelJuju3(c, "3.10.0", true) } func (s *modelUpgradeSuite) TestUpgradeModelJuju3Failed(c *gc.C) { @@ -651,9 +656,9 @@ func (s *modelUpgradeSuite) TestUpgradeModelJuju3Failed(c *gc.C) { model.EXPECT().Life().Return(state.Alive) model.EXPECT().AgentVersion().Return(version.MustParse("2.9.1"), nil) model.EXPECT().Type().Return(state.ModelTypeIAAS) - model.EXPECT().IsControllerModel().Return(false) + model.EXPECT().IsControllerModel().Return(false).AnyTimes() s.statePool.EXPECT().ControllerModel().Return(ctrlModel, nil) - ctrlModel.EXPECT().AgentVersion().Return(version.MustParse("3.9.99"), nil) + ctrlModel.EXPECT().AgentVersion().Return(version.MustParse("3.10.0"), nil) s.toolsFinder.EXPECT().FindAgents(gomock.Any(), common.FindAgentsParams{ Number: version.MustParse("3.9.99"), ControllerCfg: controllerCfg, ModelType: state.ModelTypeIAAS}).Return( @@ -661,7 +666,6 @@ func (s *modelUpgradeSuite) TestUpgradeModelJuju3Failed(c *gc.C) { {Version: version.MustParseBinary("3.9.99-ubuntu-amd64")}, }, nil, ) - model.EXPECT().IsControllerModel().Return(false).Times(2) // - check no upgrade series in process. st.EXPECT().HasUpgradeSeriesLocks().Return(true, nil) @@ -697,6 +701,39 @@ cannot upgrade to "3.9.99" due to issues with these models: - LXD version has to be at least "5.0.0", but current version is only "4.0.0"`[1:]) } +func (s *modelUpgradeSuite) TestCannotUpgradePastControllerVersion(c *gc.C) { + ctrl := s.setupMocks(c) + defer ctrl.Finish() + + api := s.newFacade(c) + + modelUUID := coretesting.ModelTag.Id() + model := mocks.NewMockModel(ctrl) + st := mocks.NewMockState(ctrl) + st.EXPECT().Release().AnyTimes() + + s.statePool.EXPECT().Get(modelUUID).AnyTimes().Return(st, nil) + st.EXPECT().Model().AnyTimes().Return(model, nil) + ctrlModel := mocks.NewMockModel(ctrl) + + s.blockChecker.EXPECT().ChangeAllowed(gomock.Any()).Return(nil) + + st.EXPECT().ControllerConfig().Return(controllerCfg, nil) + model.EXPECT().Life().Return(state.Alive) + model.EXPECT().AgentVersion().Return(version.MustParse("2.9.1"), nil) + model.EXPECT().IsControllerModel().Return(false) + s.statePool.EXPECT().ControllerModel().Return(ctrlModel, nil) + ctrlModel.EXPECT().AgentVersion().Return(version.MustParse("3.9.99"), nil) + + _, err := api.UpgradeModel(stdcontext.Background(), + params.UpgradeModelParams{ + ModelTag: coretesting.ModelTag.String(), + TargetVersion: version.MustParse("3.12.0"), + }, + ) + c.Assert(err, gc.ErrorMatches, `cannot upgrade to a version "3.12.0" greater than that of the controller "3.9.99"`) +} + func (s *modelUpgradeSuite) TestAbortCurrentUpgrade(c *gc.C) { ctrl := s.setupMocks(c) defer ctrl.Finish() diff --git a/caas/kubernetes/provider/application/application.go b/caas/kubernetes/provider/application/application.go index d23b8266c31..23884ca0953 100644 --- a/caas/kubernetes/provider/application/application.go +++ b/caas/kubernetes/provider/application/application.go @@ -76,6 +76,7 @@ const ( var ( containerAgentPebbleVersion = version.MustParse("2.9.37") profileDirVersion = version.MustParse("3.5-beta1") + pebbleCopyOnceVersion = version.MustParse("3.5-beta1") ) type app struct { @@ -1501,6 +1502,20 @@ func (a *app) ApplicationPodSpec(config caas.ApplicationConfig) (*corev1.PodSpec } } + containerExtraEnv := []corev1.EnvVar{{ + Name: "PEBBLE_SOCKET", + Value: "/charm/container/pebble.socket", + }} + if agentVersionNoBuild.Compare(pebbleCopyOnceVersion) >= 0 { + containerExtraEnv = append(containerExtraEnv, corev1.EnvVar{ + Name: "PEBBLE", + Value: "/charm/container/pebble", + }, corev1.EnvVar{ + Name: "PEBBLE_COPY_ONCE", + Value: constants.DefaultPebbleDir, + }) + } + containerSpecs := []corev1.Container{charmContainer} for i, v := range containers { container := corev1.Container{ @@ -1515,16 +1530,10 @@ func (a *app) ApplicationPodSpec(config caas.ApplicationConfig) (*corev1.PodSpec "--http", fmt.Sprintf(":%s", pebble.WorkloadHealthCheckPort(i)), "--verbose", }, - Env: []corev1.EnvVar{{ + Env: append([]corev1.EnvVar{{ Name: "JUJU_CONTAINER_NAME", Value: v.Name, - }, { - Name: "PEBBLE_SOCKET", - Value: "/charm/container/pebble.socket", - }, { - Name: "PEBBLE", - Value: "/charm/container/pebble", - }}, + }}, containerExtraEnv...), LivenessProbe: &corev1.Probe{ ProbeHandler: pebble.LivenessHandler(pebble.WorkloadHealthCheckPort(i)), InitialDelaySeconds: containerProbeInitialDelay, diff --git a/caas/kubernetes/provider/application/application_test.go b/caas/kubernetes/provider/application/application_test.go index a052ee54dc5..18f71baca09 100644 --- a/caas/kubernetes/provider/application/application_test.go +++ b/caas/kubernetes/provider/application/application_test.go @@ -649,6 +649,10 @@ func getPodSpec() corev1.PodSpec { Name: "PEBBLE", Value: "/charm/container/pebble", }, + { + Name: "PEBBLE_COPY_ONCE", + Value: "/var/lib/pebble/default", + }, }, LivenessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ @@ -716,6 +720,10 @@ func getPodSpec() corev1.PodSpec { Name: "PEBBLE", Value: "/charm/container/pebble", }, + { + Name: "PEBBLE_COPY_ONCE", + Value: "/var/lib/pebble/default", + }, }, LivenessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ diff --git a/cmd/juju/sshkeys/remove_sshkeys.go b/cmd/juju/sshkeys/remove_sshkeys.go index dbe2a6b560c..a6dbe0eebf2 100644 --- a/cmd/juju/sshkeys/remove_sshkeys.go +++ b/cmd/juju/sshkeys/remove_sshkeys.go @@ -22,7 +22,8 @@ Juju maintains a per-model cache of public SSH keys which it copies to each unit. This command will remove a specified key (or space separated list of keys) from the model cache and all current units deployed in that model. The keys to be removed may be specified by the key's fingerprint, -or by the text label associated with them. +or by the text label associated with them. Invalid keys in the model cache +can be removed by specifying the key verbatim. `[1:] diff --git a/cmd/modelcmd/base.go b/cmd/modelcmd/base.go index b1f50d4c1b7..52ea5d02167 100644 --- a/cmd/modelcmd/base.go +++ b/cmd/modelcmd/base.go @@ -214,19 +214,9 @@ func (c *CommandBase) NewAPIRoot( return c.NewAPIRootWithDialOpts(store, controllerName, modelName, nil, nil) } -// NewAPIRootWithDialOpts returns a new connection to the API server for the -// given model or controller (the default dial options will be overridden if -// dialOpts is not nil). -func (c *CommandBase) NewAPIRootWithDialOpts( - store jujuclient.ClientStore, - controllerName, modelName string, - addressOverride []string, - dialOpts *api.DialOpts, -) (api.Connection, error) { - c.assertRunStarted() - accountDetails, err := store.AccountDetails(controllerName) - if err != nil && !errors.Is(err, errors.NotFound) { - return nil, errors.Trace(err) +func processAccountDetails(accountDetails *jujuclient.AccountDetails) *jujuclient.AccountDetails { + if accountDetails != nil && accountDetails.Type != "" && accountDetails.Type != jujuclient.UserPassAccountDetailsType { + return accountDetails } // If there are no account details or there's no logged-in // user or the user is external, then trigger macaroon authentication @@ -249,6 +239,25 @@ func (c *CommandBase) NewAPIRootWithDialOpts( } } } + return accountDetails +} + +// NewAPIRootWithDialOpts returns a new connection to the API server for the +// given model or controller (the default dial options will be overridden if +// dialOpts is not nil). +func (c *CommandBase) NewAPIRootWithDialOpts( + store jujuclient.ClientStore, + controllerName, modelName string, + addressOverride []string, + dialOpts *api.DialOpts, +) (api.Connection, error) { + c.assertRunStarted() + accountDetails, err := store.AccountDetails(controllerName) + if err != nil && !errors.Is(err, errors.NotFound) { + return nil, errors.Trace(err) + } + + accountDetails = processAccountDetails(accountDetails) param, err := c.NewAPIConnectionParams( store, controllerName, modelName, accountDetails, diff --git a/cmd/modelcmd/base_test.go b/cmd/modelcmd/base_test.go index e815411b8f1..f529a3eab47 100644 --- a/cmd/modelcmd/base_test.go +++ b/cmd/modelcmd/base_test.go @@ -13,6 +13,7 @@ import ( "github.com/juju/cmd/v4" "github.com/juju/cmd/v4/cmdtesting" "github.com/juju/errors" + "github.com/juju/names/v5" cookiejar "github.com/juju/persistent-cookiejar" "github.com/juju/testing" jc "github.com/juju/testing/checkers" @@ -407,3 +408,65 @@ func addCookie(c *gc.C, jar http.CookieJar, mac *macaroon.Macaroon, url *url.URL cookie.Expires = time.Now().Add(time.Hour) // only persistent cookies are stored jar.SetCookies(url, []*http.Cookie{cookie}) } + +func (s *BaseCommandSuite) TestProcessAccountDetails(c *gc.C) { + + m, err := macaroon.New([]byte("test-root-key"), []byte("test-id"), "", macaroon.V2) + c.Assert(err, gc.IsNil) + + tests := []struct { + input jujuclient.AccountDetails + expectedOutput jujuclient.AccountDetails + }{{ + input: jujuclient.AccountDetails{ + Type: jujuclient.OAuth2DeviceFlowAccountDetailsType, + SessionToken: "test-session-token", + }, + expectedOutput: jujuclient.AccountDetails{ + Type: jujuclient.OAuth2DeviceFlowAccountDetailsType, + SessionToken: "test-session-token", + }, + }, { + input: jujuclient.AccountDetails{ + Type: "", + User: names.NewUserTag("alice").String(), + Password: "test-secret-password", + }, + expectedOutput: jujuclient.AccountDetails{ + Type: "", + User: names.NewUserTag("alice").String(), + Password: "test-secret-password", + }, + }, { + input: jujuclient.AccountDetails{ + Type: jujuclient.UserPassAccountDetailsType, + User: names.NewUserTag("alice").String(), + Macaroons: []macaroon.Slice{{m}}, + }, + expectedOutput: jujuclient.AccountDetails{ + Type: jujuclient.UserPassAccountDetailsType, + User: names.NewUserTag("alice").String(), + Macaroons: []macaroon.Slice{{m}}, + }, + }, { + input: jujuclient.AccountDetails{ + Type: jujuclient.UserPassAccountDetailsType, + User: names.NewUserTag("alice@wonderland.canonical.com").String(), + Macaroons: []macaroon.Slice{{m}}, + }, + expectedOutput: jujuclient.AccountDetails{ + User: names.NewUserTag("alice@wonderland.canonical.com").String(), + Macaroons: []macaroon.Slice{{m}}, + }, + }, { + input: jujuclient.AccountDetails{ + User: names.NewUserTag("alice@wonderland.canonical.com").String(), + }, + expectedOutput: jujuclient.AccountDetails{}, + }} + for i, test := range tests { + c.Logf("running test case %d", i) + output := modelcmd.ProcessAccountDetails(&test.input) + c.Assert(output, gc.DeepEquals, &test.expectedOutput) + } +} diff --git a/cmd/modelcmd/export_test.go b/cmd/modelcmd/export_test.go index e0be787e2dd..9c08c34ec17 100644 --- a/cmd/modelcmd/export_test.go +++ b/cmd/modelcmd/export_test.go @@ -10,7 +10,10 @@ import ( "github.com/juju/juju/jujuclient" ) -var NewAPIContext = newAPIContext +var ( + NewAPIContext = newAPIContext + ProcessAccountDetails = processAccountDetails +) func Interactor(ctx *apiContext) httpbakery.Interactor { return ctx.interactor diff --git a/go.mod b/go.mod index 55ed19f3e17..08e6efb45cb 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/bmizerany/pat v0.0.0-20210406213842-e4b6760bdd6f github.com/canonical/go-dqlite v1.21.0 github.com/canonical/lxd v0.0.0-20231214113525-e676fc63c50a - github.com/canonical/pebble v1.9.0 + github.com/canonical/pebble v1.10.2 github.com/canonical/sqlair v0.0.0-20240319123511-a48b89bb30aa github.com/chzyer/readline v1.5.1 github.com/coreos/go-systemd/v22 v22.5.0 @@ -48,7 +48,7 @@ require ( github.com/juju/clock v1.0.3 github.com/juju/cmd/v4 v4.0.0 github.com/juju/collections v1.0.4 - github.com/juju/description/v5 v5.0.3 + github.com/juju/description/v5 v5.0.4 github.com/juju/errors v1.0.0 github.com/juju/featureflag v1.0.0 github.com/juju/gnuflag v1.0.0 @@ -77,7 +77,7 @@ require ( github.com/juju/testing v1.2.0 github.com/juju/txn/v3 v3.0.2 github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647 - github.com/juju/utils/v4 v4.0.1 + github.com/juju/utils/v4 v4.0.2 github.com/juju/version/v2 v2.0.1 github.com/juju/viddy v0.0.0-beta5 github.com/juju/webbrowser v1.0.0 @@ -112,7 +112,7 @@ require ( go.uber.org/goleak v1.3.0 go.uber.org/mock v0.4.0 golang.org/x/crypto v0.21.0 - golang.org/x/net v0.21.0 + golang.org/x/net v0.22.0 golang.org/x/oauth2 v0.15.0 golang.org/x/sync v0.5.0 golang.org/x/sys v0.18.0 diff --git a/go.sum b/go.sum index dcf1432465a..7792f2c99cf 100644 --- a/go.sum +++ b/go.sum @@ -152,8 +152,8 @@ github.com/canonical/go-flags v0.0.0-20230403090104-105d09a091b8 h1:zGaJEJI9qPVy github.com/canonical/go-flags v0.0.0-20230403090104-105d09a091b8/go.mod h1:ZZFeR9K9iGgpwOaLYF9PdT44/+lfSJ9sQz3B+SsGsYU= github.com/canonical/lxd v0.0.0-20231214113525-e676fc63c50a h1:Tfo/MzXK5GeG7gzSHqxGeY/669Mhh5ea43dn1mRDnk8= github.com/canonical/lxd v0.0.0-20231214113525-e676fc63c50a/go.mod h1:UxfHGKFoRjgu1NUA9EFiR++dKvyAiT0h9HT0ffMlzjc= -github.com/canonical/pebble v1.9.0 h1:FWVEh1fg3aaW2HNue2Z2eYMwkJEQT8mgMFW3R5Iocn4= -github.com/canonical/pebble v1.9.0/go.mod h1:9Qkjmq298g0+9SvM2E5eekkEN4pjHDWhgg9eB2I0tjk= +github.com/canonical/pebble v1.10.2 h1:TG0RYLqH+WEjnxsTB1JbaW0wzeygG0/dPHEEFQKn2JE= +github.com/canonical/pebble v1.10.2/go.mod h1:BXpt85cFqrBgACeVRrTQ7JxZIdnGILv32V7mAfDcGFc= github.com/canonical/sqlair v0.0.0-20240319123511-a48b89bb30aa h1:eRRgzybbMhVtJXwIyrTCTuS66n46Mq0Li/tQF3ZtHtU= github.com/canonical/sqlair v0.0.0-20240319123511-a48b89bb30aa/go.mod h1:T+40I2sXshY3KRxx0QQpqqn6hCibSKJ2KHzjBvJj8T4= github.com/canonical/x-go v0.0.0-20230522092633-7947a7587f5b h1:Da2fardddn+JDlVEYtrzBLTtyzoyU3nIS0Cf0GvjmwU= @@ -496,8 +496,8 @@ github.com/juju/collections v0.0.0-20220203020748-febd7cad8a7a/go.mod h1:JWeZdyt github.com/juju/collections v1.0.0/go.mod h1:JWeZdyttIEbkR51z2S13+J+aCuHVe0F6meRy+P0YGDo= github.com/juju/collections v1.0.4 h1:GjL+aN512m2rVDqhPII7P6qB0e+iYFubz8sqBhZaZtk= github.com/juju/collections v1.0.4/go.mod h1:hVrdB0Zwq9wIU1Fl6ItD2+UETeNeOEs+nGvJufVe+0c= -github.com/juju/description/v5 v5.0.3 h1:YrFPByKv7aBb8PxQPKeMIVe2OPCbR4ED6+RgNnZm91I= -github.com/juju/description/v5 v5.0.3/go.mod h1:TQsp2Z56EVab7onQY2/O6tLHznovAcckyLz/DgDfVPY= +github.com/juju/description/v5 v5.0.4 h1:qA35hRglZ47j1mmo9zUM9R+2WSDCH5dvL5ik7gA2aVE= +github.com/juju/description/v5 v5.0.4/go.mod h1:TQsp2Z56EVab7onQY2/O6tLHznovAcckyLz/DgDfVPY= github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/errors v0.0.0-20200330140219-3fe23663418f/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/errors v0.0.0-20210818161939-5560c4c073ff/go.mod h1:i1eL7XREII6aHpQ2gApI/v6FkVUDEBremNkcBCKYAcY= @@ -605,8 +605,8 @@ github.com/juju/utils/v3 v3.0.0-20220203023959-c3fbc78a33b0/go.mod h1:8csUcj1VRk github.com/juju/utils/v3 v3.0.0/go.mod h1:8csUcj1VRkfjNIRzBFWzLFCMLwLqsRWvkmhfVAUwbC4= github.com/juju/utils/v3 v3.1.0 h1:NrNo73oVtfr7kLP17/BDpubXwa7YEW16+Ult6z9kpHI= github.com/juju/utils/v3 v3.1.0/go.mod h1:nAj3sHtdYfAkvnkqttTy3Xzm2HzkD9Hfgnc+upOW2Z8= -github.com/juju/utils/v4 v4.0.1 h1:8cBliYXk1I/MPaZZ/FLLrPsgjmdWgL3aIhsUlZ7wmWo= -github.com/juju/utils/v4 v4.0.1/go.mod h1:j5wVHbRzw2LF85mb3H46cPPXBkyw5k4laDL6cOW55LY= +github.com/juju/utils/v4 v4.0.2 h1:lh1cPruYCPLWBLuZpaAP18cc+3j9X/JsLExjwQ/+NxQ= +github.com/juju/utils/v4 v4.0.2/go.mod h1:j5wVHbRzw2LF85mb3H46cPPXBkyw5k4laDL6cOW55LY= github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= github.com/juju/version v0.0.0-20191219164919-81c1be00b9a6/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U= @@ -1077,8 +1077,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/internal/worker/remoterelations/remoteapplicationworker.go b/internal/worker/remoterelations/remoteapplicationworker.go index 3e7671f29f0..f2ac92dfd97 100644 --- a/internal/worker/remoterelations/remoteapplicationworker.go +++ b/internal/worker/remoterelations/remoteapplicationworker.go @@ -665,11 +665,9 @@ func (w *remoteApplicationWorker) processConsumingRelation( if w.secretChangesWatcher == nil { w.secretChangesWatcher, err = w.remoteModelFacade.WatchConsumedSecretsChanges(context.TODO(), applicationToken, relationToken, w.offerMacaroon) - if err != nil && !errors.Is(err, errors.NotFound) { + if err != nil && !errors.Is(err, errors.NotFound) && !errors.Is(err, errors.NotImplemented) { w.checkOfferPermissionDenied(err, "", "") - if !isNotFound(err) { - return errors.Annotate(err, "watching consumed secret changes") - } + return errors.Annotate(err, "watching consumed secret changes") } if err == nil { if err := w.catacomb.Add(w.secretChangesWatcher); err != nil { diff --git a/internal/worker/remoterelations/remoterelations_test.go b/internal/worker/remoterelations/remoterelations_test.go index 9d217dcca3e..55213039727 100644 --- a/internal/worker/remoterelations/remoterelations_test.go +++ b/internal/worker/remoterelations/remoterelations_test.go @@ -1099,6 +1099,77 @@ func (s *remoteRelationsSuite) TestRemoteSecretChangedConsumes(c *gc.C) { s.waitForWorkerStubCalls(c, expected) } +func (s *remoteRelationsSuite) TestRemoteSecretNotImplemented(c *gc.C) { + s.relationsFacade.relations["db2:db django:db"] = newMockRelation(123) + w := s.assertRemoteApplicationWorkers(c) + s.stub.ResetCalls() + + s.stub.SetErrors(nil, nil, nil, nil, nil, nil, nil, nil, errors.NotImplemented) + + s.relationsFacade.relationsEndpoints["db2:db django:db"] = &relationEndpointInfo{ + localApplicationName: "django", + localEndpoint: params.RemoteEndpoint{ + Name: "db2", + Role: "requires", + Interface: "db2", + }, + remoteEndpointName: "data", + } + + relWatcher, _ := s.relationsFacade.remoteApplicationRelationsWatcher("db2") + relWatcher.changes <- []string{"db2:db django:db"} + + mac, err := testing.NewMacaroon("test") + c.Assert(err, jc.ErrorIsNil) + apiMac, err := testing.NewMacaroon("apimac") + c.Assert(err, jc.ErrorIsNil) + relTag := names.NewRelationTag("db2:db django:db") + expected := []jujutesting.StubCall{ + {"Relations", []interface{}{[]string{"db2:db django:db"}}}, + {"ExportEntities", []interface{}{ + []names.Tag{names.NewApplicationTag("django"), relTag}}}, + {"RegisterRemoteRelations", []interface{}{[]params.RegisterRemoteRelationArg{{ + ApplicationToken: "token-django", + SourceModelTag: "model-local-model-uuid", + RelationToken: "token-db2:db django:db", + RemoteEndpoint: params.RemoteEndpoint{ + Name: "db2", + Role: "requires", + Interface: "db2", + }, + OfferUUID: "offer-db2-uuid", + ConsumeVersion: 666, + LocalEndpointName: "data", + Macaroons: macaroon.Slice{mac}, + BakeryVersion: bakery.LatestVersion, + }}}}, + {"SaveMacaroon", []interface{}{relTag, apiMac}}, + {"ImportRemoteEntity", []interface{}{names.NewApplicationTag("db2"), "token-offer-db2-uuid"}}, + {"WatchRelationSuspendedStatus", []interface{}{"token-db2:db django:db", macaroon.Slice{apiMac}}}, + {"WatchLocalRelationChanges", []interface{}{"db2:db django:db"}}, + {"WatchRelationChanges", []interface{}{"token-db2:db django:db", "token-offer-db2-uuid", macaroon.Slice{apiMac}}}, + {"WatchConsumedSecretsChanges", []interface{}{"token-django", "token-db2:db django:db", mac}}, + } + s.waitForWorkerStubCalls(c, expected) + + changeWatcher, ok := s.relationsFacade.remoteRelationWatcher("db2:db django:db") + c.Check(ok, jc.IsTrue) + waitForStubCalls(c, &changeWatcher.Stub, []jujutesting.StubCall{ + {"Changes", nil}, + }) + changeWatcher, ok = s.remoteRelationsFacade.remoteRelationWatcher("token-db2:db django:db") + c.Check(ok, jc.IsTrue) + waitForStubCalls(c, &changeWatcher.Stub, []jujutesting.StubCall{ + {"Changes", nil}, + }) + relationStatusWatcher, ok := s.remoteRelationsFacade.relationsStatusWatcher("token-db2:db django:db") + c.Check(ok, jc.IsTrue) + waitForStubCalls(c, &relationStatusWatcher.Stub, []jujutesting.StubCall{ + {"Changes", nil}, + }) + workertest.CleanKill(c, w) +} + func (s *remoteRelationsSuite) TestRegisteredApplicationNotRegistered(c *gc.C) { s.relationsFacade.relations["db2:db django:db"] = newMockRelation(123) db2app := newMockRemoteApplication("db2", "db2url") diff --git a/internal/worker/uniter/actions/resolver.go b/internal/worker/uniter/actions/resolver.go index 567deaf8815..d63417bbc69 100644 --- a/internal/worker/uniter/actions/resolver.go +++ b/internal/worker/uniter/actions/resolver.go @@ -23,6 +23,7 @@ var _ logger = struct{}{} // Logger represents the logging methods used by the actions resolver. type Logger interface { Infof(string, ...interface{}) + Debugf(string, ...interface{}) } type actionsResolver struct { @@ -60,11 +61,16 @@ func (r *actionsResolver) NextOp( // deferred until the unit is running. If the remote charm needs // updating, hold off on action running. if remoteState.ActionsBlocked || localState.OutdatedRemoteCharm { + r.logger.Infof("actions are blocked=%v; outdated remote charm=%v - have pending actions: %v", remoteState.ActionsBlocked, localState.OutdatedRemoteCharm, remoteState.ActionsPending) + if localState.ActionId == nil { + r.logger.Debugf("actions are blocked, no in flight actions") + return nil, resolver.ErrNoOperation + } // If we were somehow running an action during remote container changes/restart // we need to fail it and move on. + r.logger.Infof("incomplete action %v is blocked", *localState.ActionId) if localState.Kind == operation.RunAction { if localState.Hook != nil { - r.logger.Infof("found incomplete action %v; ignoring", localState.ActionId) r.logger.Infof("recommitting prior %q hook", localState.Hook.Kind) return opFactory.NewSkipHook(*localState.Hook) } @@ -80,6 +86,9 @@ func (r *actionsResolver) NextOp( if err != nil && err != resolver.ErrNoOperation { return nil, err } + if nextActionId == "" { + r.logger.Debugf("no next action from pending=%v; completed=%v", remoteState.ActionsPending, localState.CompletedActions) + } defer func() { if errors.Cause(err) == charmrunner.ErrActionNotAvailable { @@ -108,7 +117,7 @@ func (r *actionsResolver) NextOp( return opFactory.NewSkipHook(*localState.Hook) } - r.logger.Infof("%q hook is nil", operation.RunAction) + r.logger.Infof("%q hook is nil, so running action %v", operation.RunAction, nextActionId) // If the next action is the same as what the uniter is // currently running then this means that the uniter was // some how interrupted (killed) when running the action @@ -117,6 +126,7 @@ func (r *actionsResolver) NextOp( // is fail the action, since rerunning an arbitrary // command can potentially be hazardous. if nextActionId == *localState.ActionId { + r.logger.Debugf("unit agent was interrupted while running action %v", *localState.ActionId) return opFactory.NewFailAction(*localState.ActionId) } diff --git a/internal/worker/uniter/remotestate/watcher.go b/internal/worker/uniter/remotestate/watcher.go index 4928d2d0b82..1658c40dda4 100644 --- a/internal/worker/uniter/remotestate/watcher.go +++ b/internal/worker/uniter/remotestate/watcher.go @@ -372,6 +372,7 @@ func (w *RemoteStateWatcher) setUp(ctx context.Context, unitTag names.UnitTag) ( } } } + w.logger.Debugf("starting remote state watcher, actions for %s; blocked=%v", w.unit.Tag(), w.current.ActionsBlocked) return nil } @@ -1314,6 +1315,7 @@ func (w *RemoteStateWatcher) actionsChanged(actions []string) { func (w *RemoteStateWatcher) containerRunningStatus(runningStatus ContainerRunningStatus) { w.mu.Lock() + w.logger.Debugf("running status update for %s(provider-id=%s): %+v", w.unit.Tag(), w.current.ProviderID, runningStatus) w.current.ActionsBlocked = !runningStatus.Running w.current.ContainerRunningStatus = &runningStatus w.mu.Unlock() diff --git a/internal/worker/uniter/resolver.go b/internal/worker/uniter/resolver.go index 6a95a1b1718..a4481d624e8 100644 --- a/internal/worker/uniter/resolver.go +++ b/internal/worker/uniter/resolver.go @@ -62,7 +62,14 @@ func (s *uniterResolver) NextOp( localState resolver.LocalState, remoteState remotestate.Snapshot, opFactory operation.Factory, -) (operation.Operation, error) { +) (_ operation.Operation, err error) { + badge := "" + defer func() { + if err != nil && errors.Cause(err) != resolver.ErrNoOperation && err != resolver.ErrRestart { + s.config.Logger.Debugf("next %q operation could not be resolved: %v", badge, err) + } + }() + if remoteState.Life == life.Dead || localState.Removed { return nil, resolver.ErrUnitDead } @@ -71,6 +78,7 @@ func (s *uniterResolver) NextOp( // Operations for series-upgrade need to be resolved early, // in particular because no other operations should be run when the unit // has completed preparation and is waiting for upgrade completion. + badge = "upgrade series" op, err := s.config.UpgradeSeries.NextOp(ctx, localState, remoteState, opFactory) if errors.Cause(err) != resolver.ErrNoOperation { if errors.Cause(err) == resolver.ErrDoNotProceed { @@ -80,12 +88,14 @@ func (s *uniterResolver) NextOp( } // Check if we need to notify the charms because a reboot was detected. + badge = "reboot" op, err = s.config.Reboot.NextOp(ctx, localState, remoteState, opFactory) if errors.Cause(err) != resolver.ErrNoOperation { return op, err } if localState.Kind == operation.Upgrade { + badge = "upgrade" if localState.Conflicted { return s.nextOpConflicted(ctx, localState, remoteState, opFactory) } @@ -109,16 +119,19 @@ func (s *uniterResolver) NextOp( s.retryHookTimerStarted = false } + badge = "relations" op, err = s.config.CreatedRelations.NextOp(ctx, localState, remoteState, opFactory) if errors.Cause(err) != resolver.ErrNoOperation { return op, err } + badge = "leadership" op, err = s.config.Leadership.NextOp(ctx, localState, remoteState, opFactory) if errors.Cause(err) != resolver.ErrNoOperation { return op, err } + badge = "optional" for _, r := range s.config.OptionalResolvers { op, err = r.NextOp(ctx, localState, remoteState, opFactory) if errors.Cause(err) != resolver.ErrNoOperation { @@ -126,21 +139,25 @@ func (s *uniterResolver) NextOp( } } + badge = "secrets" op, err = s.config.Secrets.NextOp(ctx, localState, remoteState, opFactory) if errors.Cause(err) != resolver.ErrNoOperation { return op, err } + badge = "actions" op, err = s.config.Actions.NextOp(ctx, localState, remoteState, opFactory) if errors.Cause(err) != resolver.ErrNoOperation { return op, err } + badge = "commands" op, err = s.config.Commands.NextOp(ctx, localState, remoteState, opFactory) if errors.Cause(err) != resolver.ErrNoOperation { return op, err } + badge = "storage" op, err = s.config.Storage.NextOp(ctx, localState, remoteState, opFactory) if errors.Cause(err) != resolver.ErrNoOperation { return op, err @@ -148,6 +165,7 @@ func (s *uniterResolver) NextOp( // If we are to shut down, we don't want to start running any more queued/pending hooks. if remoteState.Shutdown { + badge = "shutdown" logger.Debugf("unit agent is shutting down, will not run pending/queued hooks") return s.nextOp(ctx, localState, remoteState, opFactory) } @@ -160,10 +178,12 @@ func (s *uniterResolver) NextOp( } switch step { case operation.Pending: + badge = "resolve hook" logger.Infof("awaiting error resolution for %q hook", localState.Hook.Kind) return s.nextOpHookError(ctx, localState, remoteState, opFactory) case operation.Queued: + badge = "queued hook" logger.Infof("found queued %q hook", localState.Hook.Kind) if localState.Hook.Kind == hooks.Install { // Special case: handle install in nextOp, @@ -176,6 +196,7 @@ func (s *uniterResolver) NextOp( // Only check for the wrench if trace logging is enabled. Otherwise, // we'd have to parse the charm url every time just to check to see // if a wrench existed. + badge = "commit hook" if localState.CharmURL != "" && logger.IsTraceEnabled() { // If it's set, the charm url will parse. curl := jujucharm.MustParseURL(localState.CharmURL) @@ -193,6 +214,7 @@ func (s *uniterResolver) NextOp( } case operation.Continue: + badge = "idle" logger.Debugf("no operations in progress; waiting for changes") return s.nextOp(ctx, localState, remoteState, opFactory) diff --git a/juju/api.go b/juju/api.go index 08006446e80..4112de3aa79 100644 --- a/juju/api.go +++ b/juju/api.go @@ -147,40 +147,55 @@ func NewAPIConnection(args NewAPIConnectionParams) (_ api.Connection, err error) } // Process the account details obtained from login. + processAccountDetails( + st.AuthTag(), + apiInfo.Tag, + args.ControllerName, + st.ControllerAccess(), + args.Store, apiInfo.SkipLogin, + ) + + return st, nil +} + +func processAccountDetails(authTag names.Tag, apiInfoTag names.Tag, controllerName string, controllerAccess string, store jujuclient.ClientStore, skipLogin bool) { var accountDetails *jujuclient.AccountDetails - user, ok := st.AuthTag().(names.UserTag) - if !apiInfo.SkipLogin { + var err error + + user, ok := authTag.(names.UserTag) + if !skipLogin { if ok { - if accountDetails, err = args.Store.AccountDetails(args.ControllerName); err != nil { + if accountDetails, err = store.AccountDetails(controllerName); err != nil { if !errors.Is(err, errors.NotFound) { logger.Errorf("cannot load local account information: %v", err) } } else { - accountDetails.LastKnownAccess = st.ControllerAccess() + accountDetails.LastKnownAccess = controllerAccess } } accountType := jujuclient.UserPassAccountDetailsType if accountDetails != nil { accountType = accountDetails.Type } - if ok && !user.IsLocal() && apiInfo.Tag == nil && accountType != jujuclient.OAuth2DeviceFlowAccountDetailsType { - // We used macaroon auth to login; save the username - // that we've logged in as. - accountDetails = &jujuclient.AccountDetails{ - Type: jujuclient.UserPassAccountDetailsType, - User: user.Id(), - LastKnownAccess: st.ControllerAccess(), + if accountType == "" || accountType == jujuclient.UserPassAccountDetailsType { + if ok && !user.IsLocal() && apiInfoTag == nil { + // We used macaroon auth to login; save the username + // that we've logged in as. + accountDetails = &jujuclient.AccountDetails{ + Type: jujuclient.UserPassAccountDetailsType, + User: user.Id(), + LastKnownAccess: controllerAccess, + } + } else if apiInfoTag == nil { + logger.Errorf("unexpected logged-in username %v", authTag) } - } else if apiInfo.Tag == nil { - logger.Errorf("unexpected logged-in username %v", st.AuthTag()) } } if accountDetails != nil { - if err := args.Store.UpdateAccount(args.ControllerName, *accountDetails); err != nil { + if err := store.UpdateAccount(controllerName, *accountDetails); err != nil { logger.Errorf("cannot update account information: %v", err) } } - return st, nil } // connectionInfo returns connection information suitable for diff --git a/juju/api_test.go b/juju/api_test.go index e4aa265d829..a0f7d9d6f32 100644 --- a/juju/api_test.go +++ b/juju/api_test.go @@ -8,6 +8,7 @@ import ( "crypto/tls" "fmt" "net" + "sync" "time" "github.com/juju/errors" @@ -545,3 +546,103 @@ type ipAddrResolverFunc func(ctx context.Context, host string) ([]net.IPAddr, er func (f ipAddrResolverFunc) LookupIPAddr(ctx context.Context, host string) ([]net.IPAddr, error) { return f(ctx, host) } + +func (s *NewAPIClientSuite) TestProcessAccountDetails(c *gc.C) { + tests := []struct { + authTag names.Tag + apiInfoTag names.Tag + controllerName string + controllerAccess string + skipLogin bool + accountDetails *jujuclient.AccountDetails + expectedAccountDetails *jujuclient.AccountDetails + }{{ + authTag: names.NewMachineTag("machine-1"), + controllerName: "test-controller", + skipLogin: false, + expectedAccountDetails: nil, + }, { + authTag: names.NewUserTag("eve"), + controllerName: "test-controller", + controllerAccess: "superuser", + skipLogin: false, + accountDetails: &jujuclient.AccountDetails{ + Type: jujuclient.UserPassAccountDetailsType, + User: "eve", + Password: "test-password", + }, + expectedAccountDetails: &jujuclient.AccountDetails{ + Type: jujuclient.UserPassAccountDetailsType, + User: "eve", + Password: "test-password", + LastKnownAccess: "superuser", + }, + }, { + authTag: names.NewUserTag("eve@external"), + controllerName: "test-controller", + controllerAccess: "superuser", + skipLogin: false, + accountDetails: &jujuclient.AccountDetails{}, + expectedAccountDetails: &jujuclient.AccountDetails{ + Type: jujuclient.UserPassAccountDetailsType, + User: "eve@external", + LastKnownAccess: "superuser", + }, + }, { + authTag: names.NewUserTag("eve@external"), + controllerName: "test-controller", + controllerAccess: "superuser", + skipLogin: false, + accountDetails: &jujuclient.AccountDetails{ + Type: jujuclient.OAuth2DeviceFlowAccountDetailsType, + SessionToken: "test-session-token", + }, + expectedAccountDetails: &jujuclient.AccountDetails{ + Type: jujuclient.OAuth2DeviceFlowAccountDetailsType, + SessionToken: "test-session-token", + LastKnownAccess: "superuser", + }, + }} + + for i, test := range tests { + c.Logf("running test case %d", i) + store := &testClientStore{ + accountDetails: map[string]*jujuclient.AccountDetails{ + test.controllerName: test.accountDetails, + }, + } + + juju.ProcessAccountDetails(test.authTag, test.apiInfoTag, test.controllerName, test.controllerAccess, store, test.skipLogin) + + c.Assert(store.accountDetails[test.controllerName], gc.DeepEquals, test.expectedAccountDetails) + } + +} + +type testClientStore struct { + jujuclient.ClientStore + + mu sync.RWMutex + accountDetails map[string]*jujuclient.AccountDetails +} + +func (s *testClientStore) AccountDetails(controllerName string) (*jujuclient.AccountDetails, error) { + s.mu.RLock() + defer s.mu.RUnlock() + + if s.accountDetails[controllerName] == nil { + return nil, errors.NotFound + } + return s.accountDetails[controllerName], nil +} + +func (s *testClientStore) UpdateAccount(controllerName string, accountDetails jujuclient.AccountDetails) error { + s.mu.Lock() + defer s.mu.Unlock() + + if s.accountDetails == nil { + return errors.NotImplemented + } + s.accountDetails[controllerName] = &accountDetails + return nil +} diff --git a/juju/export_test.go b/juju/export_test.go index 6daa27d12b2..75177e59e29 100644 --- a/juju/export_test.go +++ b/juju/export_test.go @@ -4,5 +4,6 @@ package juju var ( - ConnectionInfo = connectionInfo + ConnectionInfo = connectionInfo + ProcessAccountDetails = processAccountDetails ) diff --git a/tests/includes/wait-for.sh b/tests/includes/wait-for.sh index 668baaf32c0..6a6ac15ecb0 100644 --- a/tests/includes/wait-for.sh +++ b/tests/includes/wait-for.sh @@ -331,3 +331,50 @@ wait_for_storage() { sleep "${SHORT_TIMEOUT}" fi } + +# wait_for_aws_ingress_cidrs_for_port_range blocks until the expected CIDRs +# are present in the AWS security group rules for the specified port range. +wait_for_aws_ingress_cidrs_for_port_range() { + local from_port to_port exp_cidrs cidr_type + + from_port=${1} + to_port=${2} + exp_cidrs=${3} + cidr_type=${4} + + ipV6Suffix="" + if [ "$cidr_type" = "ipv6" ]; then + ipV6Suffix="v6" + fi + + # shellcheck disable=SC2086 + secgrp_list=$(aws ec2 describe-security-groups --filters Name=ip-permission.from-port,Values=${from_port} Name=ip-permission.to-port,Values=${to_port}) + # print the security group rules + # shellcheck disable=SC2086 + got_cidrs=$(echo ${secgrp_list} | jq -r ".SecurityGroups[0].IpPermissions | .[] | select(.FromPort == ${from_port} and .ToPort == ${to_port}) | .Ip${ipV6Suffix}Ranges | .[] | .CidrIp${ipV6Suffix}" | sort | paste -sd, -) + + attempt=0 + # shellcheck disable=SC2046,SC2143 + while [ "$attempt" -lt "3" ]; do + echo "[+] (attempt ${attempt}) polling security group rules" + # shellcheck disable=SC2086 + secgrp_list=$(aws ec2 describe-security-groups --filters Name=ip-permission.from-port,Values=${from_port} Name=ip-permission.to-port,Values=${to_port}) + # shellcheck disable=SC2086 + got_cidrs=$(echo ${secgrp_list} | jq -r ".SecurityGroups[0].IpPermissions | .[] | select(.FromPort == ${from_port} and .ToPort == ${to_port}) | .Ip${ipV6Suffix}Ranges | .[] | .CidrIp${ipV6Suffix}" | sort | paste -sd, -) + sleep "${SHORT_TIMEOUT}" + + if [ "$got_cidrs" == "$exp_cidrs" ]; then + break + fi + + attempt=$((attempt + 1)) + done + + if [ "$got_cidrs" != "$exp_cidrs" ]; then + # shellcheck disable=SC2046 + echo $(red "expected generated EC2 ${cidr_type} ingress CIDRs for range [${from_port}, ${to_port}] to be:\n${exp_cidrs}\nGOT:\n${got_cidrs}") + exit 1 + fi + + echo "[+] security group rules for port range [${from_port}, ${to_port}] and CIDRs ${exp_cidrs} updated" +} diff --git a/tests/suites/deploy/bundles/telegraf_bundle.yaml b/tests/suites/deploy/bundles/telegraf_bundle.yaml index 4c47504ff1a..8ddb42af223 100644 --- a/tests/suites/deploy/bundles/telegraf_bundle.yaml +++ b/tests/suites/deploy/bundles/telegraf_bundle.yaml @@ -2,7 +2,7 @@ default-base: ubuntu@20.04/stable applications: influxdb: charm: influxdb - channel: stable + channel: latest/stable revision: 24 num_units: 1 to: @@ -10,11 +10,11 @@ applications: constraints: arch=amd64 telegraf: charm: telegraf - channel: stable + channel: latest/stable revision: 53 juju-qa-test: charm: juju-qa-test - channel: stable + channel: latest/stable revision: 19 resources: foo-file: 2 diff --git a/tests/suites/deploy/bundles/telegraf_bundle_with_fake_revisions.yaml b/tests/suites/deploy/bundles/telegraf_bundle_with_fake_revisions.yaml index e1370710757..1773f6e5590 100644 --- a/tests/suites/deploy/bundles/telegraf_bundle_with_fake_revisions.yaml +++ b/tests/suites/deploy/bundles/telegraf_bundle_with_fake_revisions.yaml @@ -2,7 +2,7 @@ default-base: ubuntu@20.04/stable applications: influxdb: charm: influxdb - channel: stable + channel: latest/stable revision: -1 num_units: 1 to: @@ -10,11 +10,11 @@ applications: constraints: arch=amd64 telegraf: charm: telegraf - channel: stable + channel: latest/stable revision: -1 juju-qa-test: charm: juju-qa-test - channel: candidate + channel: latest/candidate revision: -1 resources: foo-file: 2 diff --git a/tests/suites/deploy/bundles/telegraf_bundle_without_revisions.yaml b/tests/suites/deploy/bundles/telegraf_bundle_without_revisions.yaml index 5d5a22339c1..db1920728a1 100644 --- a/tests/suites/deploy/bundles/telegraf_bundle_without_revisions.yaml +++ b/tests/suites/deploy/bundles/telegraf_bundle_without_revisions.yaml @@ -2,17 +2,17 @@ default-base: ubuntu@20.04/stable applications: influxdb: charm: influxdb - channel: stable + channel: latest/stable num_units: 1 to: - "0" constraints: arch=amd64 telegraf: charm: telegraf - channel: stable + channel: latest/stable juju-qa-test: charm: juju-qa-test - channel: candidate + channel: latest/candidate num_units: 1 resources: foo-file: 2 diff --git a/tests/suites/firewall/expose_app.sh b/tests/suites/firewall/expose_app.sh index 08084aafd30..2be6e9eb248 100644 --- a/tests/suites/firewall/expose_app.sh +++ b/tests/suites/firewall/expose_app.sh @@ -54,50 +54,15 @@ assert_ingress_cidrs_for_exposed_app() { juju expose ubuntu-lite --endpoints ubuntu # expose to the world # overwrite previous command juju expose ubuntu-lite --endpoints ubuntu --to-cidrs 10.42.0.0/16,2002:0:0:1234::/64 - sleep 2 # wait for firewall worker to detect and apply the changes + echo "==> Waiting for the security group rules will be updated" # Range 1337-1339 is opened for all endpoints. We expect it to be reachable # by the expose-all CIDR list plus the CIDR for the ubuntu endpoint. - assert_ipv4_ingress_cidrs_for_port_range "1337" "1339" "10.0.0.0/24,10.42.0.0/16,192.168.0.0/24" + wait_for_aws_ingress_cidrs_for_port_range "1337" "1339" "10.0.0.0/24,10.42.0.0/16,192.168.0.0/24" "ipv4" # Port 1234 should only be opened for the CIDR specified for the ubuntu endpoint - assert_ipv4_ingress_cidrs_for_port_range "1234" "1234" "10.42.0.0/16" - assert_ipv6_ingress_cidrs_for_port_range "1234" "1234" "2002:0:0:1234::/64" -} - -# assert_ipv4_ingress_cidrs_for_port_range $from_port, $to_port $exp_cidrs -assert_ipv4_ingress_cidrs_for_port_range() { - assert_ingress_cidrs_for_port_range "$1" "$2" "$3" "ipv4" -} - -# assert_ipv6_ingress_cidrs_for_port_range $from_port, $to_port $exp_cidrs -assert_ipv6_ingress_cidrs_for_port_range() { - assert_ingress_cidrs_for_port_range "$1" "$2" "$3" "ipv6" -} - -assert_ingress_cidrs_for_port_range() { - local from_port to_port exp_cidrs cidr_type - - from_port=${1} - to_port=${2} - exp_cidrs=${3} - cidr_type=${4} - - # shellcheck disable=SC2086 - secgrp_list=$(aws ec2 describe-security-groups --filters Name=ip-permission.from-port,Values=${from_port} Name=ip-permission.to-port,Values=${to_port}) - if [ "$cidr_type" = "ipv4" ]; then - # shellcheck disable=SC2086 - got_cidrs=$(echo ${secgrp_list} | jq -r ".SecurityGroups[0].IpPermissions | .[] | select(.FromPort == ${from_port} and .ToPort == ${to_port}) | .IpRanges | .[] | .CidrIp" | sort | paste -sd, -) - else - # shellcheck disable=SC2086 - got_cidrs=$(echo ${secgrp_list} | jq -r ".SecurityGroups[0].IpPermissions | .[] | select(.FromPort == ${from_port} and .ToPort == ${to_port}) | .Ipv6Ranges | .[] | .CidrIpv6" | sort | paste -sd, -) - fi - - if [ "$got_cidrs" != "$exp_cidrs" ]; then - # shellcheck disable=SC2046 - echo $(red "expected generated EC2 ${cidr_type} ingress CIDRs for range [${from_port}, ${to_port}] to be:\n${exp_cidrs}\nGOT:\n${got_cidrs}") - exit 1 - fi + wait_for_aws_ingress_cidrs_for_port_range "1234" "1234" "10.42.0.0/16" "ipv4" + wait_for_aws_ingress_cidrs_for_port_range "1234" "1234" "2002:0:0:1234::/64" "ipv6" } assert_export_bundle_output_includes_exposed_endpoints() { diff --git a/tests/suites/refresh/refresh.sh b/tests/suites/refresh/refresh.sh index dbe66ad6612..dad760d1a53 100644 --- a/tests/suites/refresh/refresh.sh +++ b/tests/suites/refresh/refresh.sh @@ -104,7 +104,7 @@ run_refresh_channel_no_new_revision() { juju refresh juju-qa-fixed-rev --channel edge - wait_for "juju-qa-fixed-rev" "$(charm_channel "juju-qa-fixed-rev" "edge")" + wait_for "juju-qa-fixed-rev" "$(charm_channel "juju-qa-fixed-rev" "latest/edge")" wait_for "juju-qa-fixed-rev" "$(charm_rev "juju-qa-fixed-rev" "${cs_revision}")" wait_for "juju-qa-fixed-rev" "$(idle_condition "juju-qa-fixed-rev")" @@ -126,7 +126,7 @@ run_refresh_revision() { # refresh to a revision not at the tip of the stable channel juju refresh juju-qa-test --revision 23 wait_for "juju-qa-test" "$(charm_rev "juju-qa-test" "23")" - wait_for "juju-qa-test" "$(charm_channel "juju-qa-test" "stable")" + wait_for "juju-qa-test" "$(charm_channel "juju-qa-test" "latest/stable")" wait_for "juju-qa-test" "$(idle_condition "juju-qa-test")" # do a generic refresh, should pick up revision from latest stable @@ -138,7 +138,7 @@ run_refresh_revision() { revision=$(echo "${OUT}" | awk 'BEGIN{FS=","} {print $2}' | awk 'BEGIN{FS=" "} {print $2}') wait_for "juju-qa-test" "$(charm_rev "juju-qa-test" "${revision}")" - wait_for "juju-qa-test" "$(charm_channel "juju-qa-test" "stable")" + wait_for "juju-qa-test" "$(charm_channel "juju-qa-test" "latest/stable")" wait_for "juju-qa-test" "$(idle_condition "juju-qa-test")" destroy_model "${model_name}" diff --git a/tests/suites/refresh/switch.sh b/tests/suites/refresh/switch.sh index 33c17675332..16263d49291 100644 --- a/tests/suites/refresh/switch.sh +++ b/tests/suites/refresh/switch.sh @@ -25,7 +25,7 @@ run_refresh_switch_local_to_ch_channel() { revision=$(echo "${OUT}" | awk 'BEGIN{FS=","} {print $2}' | awk 'BEGIN{FS=" "} {print $2}') wait_for "ubuntu" "$(charm_rev "ubuntu" "${revision}")" - wait_for "ubuntu" "$(charm_channel "ubuntu" "edge")" + wait_for "ubuntu" "$(charm_channel "ubuntu" "latest/edge")" wait_for "ubuntu" "$(idle_condition "ubuntu")" destroy_model "${model_name}" diff --git a/tests/suites/secrets_k8s/k8s.sh b/tests/suites/secrets_k8s/k8s.sh index 2b7e4182a7b..a488a9f94c8 100644 --- a/tests/suites/secrets_k8s/k8s.sh +++ b/tests/suites/secrets_k8s/k8s.sh @@ -7,7 +7,10 @@ run_secrets() { juju --show-log add-model "$model_name" --config secret-backend=auto juju --show-log deploy hello-kubecon hello - juju --show-log deploy nginx-ingress-integrator nginx + # TODO(anvial): remove the revision flag once we update hello-kubecon charm + # (https://discourse.charmhub.io/t/old-ingress-relation-removal/12944) + # or we choose an alternative pair of charms to integrate. + juju --show-log deploy nginx-ingress-integrator nginx --channel=latest/stable --revision=83 juju --show-log integrate nginx hello juju --show-log trust nginx --scope=cluster