Skip to content

Commit 423cbf3

Browse files
authored
feat: tweak remove-controller logic (#1893)
* feat: tweak remove-controller logic * test: update integration test
1 parent 26e9f32 commit 423cbf3

File tree

4 files changed

+48
-38
lines changed

4 files changed

+48
-38
lines changed

docs/howto/manage-juju-controllers.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ These options include:
177177
1. Use the `jaas` plugin to first unregister the controller then use the `juju` CLI to destroy it.
178178
2. Use the `jaas` plugin destroy the controller via an API call to JIMM, allowing for greater automation.
179179

180+
Removing a controller can only be done once the controller is not hosting
181+
any models known to JIMM. Migrate or destroy these models before destroying the controller.
182+
180183
````{dropdown} Unregister and destroy
181184
182185
Switch to the JIMM controller and unregister your controller from JIMM:
@@ -196,8 +199,6 @@ juju destroy-controller mycontroller
196199
````{dropdown} JIMM destroy
197200
Switch to the JIMM controller and destroy the controller.
198201
199-
Note that this command will return an error if the controller is still hosting
200-
any models. Migrate or destroy these models before destroying the controller.
201202
```text
202203
juju switch jimm
203204
juju jaas destroy-controller mycontroller

internal/jimm/juju/jimm.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,18 +130,16 @@ func (j *JujuManager) RemoveController(ctx context.Context, user *openfga.User,
130130
return err
131131
}
132132

133-
// if c.UnavailableSince is valid, then we can delete is
134-
// if c.UnavailableSince is no valid, then we can't delete is
135-
// if force is true, we can always delete is
136-
if !force && !c.UnavailableSince.Valid {
137-
return errors.E(errors.CodeStillAlive, "controller is still alive")
138-
}
139-
140133
models, err := db.GetModelsByController(ctx, c)
141134
if err != nil {
142135
return err
143136
}
144-
// Delete its models first.
137+
if len(models) > 0 && !force {
138+
return errors.E(errors.CodeStillAlive, "controller still has models")
139+
}
140+
141+
// Remove all models associated with the controller. If force is false,
142+
// we can only reach here with an empty list of models.
145143
for _, model := range models {
146144
err := db.DeleteModel(ctx, &model)
147145
if err != nil {

internal/jimm/juju/jimm_test.go

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ func TestSetControllerDeprecated(t *testing.T) {
231231
}
232232
}
233233

234-
const removeControllerTestEnv = `clouds:
234+
const removeControllerWithModelsTestEnv = `clouds:
235235
- name: test-cloud
236236
type: test-provider
237237
regions:
@@ -270,54 +270,64 @@ models:
270270
access: read
271271
`
272272

273+
const removeControllerWithoutModelsTestEnv = `clouds:
274+
- name: test-cloud
275+
type: test-provider
276+
regions:
277+
- name: test-cloud-region
278+
cloud-credentials:
279+
- owner: alice@canonical.com
280+
name: cred-1
281+
cloud: test-cloud
282+
users:
283+
- username: alice@canonical.com
284+
controller-access: superuser
285+
controllers:
286+
- name: controller-1
287+
uuid: 00000001-0000-0000-0000-000000000001
288+
cloud: test-cloud
289+
region: test-cloud-region
290+
`
291+
273292
func TestRemoveController(t *testing.T) {
274293
c := qt.New(t)
275294

276295
ctx := context.Background()
277296

278297
tests := []struct {
279-
about string
280-
user string
281-
unavailableSince *time.Time
282-
force bool
283-
expectedError string
298+
about string
299+
user string
300+
force bool
301+
env string
302+
expectedError string
284303
}{{
285-
about: "remove an unavailable controller",
286-
user: "alice@canonical.com",
287-
unavailableSince: &now,
288-
force: true,
304+
about: "remove a controller without models",
305+
user: "alice@canonical.com",
306+
force: true,
307+
env: removeControllerWithoutModelsTestEnv,
289308
}, {
290-
about: "remove a live controller with force",
309+
about: "remove a controller with models with force",
291310
user: "alice@canonical.com",
292311
force: true,
312+
env: removeControllerWithModelsTestEnv,
293313
}, {
294-
about: "error when removing a live controller",
314+
about: "error when removing a controller with models",
295315
user: "alice@canonical.com",
296316
force: false,
297-
expectedError: "controller is still alive",
317+
expectedError: "controller still has models",
318+
env: removeControllerWithModelsTestEnv,
298319
}}
299320

300321
for _, test := range tests {
301322
c.Run(test.about, func(c *qt.C) {
302323
j := newTestJujuManager(c, nil)
303324

304-
env := jimmtest.ParseEnvironment(c, removeControllerTestEnv)
325+
env := jimmtest.ParseEnvironment(c, test.env)
305326
env.PopulateDB(c, j.Database)
306327

307328
dbUser := env.User(test.user).DBObject(c, j.Database)
308329
user := openfga.NewUser(&dbUser, nil)
309330

310-
if test.unavailableSince != nil {
311-
// make the controller unavailable
312-
controller := env.Controller("controller-1").DBObject(c, j.Database)
313-
controller.UnavailableSince = sql.NullTime{
314-
Valid: true,
315-
Time: *test.unavailableSince,
316-
}
317-
err := j.Database.UpdateController(ctx, &controller)
318-
c.Assert(err, qt.Equals, nil)
319-
}
320-
321331
err := j.RemoveController(ctx, user, "controller-1", test.force)
322332
if test.expectedError != "" {
323333
c.Assert(err, qt.ErrorMatches, test.expectedError)

testing/jimm_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ func TestAddControllerCustomTLSHostname(t *testing.T) {
361361
func TestRemoveController(t *testing.T) {
362362
c := qt.New(t)
363363
s := jimmtest.SetupJimmWithControllers(c)
364+
model := s.CreateModelForBob(c)
364365

365366
conn := s.Open(c, nil, "alice", nil)
366367
defer conn.Close()
@@ -371,8 +372,9 @@ func TestRemoveController(t *testing.T) {
371372
_, err := client.RemoveController(&apiparams.RemoveControllerRequest{
372373
Name: name,
373374
})
374-
c.Check(err, qt.ErrorMatches, `controller is still alive \(still alive\)`)
375+
c.Check(err, qt.ErrorMatches, `controller still has models.*`)
375376
c.Check(jujuparams.ErrCode(err), qt.Equals, apiparams.CodeStillAlive)
377+
s.DestroyModelAndDeleteFromDatabase(c, model.ResourceTag())
376378

377379
conn2 := s.Open(c, nil, "bob", nil)
378380
defer conn2.Close()
@@ -385,8 +387,7 @@ func TestRemoveController(t *testing.T) {
385387
c.Check(jujuparams.ErrCode(err), qt.Equals, jujuparams.CodeUnauthorized)
386388

387389
ci, err := client.RemoveController(&apiparams.RemoveControllerRequest{
388-
Name: name,
389-
Force: true,
390+
Name: name,
390391
})
391392
c.Assert(err, qt.Equals, nil)
392393
ciExpected := apiparams.ControllerInfo{

0 commit comments

Comments
 (0)