github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/migration_import_tasks_test.go (about)

     1  // Copyright 2019 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/description/v5"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/mgo/v3/txn"
    12  	"github.com/juju/names/v5"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/juju/utils/v3"
    15  	"go.uber.org/mock/gomock"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	"github.com/juju/juju/core/permission"
    19  	"github.com/juju/juju/environs/config"
    20  )
    21  
    22  type MigrationImportTasksSuite struct{}
    23  
    24  var _ = gc.Suite(&MigrationImportTasksSuite{})
    25  
    26  func (s *MigrationImportTasksSuite) TestImportApplicationOffers(c *gc.C) {
    27  	ctrl := gomock.NewController(c)
    28  	defer ctrl.Finish()
    29  
    30  	offerUUID, err := utils.NewUUID()
    31  	c.Assert(err, jc.ErrorIsNil)
    32  	offerUUID2, err := utils.NewUUID()
    33  	c.Assert(err, jc.ErrorIsNil)
    34  
    35  	runner := ImportApplicationOfferRunner{
    36  		OfferUUID: offerUUID.String(),
    37  		model:     NewMockApplicationOfferInput(ctrl),
    38  		runner:    NewMockTransactionRunner(ctrl),
    39  	}
    40  
    41  	entity := s.applicationOffer(ctrl)
    42  	offerDoc := applicationOfferDoc{
    43  		DocID:                  fmt.Sprintf("%s:%s", offerUUID.String(), "offer-name-foo"),
    44  		OfferUUID:              offerUUID.String(),
    45  		OfferName:              "offer-name-foo",
    46  		ApplicationName:        "foo",
    47  		ApplicationDescription: "foo app description",
    48  		Endpoints: map[string]string{
    49  			"db": "db",
    50  		},
    51  	}
    52  	secondOfferDoc := offerDoc
    53  	secondOfferDoc.DocID = fmt.Sprintf("%s:%s", offerUUID2.String(), "second-offer")
    54  	secondOfferDoc.OfferUUID = offerUUID2.String()
    55  	secondOfferDoc.OfferName = "second-offer"
    56  
    57  	entity.EXPECT().ApplicationName().Return(offerDoc.ApplicationName).Times(2)
    58  	runner.Add(runner.applicationOffers(entity, entity))
    59  	runner.Add(runner.docID(offerDoc.OfferName, offerDoc.DocID))
    60  	runner.Add(runner.docID(secondOfferDoc.OfferName, secondOfferDoc.DocID))
    61  	runner.Add(runner.applicationOfferDoc(offerDoc, entity))
    62  	runner.Add(runner.applicationOfferDoc(secondOfferDoc, entity))
    63  
    64  	refOp := txn.Op{
    65  		Assert: txn.DocMissing,
    66  	}
    67  	runner.Add(runner.applicationOffersRefOp(refOp, 2))
    68  
    69  	entity.EXPECT().ACL().Return(map[string]string{"fred": "consume"}).Times(2)
    70  	permissionOp := createPermissionOp(applicationOfferKey(
    71  		offerUUID.String()), userGlobalKey(userAccessID(names.NewUserTag("fred"))), permission.ConsumeAccess)
    72  	permissionOp2 := createPermissionOp(applicationOfferKey(
    73  		offerUUID2.String()), userGlobalKey(userAccessID(names.NewUserTag("fred"))), permission.ConsumeAccess)
    74  
    75  	runner.Add(runner.transaction([]applicationOfferDoc{offerDoc, secondOfferDoc}, []txn.Op{permissionOp, permissionOp2}, refOp))
    76  
    77  	err = runner.Run(ctrl)
    78  	c.Assert(err, jc.ErrorIsNil)
    79  }
    80  
    81  func (s *MigrationImportTasksSuite) TestImportApplicationOffersTransactionFailure(c *gc.C) {
    82  	ctrl := gomock.NewController(c)
    83  	defer ctrl.Finish()
    84  
    85  	offerUUID, err := utils.NewUUID()
    86  	c.Assert(err, jc.ErrorIsNil)
    87  
    88  	runner := ImportApplicationOfferRunner{
    89  		OfferUUID: offerUUID.String(),
    90  		model:     NewMockApplicationOfferInput(ctrl),
    91  		runner:    NewMockTransactionRunner(ctrl),
    92  	}
    93  
    94  	entity := s.applicationOffer(ctrl)
    95  	offerDoc := applicationOfferDoc{
    96  		DocID:                  fmt.Sprintf("%s:%s", offerUUID.String(), "offer-name-foo"),
    97  		OfferUUID:              offerUUID.String(),
    98  		OfferName:              "offer-name-foo",
    99  		ApplicationName:        "foo",
   100  		ApplicationDescription: "foo app description",
   101  		Endpoints: map[string]string{
   102  			"db": "db",
   103  		},
   104  	}
   105  
   106  	entity.EXPECT().ACL().Return(map[string]string{})
   107  	entity.EXPECT().ApplicationName().Return(offerDoc.ApplicationName)
   108  	runner.Add(runner.applicationOffers(entity))
   109  	runner.Add(runner.applicationOfferDoc(offerDoc, entity))
   110  	runner.Add(runner.docID(offerDoc.OfferName, offerDoc.DocID))
   111  
   112  	refOp := txn.Op{
   113  		Assert: txn.DocMissing,
   114  	}
   115  	runner.Add(runner.applicationOffersRefOp(refOp, 1))
   116  	runner.Add(runner.transactionWithError(errors.New("fail")))
   117  
   118  	err = runner.Run(ctrl)
   119  	c.Assert(err, gc.ErrorMatches, "fail")
   120  }
   121  
   122  type ImportApplicationOfferRunner struct {
   123  	OfferUUID string
   124  	model     *MockApplicationOfferInput
   125  	runner    *MockTransactionRunner
   126  	ops       []func(*gomock.Controller)
   127  }
   128  
   129  func (s *ImportApplicationOfferRunner) Add(fn func(*gomock.Controller)) {
   130  	s.ops = append(s.ops, fn)
   131  }
   132  
   133  func (s *ImportApplicationOfferRunner) Run(ctrl *gomock.Controller) error {
   134  	for _, v := range s.ops {
   135  		v(ctrl)
   136  	}
   137  
   138  	m := ImportApplicationOffer{}
   139  	return m.Execute(s.model, s.runner)
   140  }
   141  
   142  func (s *ImportApplicationOfferRunner) applicationOffers(entities ...description.ApplicationOffer) func(ctrl *gomock.Controller) {
   143  	return func(ctrl *gomock.Controller) {
   144  		s.model.EXPECT().Offers().Return(entities)
   145  	}
   146  }
   147  
   148  func (s *ImportApplicationOfferRunner) applicationOfferDoc(offerDoc applicationOfferDoc, entity description.ApplicationOffer) func(ctrl *gomock.Controller) {
   149  	return func(ctrl *gomock.Controller) {
   150  		s.model.EXPECT().MakeApplicationOfferDoc(entity).Return(offerDoc, nil)
   151  	}
   152  }
   153  
   154  func (s *ImportApplicationOfferRunner) applicationOffersRefOp(op txn.Op, cnt int) func(ctrl *gomock.Controller) {
   155  	return func(ctrl *gomock.Controller) {
   156  		s.model.EXPECT().MakeApplicationOffersRefOp("foo", cnt).Return(op, nil)
   157  	}
   158  }
   159  
   160  func (s *MigrationImportTasksSuite) applicationOffer(ctrl *gomock.Controller) *MockApplicationOffer {
   161  	return NewMockApplicationOffer(ctrl)
   162  }
   163  
   164  func (s *ImportApplicationOfferRunner) docID(offerName, docID string) func(ctrl *gomock.Controller) {
   165  	return func(ctrl *gomock.Controller) {
   166  		s.model.EXPECT().DocID(offerName).Return(docID)
   167  	}
   168  }
   169  
   170  func (s *ImportApplicationOfferRunner) transaction(offerDocs []applicationOfferDoc, permissionOps []txn.Op, ops ...txn.Op) func(ctrl *gomock.Controller) {
   171  	return func(ctrl *gomock.Controller) {
   172  		useOps := make([]txn.Op, 0)
   173  
   174  		for i, doc := range offerDocs {
   175  			useOps = append(useOps, []txn.Op{
   176  				{
   177  					C:      applicationOffersC,
   178  					Id:     doc.DocID,
   179  					Assert: txn.DocMissing,
   180  					Insert: doc,
   181  				},
   182  				permissionOps[i],
   183  			}...)
   184  		}
   185  		useOps = append(useOps, ops...)
   186  
   187  		s.runner.EXPECT().RunTransaction(useOps).Return(nil)
   188  	}
   189  }
   190  
   191  func (s *ImportApplicationOfferRunner) transactionWithError(err error) func(ctrl *gomock.Controller) {
   192  	return func(ctrl *gomock.Controller) {
   193  		s.runner.EXPECT().RunTransaction(gomock.Any()).Return(err)
   194  	}
   195  }
   196  
   197  func (s *MigrationImportTasksSuite) TestImportRemoteApplications(c *gc.C) {
   198  	ctrl := gomock.NewController(c)
   199  	defer ctrl.Finish()
   200  
   201  	status := s.status(ctrl)
   202  
   203  	entity0 := s.remoteApplication(ctrl, status)
   204  	entities := []description.RemoteApplication{
   205  		entity0,
   206  	}
   207  
   208  	appDoc := &remoteApplicationDoc{
   209  		Name:      "remote-application",
   210  		URL:       "me/model.rainbow",
   211  		OfferUUID: "offer-uuid",
   212  		Endpoints: []remoteEndpointDoc{
   213  			{Name: "db", Interface: "mysql"},
   214  			{Name: "db-admin", Interface: "mysql-root"},
   215  		},
   216  		Spaces: []remoteSpaceDoc{
   217  			{CloudType: "ec2"},
   218  		},
   219  	}
   220  	statusDoc := statusDoc{}
   221  	statusOp := txn.Op{}
   222  
   223  	model := NewMockRemoteApplicationsInput(ctrl)
   224  	model.EXPECT().RemoteApplications().Return(entities)
   225  	model.EXPECT().MakeRemoteApplicationDoc(entity0).Return(appDoc)
   226  	model.EXPECT().NewRemoteApplication(appDoc).Return(&RemoteApplication{
   227  		doc: *appDoc,
   228  	})
   229  	model.EXPECT().MakeStatusDoc(status).Return(statusDoc)
   230  	model.EXPECT().MakeStatusOp("c#remote-application", statusDoc).Return(statusOp)
   231  	model.EXPECT().DocID("remote-application").Return("c#remote-application")
   232  
   233  	runner := NewMockTransactionRunner(ctrl)
   234  	runner.EXPECT().RunTransaction([]txn.Op{
   235  		{
   236  			C:      applicationsC,
   237  			Id:     "remote-application",
   238  			Assert: txn.DocMissing,
   239  		},
   240  		{
   241  			C:      remoteApplicationsC,
   242  			Id:     "c#remote-application",
   243  			Assert: txn.DocMissing,
   244  			Insert: appDoc,
   245  		},
   246  		statusOp,
   247  	}).Return(nil)
   248  
   249  	m := ImportRemoteApplications{}
   250  	err := m.Execute(model, runner)
   251  	c.Assert(err, jc.ErrorIsNil)
   252  }
   253  
   254  // A Remote Application with a missing status field is a valid remote
   255  // application and should be correctly imported.
   256  func (s *MigrationImportTasksSuite) TestImportRemoteApplicationsWithMissingStatusField(c *gc.C) {
   257  	ctrl := gomock.NewController(c)
   258  	defer ctrl.Finish()
   259  
   260  	entity0 := s.remoteApplication(ctrl, nil)
   261  
   262  	entities := []description.RemoteApplication{
   263  		entity0,
   264  	}
   265  
   266  	appDoc := &remoteApplicationDoc{
   267  		Name:      "remote-application",
   268  		URL:       "me/model.rainbow",
   269  		OfferUUID: "offer-uuid",
   270  		Endpoints: []remoteEndpointDoc{
   271  			{Name: "db", Interface: "mysql"},
   272  			{Name: "db-admin", Interface: "mysql-root"},
   273  		},
   274  		Spaces: []remoteSpaceDoc{
   275  			{CloudType: "ec2"},
   276  		},
   277  	}
   278  
   279  	model := NewMockRemoteApplicationsInput(ctrl)
   280  	model.EXPECT().RemoteApplications().Return(entities)
   281  	model.EXPECT().MakeRemoteApplicationDoc(entity0).Return(appDoc)
   282  	model.EXPECT().NewRemoteApplication(appDoc).Return(&RemoteApplication{
   283  		doc: *appDoc,
   284  	})
   285  	model.EXPECT().DocID("remote-application").Return("c#remote-application")
   286  
   287  	runner := NewMockTransactionRunner(ctrl)
   288  	runner.EXPECT().RunTransaction([]txn.Op{
   289  		{
   290  			C:      applicationsC,
   291  			Id:     "remote-application",
   292  			Assert: txn.DocMissing,
   293  		},
   294  		{
   295  			C:      remoteApplicationsC,
   296  			Id:     "c#remote-application",
   297  			Assert: txn.DocMissing,
   298  			Insert: appDoc,
   299  		},
   300  	}).Return(nil)
   301  
   302  	m := ImportRemoteApplications{}
   303  	err := m.Execute(model, runner)
   304  	c.Assert(err, jc.ErrorIsNil)
   305  }
   306  
   307  func (s *MigrationImportTasksSuite) remoteApplication(ctrl *gomock.Controller, status description.Status) description.RemoteApplication {
   308  	entity := NewMockRemoteApplication(ctrl)
   309  	entity.EXPECT().Status().Return(status)
   310  	return entity
   311  }
   312  
   313  func (s *MigrationImportTasksSuite) status(ctrl *gomock.Controller) description.Status {
   314  	entity := NewMockStatus(ctrl)
   315  	return entity
   316  }
   317  
   318  func (s *MigrationImportTasksSuite) TestImportRemoteEntities(c *gc.C) {
   319  	ctrl := gomock.NewController(c)
   320  	defer ctrl.Finish()
   321  
   322  	entity0 := s.remoteEntity(ctrl, "application-app2", "xxx-yyy-ccc")
   323  	entity1 := s.remoteEntity(ctrl, "applicationoffer-offer3", "aaa-bbb-zzz")
   324  
   325  	entities := []description.RemoteEntity{
   326  		entity0,
   327  		entity1,
   328  	}
   329  
   330  	model := NewMockRemoteEntitiesInput(ctrl)
   331  	model.EXPECT().RemoteEntities().Return(entities)
   332  	model.EXPECT().OfferUUIDForApp("app2").Return("uuid2", nil)
   333  	model.EXPECT().OfferUUID("offer3").Return("uuid3", true)
   334  	model.EXPECT().DocID("applicationoffer-uuid2").Return("doc-uuid2")
   335  	model.EXPECT().DocID("applicationoffer-uuid3").Return("doc-uuid3")
   336  
   337  	runner := NewMockTransactionRunner(ctrl)
   338  	runner.EXPECT().RunTransaction([]txn.Op{
   339  		{
   340  			C:      remoteEntitiesC,
   341  			Id:     "doc-uuid2",
   342  			Assert: txn.DocMissing,
   343  			Insert: &remoteEntityDoc{
   344  				DocID: "doc-uuid2",
   345  				Token: "xxx-yyy-ccc",
   346  			},
   347  		},
   348  		{
   349  			C:      remoteEntitiesC,
   350  			Id:     "doc-uuid3",
   351  			Assert: txn.DocMissing,
   352  			Insert: &remoteEntityDoc{
   353  				DocID: "doc-uuid3",
   354  				Token: "aaa-bbb-zzz",
   355  			},
   356  		},
   357  	}).Return(nil)
   358  
   359  	m := ImportRemoteEntities{}
   360  	err := m.Execute(model, runner)
   361  	c.Assert(err, jc.ErrorIsNil)
   362  }
   363  
   364  func (s *MigrationImportTasksSuite) TestImportRemoteEntitiesWithNoEntities(c *gc.C) {
   365  	ctrl := gomock.NewController(c)
   366  	defer ctrl.Finish()
   367  
   368  	entities := []description.RemoteEntity{}
   369  
   370  	model := NewMockRemoteEntitiesInput(ctrl)
   371  	model.EXPECT().RemoteEntities().Return(entities)
   372  
   373  	runner := NewMockTransactionRunner(ctrl)
   374  	// No call to RunTransaction if there are no operations.
   375  
   376  	m := ImportRemoteEntities{}
   377  	err := m.Execute(model, runner)
   378  	c.Assert(err, jc.ErrorIsNil)
   379  }
   380  
   381  func (s *MigrationImportTasksSuite) TestImportRemoteEntitiesWithTransactionRunnerReturnsError(c *gc.C) {
   382  	ctrl := gomock.NewController(c)
   383  	defer ctrl.Finish()
   384  
   385  	entity0 := s.remoteEntity(ctrl, "application-uuid2", "xxx-yyy-ccc")
   386  
   387  	entities := []description.RemoteEntity{
   388  		entity0,
   389  	}
   390  
   391  	model := NewMockRemoteEntitiesInput(ctrl)
   392  	model.EXPECT().RemoteEntities().Return(entities)
   393  	model.EXPECT().OfferUUIDForApp("uuid2").Return("offeruuid2", nil)
   394  	model.EXPECT().DocID("applicationoffer-offeruuid2").Return("doc-uuid2")
   395  
   396  	runner := NewMockTransactionRunner(ctrl)
   397  	runner.EXPECT().RunTransaction([]txn.Op{
   398  		{
   399  			C:      remoteEntitiesC,
   400  			Id:     "doc-uuid2",
   401  			Assert: txn.DocMissing,
   402  			Insert: &remoteEntityDoc{
   403  				DocID: "doc-uuid2",
   404  				Token: "xxx-yyy-ccc",
   405  			},
   406  		},
   407  	}).Return(errors.New("fail"))
   408  
   409  	m := ImportRemoteEntities{}
   410  	err := m.Execute(model, runner)
   411  	c.Assert(err, gc.ErrorMatches, "fail")
   412  }
   413  
   414  func (s *MigrationImportTasksSuite) remoteEntity(ctrl *gomock.Controller, id, token string) *MockRemoteEntity {
   415  	entity := NewMockRemoteEntity(ctrl)
   416  	entity.EXPECT().ID().Return(id).AnyTimes()
   417  	entity.EXPECT().Token().Return(token)
   418  	return entity
   419  }
   420  
   421  func (s *MigrationImportTasksSuite) TestImportRelationNetworks(c *gc.C) {
   422  	ctrl := gomock.NewController(c)
   423  	defer ctrl.Finish()
   424  
   425  	entity0 := s.relationNetwork(ctrl, "ctrl-uuid-2", "xxx-yyy-ccc", []string{"10.0.1.0/16"})
   426  	entity1 := s.relationNetwork(ctrl, "ctrl-uuid-3", "aaa-bbb-zzz", []string{"10.0.0.1/24"})
   427  
   428  	entities := []description.RelationNetwork{
   429  		entity0,
   430  		entity1,
   431  	}
   432  
   433  	model := NewMockRelationNetworksInput(ctrl)
   434  	model.EXPECT().RelationNetworks().Return(entities)
   435  	model.EXPECT().DocID("ctrl-uuid-2").Return("ctrl-uuid-2")
   436  	model.EXPECT().DocID("ctrl-uuid-3").Return("ctrl-uuid-3")
   437  
   438  	runner := NewMockTransactionRunner(ctrl)
   439  	runner.EXPECT().RunTransaction([]txn.Op{
   440  		{
   441  			C:      relationNetworksC,
   442  			Id:     "ctrl-uuid-2",
   443  			Assert: txn.DocMissing,
   444  			Insert: relationNetworksDoc{
   445  				Id:          "ctrl-uuid-2",
   446  				RelationKey: "xxx-yyy-ccc",
   447  				CIDRS:       []string{"10.0.1.0/16"},
   448  			},
   449  		},
   450  		{
   451  			C:      relationNetworksC,
   452  			Id:     "ctrl-uuid-3",
   453  			Assert: txn.DocMissing,
   454  			Insert: relationNetworksDoc{
   455  				Id:          "ctrl-uuid-3",
   456  				RelationKey: "aaa-bbb-zzz",
   457  				CIDRS:       []string{"10.0.0.1/24"},
   458  			},
   459  		},
   460  	}).Return(nil)
   461  
   462  	m := ImportRelationNetworks{}
   463  	err := m.Execute(model, runner)
   464  	c.Assert(err, jc.ErrorIsNil)
   465  }
   466  
   467  func (s *MigrationImportTasksSuite) TestImportRelationNetworksWithNoEntities(c *gc.C) {
   468  	ctrl := gomock.NewController(c)
   469  	defer ctrl.Finish()
   470  
   471  	entities := []description.RelationNetwork{}
   472  
   473  	model := NewMockRelationNetworksInput(ctrl)
   474  	model.EXPECT().RelationNetworks().Return(entities)
   475  
   476  	runner := NewMockTransactionRunner(ctrl)
   477  	// No call to RunTransaction if there are no operations.
   478  
   479  	m := ImportRelationNetworks{}
   480  	err := m.Execute(model, runner)
   481  	c.Assert(err, jc.ErrorIsNil)
   482  }
   483  
   484  func (s *MigrationImportTasksSuite) TestImportRelationNetworksWithTransactionRunnerReturnsError(c *gc.C) {
   485  	ctrl := gomock.NewController(c)
   486  	defer ctrl.Finish()
   487  
   488  	entity0 := s.relationNetwork(ctrl, "ctrl-uuid-2", "xxx-yyy-ccc", []string{"10.0.1.0/16"})
   489  
   490  	entities := []description.RelationNetwork{
   491  		entity0,
   492  	}
   493  
   494  	model := NewMockRelationNetworksInput(ctrl)
   495  	model.EXPECT().RelationNetworks().Return(entities)
   496  	model.EXPECT().DocID("ctrl-uuid-2").Return("ctrl-uuid-2")
   497  
   498  	runner := NewMockTransactionRunner(ctrl)
   499  	runner.EXPECT().RunTransaction([]txn.Op{
   500  		{
   501  			C:      relationNetworksC,
   502  			Id:     "ctrl-uuid-2",
   503  			Assert: txn.DocMissing,
   504  			Insert: relationNetworksDoc{
   505  				Id:          "ctrl-uuid-2",
   506  				RelationKey: "xxx-yyy-ccc",
   507  				CIDRS:       []string{"10.0.1.0/16"},
   508  			},
   509  		},
   510  	}).Return(errors.New("fail"))
   511  
   512  	m := ImportRelationNetworks{}
   513  	err := m.Execute(model, runner)
   514  	c.Assert(err, gc.ErrorMatches, "fail")
   515  }
   516  
   517  func (s *MigrationImportTasksSuite) relationNetwork(ctrl *gomock.Controller, id, key string, cidrs []string) *MockRelationNetwork {
   518  	entity := NewMockRelationNetwork(ctrl)
   519  	entity.EXPECT().ID().Return(id)
   520  	entity.EXPECT().RelationKey().Return(key)
   521  	entity.EXPECT().CIDRS().Return(cidrs)
   522  	return entity
   523  }
   524  
   525  func (s *MigrationImportTasksSuite) TestImportExternalControllers(c *gc.C) {
   526  	ctrl := gomock.NewController(c)
   527  	defer ctrl.Finish()
   528  
   529  	entity0 := s.externalController(ctrl, "ctrl-uuid-2", "magic", "magic-cert", []string{"10.0.1.1"}, []string{"xxxx-yyyy-zzzz"})
   530  	entity1 := s.externalController(ctrl, "ctrl-uuid-3", "foo", "foo-cert", []string{"10.0.2.24"}, []string{"aaaa-bbbb-cccc"})
   531  
   532  	entities := []description.ExternalController{
   533  		entity0,
   534  		entity1,
   535  	}
   536  
   537  	doc0 := externalControllerDoc{
   538  		Id:     "ctrl-uuid-2",
   539  		Addrs:  []string{"10.0.1.1"},
   540  		Alias:  "magic",
   541  		CACert: "magic-cert",
   542  		Models: []string{"xxxx-yyyy-zzzz"},
   543  	}
   544  	doc1 := externalControllerDoc{
   545  		Id:     "ctrl-uuid-3",
   546  		Addrs:  []string{"10.0.2.24"},
   547  		Alias:  "foo",
   548  		CACert: "foo-cert",
   549  		Models: []string{"aaaa-bbbb-cccc"},
   550  	}
   551  	ops := []txn.Op{
   552  		{
   553  			C:      externalControllersC,
   554  			Id:     "ctrl-uuid-2",
   555  			Assert: txn.DocMissing,
   556  			Insert: doc0,
   557  		},
   558  		{
   559  			C:      externalControllersC,
   560  			Id:     "ctrl-uuid-3",
   561  			Assert: txn.DocMissing,
   562  			Insert: doc1,
   563  		},
   564  	}
   565  
   566  	model := NewMockExternalControllersInput(ctrl)
   567  	model.EXPECT().ExternalControllers().Return(entities)
   568  	gomock.InOrder(
   569  		model.EXPECT().ExternalControllerDoc("ctrl-uuid-2").Return(nil, nil),
   570  		model.EXPECT().MakeExternalControllerOp(doc0, nil).Return(ops[0]),
   571  		model.EXPECT().ExternalControllerDoc("ctrl-uuid-3").Return(nil, nil),
   572  		model.EXPECT().MakeExternalControllerOp(doc1, nil).Return(ops[1]),
   573  	)
   574  
   575  	runner := NewMockTransactionRunner(ctrl)
   576  	runner.EXPECT().RunTransaction(ops).Return(nil)
   577  
   578  	m := ImportExternalControllers{}
   579  	err := m.Execute(model, runner)
   580  	c.Assert(err, jc.ErrorIsNil)
   581  }
   582  
   583  func (s *MigrationImportTasksSuite) TestImportExternalControllersWithNoEntities(c *gc.C) {
   584  	ctrl := gomock.NewController(c)
   585  	defer ctrl.Finish()
   586  
   587  	entities := []description.ExternalController{}
   588  
   589  	model := NewMockExternalControllersInput(ctrl)
   590  	model.EXPECT().ExternalControllers().Return(entities)
   591  
   592  	runner := NewMockTransactionRunner(ctrl)
   593  	// No call to RunTransaction if there are no operations.
   594  
   595  	m := ImportExternalControllers{}
   596  	err := m.Execute(model, runner)
   597  	c.Assert(err, jc.ErrorIsNil)
   598  }
   599  
   600  func (s *MigrationImportTasksSuite) TestImportExternalControllersWithTransactionRunnerReturnsError(c *gc.C) {
   601  	ctrl := gomock.NewController(c)
   602  	defer ctrl.Finish()
   603  
   604  	entity0 := s.externalController(ctrl, "ctrl-uuid-2", "magic", "magic-cert", []string{"10.0.1.1"}, []string{"xxxx-yyyy-zzzz"})
   605  
   606  	entities := []description.ExternalController{
   607  		entity0,
   608  	}
   609  
   610  	doc0 := externalControllerDoc{
   611  		Id:     "ctrl-uuid-2",
   612  		Addrs:  []string{"10.0.1.1"},
   613  		Alias:  "magic",
   614  		CACert: "magic-cert",
   615  		Models: []string{"xxxx-yyyy-zzzz"},
   616  	}
   617  	ops := []txn.Op{
   618  		{
   619  			C:      externalControllersC,
   620  			Id:     "ctrl-uuid-2",
   621  			Assert: txn.DocMissing,
   622  			Insert: doc0,
   623  		},
   624  	}
   625  
   626  	model := NewMockExternalControllersInput(ctrl)
   627  	model.EXPECT().ExternalControllers().Return(entities)
   628  	model.EXPECT().ExternalControllerDoc("ctrl-uuid-2").Return(nil, nil)
   629  	model.EXPECT().MakeExternalControllerOp(doc0, nil).Return(ops[0])
   630  
   631  	runner := NewMockTransactionRunner(ctrl)
   632  	runner.EXPECT().RunTransaction([]txn.Op{
   633  		{
   634  			C:      externalControllersC,
   635  			Id:     "ctrl-uuid-2",
   636  			Assert: txn.DocMissing,
   637  			Insert: externalControllerDoc{
   638  				Id:     "ctrl-uuid-2",
   639  				Addrs:  []string{"10.0.1.1"},
   640  				Alias:  "magic",
   641  				CACert: "magic-cert",
   642  				Models: []string{"xxxx-yyyy-zzzz"},
   643  			},
   644  		},
   645  	}).Return(errors.New("fail"))
   646  
   647  	m := ImportExternalControllers{}
   648  	err := m.Execute(model, runner)
   649  	c.Assert(err, gc.ErrorMatches, "fail")
   650  }
   651  
   652  func (s *MigrationImportTasksSuite) externalController(ctrl *gomock.Controller, id, alias, caCert string, addrs, models []string) *MockExternalController {
   653  	entity := NewMockExternalController(ctrl)
   654  	entity.EXPECT().ID().Return(names.NewControllerTag(id))
   655  	entity.EXPECT().Alias().Return(alias)
   656  	entity.EXPECT().CACert().Return(caCert)
   657  	entity.EXPECT().Addrs().Return(addrs)
   658  	entity.EXPECT().Models().Return(models)
   659  	return entity
   660  }
   661  
   662  func (s *MigrationImportTasksSuite) TestImportFirewallRules(c *gc.C) {
   663  	ctrl := gomock.NewController(c)
   664  	defer ctrl.Finish()
   665  
   666  	entity0 := s.firewallRule(ctrl, "ssh", "ssh", []string{"192.168.0.1/24", "192.168.3.0/24"})
   667  	entity1 := s.firewallRule(ctrl, "juju-application-offer", "juju-application-offer", []string{"10.0.0.1/16"})
   668  
   669  	entities := []description.FirewallRule{
   670  		entity0,
   671  		entity1,
   672  	}
   673  
   674  	modelIn := NewMockFirewallRulesInput(ctrl)
   675  	modelIn.EXPECT().FirewallRules().Return(entities)
   676  
   677  	modelOut := NewMockFirewallRulesOutput(ctrl)
   678  	modelOut.EXPECT().UpdateModelConfig(map[string]interface{}{
   679  		config.SSHAllowKey: "192.168.0.1/24,192.168.3.0/24",
   680  	}, nil)
   681  	modelOut.EXPECT().UpdateModelConfig(map[string]interface{}{
   682  		config.SAASIngressAllowKey: "10.0.0.1/16",
   683  	}, nil)
   684  
   685  	m := ImportFirewallRules{}
   686  	err := m.Execute(modelIn, modelOut)
   687  	c.Assert(err, jc.ErrorIsNil)
   688  }
   689  
   690  func (s *MigrationImportTasksSuite) TestImportFirewallRulesEmptyJujuApplicationOffer(c *gc.C) {
   691  	ctrl := gomock.NewController(c)
   692  	defer ctrl.Finish()
   693  
   694  	entity0 := s.firewallRule(ctrl, "juju-application-offer", "juju-application-offer", []string{})
   695  
   696  	entities := []description.FirewallRule{
   697  		entity0,
   698  	}
   699  
   700  	model := NewMockFirewallRulesInput(ctrl)
   701  	model.EXPECT().FirewallRules().Return(entities)
   702  
   703  	// No call to UpdateModeConfig since juju-application-offer is empty
   704  
   705  	m := ImportFirewallRules{}
   706  	err := m.Execute(model, nil)
   707  	c.Assert(err, jc.ErrorIsNil)
   708  }
   709  
   710  func (s *MigrationImportTasksSuite) TestImportFirewallRulesWithNoEntities(c *gc.C) {
   711  	ctrl := gomock.NewController(c)
   712  	defer ctrl.Finish()
   713  
   714  	entities := []description.FirewallRule{}
   715  
   716  	model := NewMockFirewallRulesInput(ctrl)
   717  	model.EXPECT().FirewallRules().Return(entities)
   718  
   719  	// No call to UpdateModeConfig if there are no operations.
   720  
   721  	m := ImportFirewallRules{}
   722  	err := m.Execute(model, nil)
   723  	c.Assert(err, jc.ErrorIsNil)
   724  }
   725  
   726  func (s *MigrationImportTasksSuite) TestImportFirewallRulesWithTransactionRunnerReturnsError(c *gc.C) {
   727  	ctrl := gomock.NewController(c)
   728  	defer ctrl.Finish()
   729  
   730  	entity0 := s.firewallRule(ctrl, "ssh", "ssh", []string{"192.168.0.1/24"})
   731  
   732  	entities := []description.FirewallRule{
   733  		entity0,
   734  	}
   735  
   736  	modelIn := NewMockFirewallRulesInput(ctrl)
   737  	modelIn.EXPECT().FirewallRules().Return(entities)
   738  
   739  	modelOut := NewMockFirewallRulesOutput(ctrl)
   740  	modelOut.EXPECT().UpdateModelConfig(map[string]interface{}{
   741  		config.SSHAllowKey: "192.168.0.1/24",
   742  	}, nil).Return(errors.New("fail"))
   743  
   744  	m := ImportFirewallRules{}
   745  	err := m.Execute(modelIn, modelOut)
   746  	c.Assert(err, gc.ErrorMatches, "fail")
   747  }
   748  
   749  func (s *MigrationImportTasksSuite) firewallRule(ctrl *gomock.Controller, id, service string, whitelist []string) *MockFirewallRule {
   750  	entity := NewMockFirewallRule(ctrl)
   751  	entity.EXPECT().WellKnownService().Return(service)
   752  	entity.EXPECT().WhitelistCIDRs().Return(whitelist)
   753  	return entity
   754  }