github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/client/modelconfig/modelconfig_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package modelconfig_test
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/names/v5"
     9  	"github.com/juju/testing"
    10  	jc "github.com/juju/testing/checkers"
    11  	"go.uber.org/mock/gomock"
    12  	gc "gopkg.in/check.v1"
    13  
    14  	commonsecrets "github.com/juju/juju/apiserver/common/secrets"
    15  	"github.com/juju/juju/apiserver/facades/client/modelconfig"
    16  	"github.com/juju/juju/apiserver/facades/client/modelconfig/mocks"
    17  	apiservertesting "github.com/juju/juju/apiserver/testing"
    18  	"github.com/juju/juju/core/constraints"
    19  	coresecrets "github.com/juju/juju/core/secrets"
    20  	"github.com/juju/juju/environs/config"
    21  	"github.com/juju/juju/feature"
    22  	"github.com/juju/juju/provider/dummy"
    23  	"github.com/juju/juju/rpc/params"
    24  	secretsprovider "github.com/juju/juju/secrets/provider"
    25  	"github.com/juju/juju/state"
    26  	coretesting "github.com/juju/juju/testing"
    27  )
    28  
    29  type modelconfigSuite struct {
    30  	testing.IsolationSuite
    31  	coretesting.JujuOSEnvSuite
    32  	backend    *mockBackend
    33  	authorizer apiservertesting.FakeAuthorizer
    34  	api        *modelconfig.ModelConfigAPIV3
    35  }
    36  
    37  var _ = gc.Suite(&modelconfigSuite{})
    38  
    39  func (s *modelconfigSuite) SetUpTest(c *gc.C) {
    40  	s.SetInitialFeatureFlags(feature.DeveloperMode)
    41  	s.IsolationSuite.SetUpTest(c)
    42  	s.JujuOSEnvSuite.SetUpTest(c)
    43  	s.authorizer = apiservertesting.FakeAuthorizer{
    44  		Tag:      names.NewUserTag("bruce@local"),
    45  		AdminTag: names.NewUserTag("bruce@local"),
    46  	}
    47  	s.backend = &mockBackend{
    48  		cfg: config.ConfigValues{
    49  			"type":            {Value: "dummy", Source: "model"},
    50  			"agent-version":   {Value: "1.2.3.4", Source: "model"},
    51  			"ftp-proxy":       {Value: "http://proxy", Source: "model"},
    52  			"authorized-keys": {Value: coretesting.FakeAuthKeys, Source: "model"},
    53  			"charmhub-url":    {Value: "http://meshuggah.rocks", Source: "model"},
    54  		},
    55  		secretBackend: &coresecrets.SecretBackend{
    56  			ID:          "backend-1",
    57  			Name:        "backend-1",
    58  			BackendType: "vault",
    59  			Config: map[string]interface{}{
    60  				"endpoint": "http://0.0.0.0:8200",
    61  			},
    62  		},
    63  	}
    64  	var err error
    65  	s.api, err = modelconfig.NewModelConfigAPI(s.backend, &s.authorizer)
    66  	c.Assert(err, jc.ErrorIsNil)
    67  }
    68  
    69  func (s *modelconfigSuite) TestAdminModelGet(c *gc.C) {
    70  	result, err := s.api.ModelGet()
    71  	c.Assert(err, jc.ErrorIsNil)
    72  	c.Assert(result.Config, jc.DeepEquals, map[string]params.ConfigValue{
    73  		"type":           {Value: "dummy", Source: "model"},
    74  		"ftp-proxy":      {Value: "http://proxy", Source: "model"},
    75  		"agent-version":  {Value: "1.2.3.4", Source: "model"},
    76  		"charmhub-url":   {Value: "http://meshuggah.rocks", Source: "model"},
    77  		"default-series": {Value: "", Source: "default"},
    78  	})
    79  }
    80  
    81  func (s *modelconfigSuite) TestUserModelGet(c *gc.C) {
    82  	s.authorizer = apiservertesting.FakeAuthorizer{
    83  		Tag:         names.NewUserTag("bruce@local"),
    84  		HasWriteTag: names.NewUserTag("bruce@local"),
    85  		AdminTag:    names.NewUserTag("mary@local"),
    86  	}
    87  	result, err := s.api.ModelGet()
    88  	c.Assert(err, jc.ErrorIsNil)
    89  	c.Assert(result.Config, jc.DeepEquals, map[string]params.ConfigValue{
    90  		"type":           {Value: "dummy", Source: "model"},
    91  		"ftp-proxy":      {Value: "http://proxy", Source: "model"},
    92  		"agent-version":  {Value: "1.2.3.4", Source: "model"},
    93  		"charmhub-url":   {Value: "http://meshuggah.rocks", Source: "model"},
    94  		"default-series": {Value: "", Source: "default"},
    95  	})
    96  }
    97  
    98  func (s *modelconfigSuite) assertConfigValue(c *gc.C, key string, expected interface{}) {
    99  	value, found := s.backend.cfg[key]
   100  	c.Assert(found, jc.IsTrue)
   101  	c.Assert(value.Value, gc.Equals, expected)
   102  }
   103  
   104  func (s *modelconfigSuite) assertConfigValueMissing(c *gc.C, key string) {
   105  	_, found := s.backend.cfg[key]
   106  	c.Assert(found, jc.IsFalse)
   107  }
   108  
   109  func (s *modelconfigSuite) TestAdminModelSet(c *gc.C) {
   110  	params := params.ModelSet{
   111  		Config: map[string]interface{}{
   112  			"some-key":  "value",
   113  			"other-key": "other value",
   114  		},
   115  	}
   116  	err := s.api.ModelSet(params)
   117  	c.Assert(err, jc.ErrorIsNil)
   118  	s.assertConfigValue(c, "some-key", "value")
   119  	s.assertConfigValue(c, "other-key", "other value")
   120  }
   121  
   122  func (s *modelconfigSuite) blockAllChanges(c *gc.C, msg string) {
   123  	s.backend.msg = msg
   124  	s.backend.b = state.ChangeBlock
   125  }
   126  
   127  func (s *modelconfigSuite) assertBlocked(c *gc.C, err error, msg string) {
   128  	c.Assert(params.IsCodeOperationBlocked(err), jc.IsTrue, gc.Commentf("error: %#v", err))
   129  	c.Assert(errors.Cause(err), jc.DeepEquals, &params.Error{
   130  		Message: msg,
   131  		Code:    "operation is blocked",
   132  	})
   133  }
   134  
   135  func (s *modelconfigSuite) assertModelSetBlocked(c *gc.C, args map[string]interface{}, msg string) {
   136  	err := s.api.ModelSet(params.ModelSet{Config: args})
   137  	s.assertBlocked(c, err, msg)
   138  }
   139  
   140  func (s *modelconfigSuite) TestBlockChangesModelSet(c *gc.C) {
   141  	s.blockAllChanges(c, "TestBlockChangesModelSet")
   142  	args := map[string]interface{}{"some-key": "value"}
   143  	s.assertModelSetBlocked(c, args, "TestBlockChangesModelSet")
   144  }
   145  
   146  func (s *modelconfigSuite) TestModelSetCannotChangeAgentVersion(c *gc.C) {
   147  	old, err := config.New(config.UseDefaults, dummy.SampleConfig().Merge(coretesting.Attrs{
   148  		"agent-version": "1.2.3.4",
   149  	}))
   150  	c.Assert(err, jc.ErrorIsNil)
   151  	s.backend.old = old
   152  	args := params.ModelSet{
   153  		Config: map[string]interface{}{"agent-version": "9.9.9"},
   154  	}
   155  	err = s.api.ModelSet(args)
   156  	c.Assert(err, gc.ErrorMatches, "agent-version cannot be changed")
   157  
   158  	// It's okay to pass config back with the same agent-version.
   159  	result, err := s.api.ModelGet()
   160  	c.Assert(err, jc.ErrorIsNil)
   161  	c.Assert(result.Config["agent-version"], gc.NotNil)
   162  	args.Config["agent-version"] = result.Config["agent-version"].Value
   163  	err = s.api.ModelSet(args)
   164  	c.Assert(err, jc.ErrorIsNil)
   165  }
   166  
   167  func (s *modelconfigSuite) TestModelSetCannotChangeCharmHubURL(c *gc.C) {
   168  	old, err := config.New(config.UseDefaults, dummy.SampleConfig().Merge(coretesting.Attrs{
   169  		"charmhub-url": "http://meshuggah.rocks",
   170  	}))
   171  	c.Assert(err, jc.ErrorIsNil)
   172  	s.backend.old = old
   173  	args := params.ModelSet{
   174  		Config: map[string]interface{}{"charmhub-url": "http://another-url.com"},
   175  	}
   176  	err = s.api.ModelSet(args)
   177  	c.Assert(err, gc.ErrorMatches, "charmhub-url cannot be changed")
   178  
   179  	// It's okay to pass config back with the same charmhub-url.
   180  	result, err := s.api.ModelGet()
   181  	c.Assert(err, jc.ErrorIsNil)
   182  	c.Assert(result.Config["charmhub-url"], gc.NotNil)
   183  	args.Config["charmhub-url"] = result.Config["charmhub-url"].Value
   184  	err = s.api.ModelSet(args)
   185  	c.Assert(err, jc.ErrorIsNil)
   186  }
   187  
   188  func (s *modelconfigSuite) TestModelSetCannotChangeBothDefaultSeriesAndDefaultBaseWithSeries(c *gc.C) {
   189  	old, err := config.New(config.UseDefaults, dummy.SampleConfig().Merge(coretesting.Attrs{
   190  		"default-series": "jammy",
   191  	}))
   192  	c.Assert(err, jc.ErrorIsNil)
   193  
   194  	s.backend.old = old
   195  	args := params.ModelSet{
   196  		Config: map[string]interface{}{
   197  			"default-series": "jammy",
   198  			"default-base":   "ubuntu@22.04",
   199  		},
   200  	}
   201  	err = s.api.ModelSet(args)
   202  	c.Assert(err, gc.ErrorMatches, "cannot set both default-series and default-base")
   203  
   204  	err = s.api.ModelSet(params.ModelSet{
   205  		Config: map[string]interface{}{
   206  			"default-series": "jammy",
   207  		},
   208  	})
   209  	c.Assert(err, jc.ErrorIsNil)
   210  
   211  	result, err := s.api.ModelGet()
   212  	c.Assert(err, jc.ErrorIsNil)
   213  	c.Assert(result.Config["default-series"], gc.NotNil)
   214  	c.Assert(result.Config["default-series"].Value, gc.Equals, "jammy")
   215  	c.Assert(result.Config["default-base"].Value, gc.Equals, "ubuntu@22.04/stable")
   216  }
   217  
   218  func (s *modelconfigSuite) TestModelSetCannotChangeBothDefaultSeriesAndDefaultBaseWithBase(c *gc.C) {
   219  	old, err := config.New(config.UseDefaults, dummy.SampleConfig().Merge(coretesting.Attrs{
   220  		"default-base": "ubuntu@22.04",
   221  	}))
   222  	c.Assert(err, jc.ErrorIsNil)
   223  
   224  	s.backend.old = old
   225  	args := params.ModelSet{
   226  		Config: map[string]interface{}{
   227  			"default-series": "jammy",
   228  			"default-base":   "ubuntu@22.04",
   229  		},
   230  	}
   231  	err = s.api.ModelSet(args)
   232  	c.Assert(err, gc.ErrorMatches, "cannot set both default-series and default-base")
   233  
   234  	err = s.api.ModelSet(params.ModelSet{
   235  		Config: map[string]interface{}{
   236  			"default-series": "jammy",
   237  		},
   238  	})
   239  	c.Assert(err, jc.ErrorIsNil)
   240  
   241  	result, err := s.api.ModelGet()
   242  	c.Assert(err, jc.ErrorIsNil)
   243  	c.Assert(result.Config["default-series"], gc.NotNil)
   244  	c.Assert(result.Config["default-series"].Value, gc.Equals, "jammy")
   245  	c.Assert(result.Config["default-base"].Value, gc.Equals, "ubuntu@22.04/stable")
   246  }
   247  
   248  func (s *modelconfigSuite) TestModelSetCannotSetAuthorizedKeys(c *gc.C) {
   249  	// Try to set the authorized-keys model config.
   250  	args := params.ModelSet{
   251  		Config: map[string]interface{}{"authorized-keys": "ssh-rsa new Juju:juju-client-key"},
   252  	}
   253  	err := s.api.ModelSet(args)
   254  	c.Assert(err, gc.ErrorMatches, "authorized-keys cannot be set")
   255  	// Make sure the authorized-keys still contains its original value.
   256  	s.assertConfigValue(c, "authorized-keys", coretesting.FakeAuthKeys)
   257  }
   258  
   259  func (s *modelconfigSuite) TestAdminCanSetLogTrace(c *gc.C) {
   260  	args := params.ModelSet{
   261  		Config: map[string]interface{}{"logging-config": "<root>=DEBUG;somepackage=TRACE"},
   262  	}
   263  	err := s.api.ModelSet(args)
   264  	c.Assert(err, jc.ErrorIsNil)
   265  
   266  	result, err := s.api.ModelGet()
   267  	c.Assert(err, jc.ErrorIsNil)
   268  	c.Assert(result.Config["logging-config"].Value, gc.Equals, "<root>=DEBUG;somepackage=TRACE")
   269  }
   270  
   271  func (s *modelconfigSuite) TestUserCanSetLogNoTrace(c *gc.C) {
   272  	args := params.ModelSet{
   273  		Config: map[string]interface{}{"logging-config": "<root>=DEBUG;somepackage=ERROR"},
   274  	}
   275  	apiUser := names.NewUserTag("fred")
   276  	s.authorizer.Tag = apiUser
   277  	s.authorizer.HasWriteTag = apiUser
   278  	err := s.api.ModelSet(args)
   279  	c.Assert(err, jc.ErrorIsNil)
   280  
   281  	result, err := s.api.ModelGet()
   282  	c.Assert(err, jc.ErrorIsNil)
   283  	c.Assert(result.Config["logging-config"].Value, gc.Equals, "<root>=DEBUG;somepackage=ERROR")
   284  }
   285  
   286  func (s *modelconfigSuite) TestUserReadAccess(c *gc.C) {
   287  	apiUser := names.NewUserTag("read")
   288  	s.authorizer.Tag = apiUser
   289  
   290  	_, err := s.api.ModelGet()
   291  	c.Assert(err, jc.ErrorIsNil)
   292  
   293  	err = s.api.ModelSet(params.ModelSet{})
   294  	c.Assert(errors.Cause(err), gc.ErrorMatches, "permission denied")
   295  }
   296  
   297  func (s *modelconfigSuite) TestUserCannotSetLogTrace(c *gc.C) {
   298  	args := params.ModelSet{
   299  		Config: map[string]interface{}{"logging-config": "<root>=DEBUG;somepackage=TRACE"},
   300  	}
   301  	apiUser := names.NewUserTag("fred")
   302  	s.authorizer.Tag = apiUser
   303  	s.authorizer.HasWriteTag = apiUser
   304  	err := s.api.ModelSet(args)
   305  	c.Assert(err, gc.ErrorMatches, `only controller admins can set a model's logging level to TRACE`)
   306  }
   307  
   308  func (s *modelconfigSuite) TestSetSecretBackend(c *gc.C) {
   309  	args := params.ModelSet{
   310  		Config: map[string]interface{}{"secret-backend": 1},
   311  	}
   312  	err := s.api.ModelSet(args)
   313  	c.Assert(err, gc.ErrorMatches, `"secret-backend" config value is not a string`)
   314  
   315  	args.Config = map[string]interface{}{"secret-backend": ""}
   316  	err = s.api.ModelSet(args)
   317  	c.Assert(err, gc.ErrorMatches, `empty "secret-backend" config value not valid`)
   318  
   319  	args.Config = map[string]interface{}{"secret-backend": "auto"}
   320  	err = s.api.ModelSet(args)
   321  	c.Assert(err, jc.ErrorIsNil)
   322  	result, err := s.api.ModelGet()
   323  	c.Assert(err, jc.ErrorIsNil)
   324  	c.Assert(result.Config["secret-backend"].Value, gc.Equals, "auto")
   325  }
   326  
   327  func (s *modelconfigSuite) TestSetSecretBackendExternal(c *gc.C) {
   328  	ctrl := gomock.NewController(c)
   329  	defer ctrl.Finish()
   330  
   331  	vaultProvider := mocks.NewMockSecretBackendProvider(ctrl)
   332  	s.PatchValue(&commonsecrets.GetProvider, func(string) (secretsprovider.SecretBackendProvider, error) { return vaultProvider, nil })
   333  	vaultBackend := mocks.NewMockSecretsBackend(ctrl)
   334  
   335  	gomock.InOrder(
   336  		vaultProvider.EXPECT().Type().Return("vault"),
   337  		vaultProvider.EXPECT().NewBackend(&secretsprovider.ModelBackendConfig{
   338  			BackendConfig: secretsprovider.BackendConfig{
   339  				BackendType: "vault",
   340  				Config:      s.backend.secretBackend.Config,
   341  			},
   342  		}).Return(vaultBackend, nil),
   343  		vaultBackend.EXPECT().Ping().Return(nil),
   344  	)
   345  
   346  	args := params.ModelSet{
   347  		Config: map[string]interface{}{"secret-backend": "backend-1"},
   348  	}
   349  	err := s.api.ModelSet(args)
   350  	c.Assert(err, jc.ErrorIsNil)
   351  	result, err := s.api.ModelGet()
   352  	c.Assert(err, jc.ErrorIsNil)
   353  	c.Assert(result.Config["secret-backend"].Value, gc.Equals, "backend-1")
   354  }
   355  
   356  func (s *modelconfigSuite) TestSetSecretBackendExternalValidationFailed(c *gc.C) {
   357  	ctrl := gomock.NewController(c)
   358  	defer ctrl.Finish()
   359  
   360  	vaultProvider := mocks.NewMockSecretBackendProvider(ctrl)
   361  	s.PatchValue(&commonsecrets.GetProvider, func(string) (secretsprovider.SecretBackendProvider, error) { return vaultProvider, nil })
   362  	vaultBackend := mocks.NewMockSecretsBackend(ctrl)
   363  
   364  	gomock.InOrder(
   365  		vaultProvider.EXPECT().Type().Return("vault"),
   366  		vaultProvider.EXPECT().NewBackend(&secretsprovider.ModelBackendConfig{
   367  			BackendConfig: secretsprovider.BackendConfig{
   368  				BackendType: "vault",
   369  				Config:      s.backend.secretBackend.Config,
   370  			},
   371  		}).Return(vaultBackend, nil),
   372  		vaultBackend.EXPECT().Ping().Return(errors.New("not reachable")),
   373  	)
   374  
   375  	args := params.ModelSet{
   376  		Config: map[string]interface{}{"secret-backend": "backend-1"},
   377  	}
   378  	err := s.api.ModelSet(args)
   379  	c.Assert(err, gc.ErrorMatches, `cannot ping backend "backend-1": not reachable`)
   380  }
   381  
   382  func (s *modelconfigSuite) TestModelUnset(c *gc.C) {
   383  	err := s.backend.UpdateModelConfig(map[string]interface{}{"abc": 123}, nil)
   384  	c.Assert(err, jc.ErrorIsNil)
   385  
   386  	args := params.ModelUnset{Keys: []string{"abc"}}
   387  	err = s.api.ModelUnset(args)
   388  	c.Assert(err, jc.ErrorIsNil)
   389  	s.assertConfigValueMissing(c, "abc")
   390  }
   391  
   392  func (s *modelconfigSuite) TestBlockModelUnset(c *gc.C) {
   393  	err := s.backend.UpdateModelConfig(map[string]interface{}{"abc": 123}, nil)
   394  	c.Assert(err, jc.ErrorIsNil)
   395  	s.blockAllChanges(c, "TestBlockModelUnset")
   396  
   397  	args := params.ModelUnset{Keys: []string{"abc"}}
   398  	err = s.api.ModelUnset(args)
   399  	s.assertBlocked(c, err, "TestBlockModelUnset")
   400  }
   401  
   402  func (s *modelconfigSuite) TestModelUnsetMissing(c *gc.C) {
   403  	// It's okay to unset a non-existent attribute.
   404  	args := params.ModelUnset{Keys: []string{"not_there"}}
   405  	err := s.api.ModelUnset(args)
   406  	c.Assert(err, jc.ErrorIsNil)
   407  }
   408  
   409  func (s *modelconfigSuite) TestSetSupportCredentals(c *gc.C) {
   410  	err := s.api.SetSLALevel(params.ModelSLA{
   411  		ModelSLAInfo: params.ModelSLAInfo{Level: "level", Owner: "bob"},
   412  		Credentials:  []byte("foobar"),
   413  	})
   414  	c.Assert(err, jc.ErrorIsNil)
   415  }
   416  
   417  func (s *modelconfigSuite) TestClientSetModelConstraints(c *gc.C) {
   418  	// Set constraints for the model.
   419  	cons, err := constraints.Parse("mem=4096", "cores=2")
   420  	c.Assert(err, jc.ErrorIsNil)
   421  	err = s.api.SetModelConstraints(params.SetConstraints{
   422  		ApplicationName: "app",
   423  		Constraints:     cons,
   424  	})
   425  	c.Assert(err, jc.ErrorIsNil)
   426  	c.Assert(s.backend.cons, gc.DeepEquals, cons)
   427  }
   428  
   429  func (s *modelconfigSuite) assertSetModelConstraintsBlocked(c *gc.C, msg string) {
   430  	// Set constraints for the model.
   431  	cons, err := constraints.Parse("mem=4096", "cores=2")
   432  	c.Assert(err, jc.ErrorIsNil)
   433  	err = s.api.SetModelConstraints(params.SetConstraints{
   434  		ApplicationName: "app",
   435  		Constraints:     cons,
   436  	})
   437  	s.assertBlocked(c, err, msg)
   438  }
   439  
   440  func (s *modelconfigSuite) TestBlockChangesClientSetModelConstraints(c *gc.C) {
   441  	s.blockAllChanges(c, "TestBlockChangesClientSetModelConstraints")
   442  	s.assertSetModelConstraintsBlocked(c, "TestBlockChangesClientSetModelConstraints")
   443  }
   444  
   445  func (s *modelconfigSuite) TestClientGetModelConstraints(c *gc.C) {
   446  	// Set constraints for the model.
   447  	cons, err := constraints.Parse("mem=4096", "cores=2")
   448  	c.Assert(err, jc.ErrorIsNil)
   449  	s.backend.cons = cons
   450  	obtained, err := s.api.GetModelConstraints()
   451  	c.Assert(err, jc.ErrorIsNil)
   452  	c.Assert(obtained.Constraints, gc.DeepEquals, cons)
   453  }
   454  
   455  type mockBackend struct {
   456  	cfg           config.ConfigValues
   457  	old           *config.Config
   458  	b             state.BlockType
   459  	msg           string
   460  	cons          constraints.Value
   461  	secretBackend *coresecrets.SecretBackend
   462  }
   463  
   464  func (m *mockBackend) SetModelConstraints(value constraints.Value) error {
   465  	m.cons = value
   466  	return nil
   467  }
   468  
   469  func (m *mockBackend) ModelConstraints() (constraints.Value, error) {
   470  	return m.cons, nil
   471  }
   472  
   473  func (m *mockBackend) ModelConfigValues() (config.ConfigValues, error) {
   474  	return m.cfg, nil
   475  }
   476  
   477  func (m *mockBackend) Sequences() (map[string]int, error) {
   478  	return nil, nil
   479  }
   480  
   481  func (m *mockBackend) UpdateModelConfig(update map[string]interface{}, remove []string,
   482  	validate ...state.ValidateConfigFunc) error {
   483  	for _, validateFunc := range validate {
   484  		if err := validateFunc(update, remove, m.old); err != nil {
   485  			return err
   486  		}
   487  	}
   488  	for k, v := range update {
   489  		m.cfg[k] = config.ConfigValue{Value: v, Source: "model"}
   490  	}
   491  	for _, n := range remove {
   492  		delete(m.cfg, n)
   493  	}
   494  	return nil
   495  }
   496  
   497  func (m *mockBackend) GetBlockForType(t state.BlockType) (state.Block, bool, error) {
   498  	if m.b == t {
   499  		return &mockBlock{t: t, m: m.msg}, true, nil
   500  	} else {
   501  		return nil, false, nil
   502  	}
   503  }
   504  
   505  func (m *mockBackend) ModelTag() names.ModelTag {
   506  	return names.NewModelTag("deadbeef-2f18-4fd2-967d-db9663db7bea")
   507  }
   508  
   509  func (m *mockBackend) ControllerTag() names.ControllerTag {
   510  	return names.NewControllerTag("deadbeef-babe-4fd2-967d-db9663db7bea")
   511  }
   512  
   513  func (m *mockBackend) SetSLA(level, owner string, credentials []byte) error {
   514  	return nil
   515  }
   516  
   517  func (m *mockBackend) SLALevel() (string, error) {
   518  	return "mock-level", nil
   519  }
   520  
   521  func (m *mockBackend) SpaceByName(string) error {
   522  	return nil
   523  }
   524  
   525  func (m *mockBackend) GetSecretBackend(name string) (*coresecrets.SecretBackend, error) {
   526  	if name == "invalid" {
   527  		return nil, errors.NotFoundf("invalid")
   528  	}
   529  	return m.secretBackend, nil
   530  }
   531  
   532  type mockBlock struct {
   533  	state.Block
   534  	t state.BlockType
   535  	m string
   536  }
   537  
   538  func (m mockBlock) Id() string { return "" }
   539  
   540  func (m mockBlock) Tag() (names.Tag, error) { return names.NewModelTag("mocktesting"), nil }
   541  
   542  func (m mockBlock) Type() state.BlockType { return m.t }
   543  
   544  func (m mockBlock) Message() string { return m.m }
   545  
   546  func (m mockBlock) ModelUUID() string { return "" }