github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/client/application/deployrepository_test.go (about) 1 // Copyright 2023 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package application 5 6 import ( 7 "fmt" 8 "reflect" 9 10 "github.com/juju/charm/v12" 11 "github.com/juju/charm/v12/resource" 12 "github.com/juju/errors" 13 "github.com/juju/names/v5" 14 jc "github.com/juju/testing/checkers" 15 "github.com/kr/pretty" 16 "go.uber.org/mock/gomock" 17 gc "gopkg.in/check.v1" 18 19 corecharm "github.com/juju/juju/core/charm" 20 coreconfig "github.com/juju/juju/core/config" 21 "github.com/juju/juju/core/constraints" 22 "github.com/juju/juju/core/instance" 23 "github.com/juju/juju/environs/config" 24 "github.com/juju/juju/rpc/params" 25 "github.com/juju/juju/state" 26 coretesting "github.com/juju/juju/testing" 27 ) 28 29 type validatorSuite struct { 30 bindings *MockBindings 31 machine *MockMachine 32 model *MockModel 33 repo *MockRepository 34 repoFactory *MockRepositoryFactory 35 state *MockDeployFromRepositoryState 36 } 37 38 var _ = gc.Suite(&deployRepositorySuite{}) 39 var _ = gc.Suite(&validatorSuite{}) 40 41 func (s *validatorSuite) TestValidateSuccess(c *gc.C) { 42 defer s.setupMocks(c).Finish() 43 s.expectSimpleValidate() 44 // resolveCharm 45 curl := charm.MustParseURL("testcharm") 46 resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4") 47 origin := corecharm.Origin{ 48 Source: "charm-hub", 49 Channel: &charm.Channel{Risk: "stable"}, 50 Platform: corecharm.Platform{Architecture: "amd64"}, 51 } 52 resolvedOrigin := corecharm.Origin{ 53 Source: "charm-hub", 54 Type: "charm", 55 Channel: &charm.Channel{Track: "default", Risk: "stable"}, 56 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"}, 57 Revision: intptr(4), 58 } 59 charmID := corecharm.CharmID{URL: curl, Origin: origin} 60 resolvedData := getResolvedData(resultURL, resolvedOrigin) 61 s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil) 62 s.repo.EXPECT().ResolveResources(nil, corecharm.CharmID{URL: resultURL, Origin: resolvedOrigin}).Return(nil, nil) 63 s.model.EXPECT().UUID().Return("") 64 65 // getCharm 66 s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil) 67 s.state.EXPECT().Charm(gomock.Any()).Return(nil, errors.NotFoundf("charm")) 68 69 arg := params.DeployFromRepositoryArg{ 70 CharmName: "testcharm", 71 } 72 dt, errs := s.getValidator().validate(arg) 73 c.Assert(errs, gc.HasLen, 0, gc.Commentf("%s", pretty.Sprint(errs))) 74 c.Assert(dt, gc.DeepEquals, deployTemplate{ 75 applicationName: "test-charm", 76 charm: corecharm.NewCharmInfoAdapter(resolvedData.EssentialMetadata), 77 charmURL: resultURL, 78 numUnits: 1, 79 origin: resolvedOrigin, 80 }) 81 } 82 83 func (s *validatorSuite) TestValidateIAASAttachStorageFail(c *gc.C) { 84 argStorageNames := []string{"one-0"} 85 expectedStorageTags := []names.StorageTag{} 86 s.testValidateIAASAttachStorage(c, argStorageNames, expectedStorageTags, errors.NotValid) 87 } 88 89 func (s *validatorSuite) TestValidateIAASAttachStorageSuccess(c *gc.C) { 90 argStorageNames := []string{"one/0", "two/3"} 91 expectedStorageTags := []names.StorageTag{names.NewStorageTag("one/0"), names.NewStorageTag("two/3")} 92 s.testValidateIAASAttachStorage(c, argStorageNames, expectedStorageTags, "") 93 } 94 95 func (s *validatorSuite) testValidateIAASAttachStorage(c *gc.C, argStorage []string, expectedStorageTags []names.StorageTag, expectedErr errors.ConstError) { 96 defer s.setupMocks(c).Finish() 97 s.expectSimpleValidate() 98 // resolveCharm 99 curl := charm.MustParseURL("testcharm") 100 resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4") 101 origin := corecharm.Origin{ 102 Source: "charm-hub", 103 Channel: &charm.Channel{Risk: "stable"}, 104 Platform: corecharm.Platform{Architecture: "amd64"}, 105 } 106 resolvedOrigin := corecharm.Origin{ 107 Source: "charm-hub", 108 Type: "charm", 109 Channel: &charm.Channel{Track: "default", Risk: "stable"}, 110 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"}, 111 Revision: intptr(4), 112 } 113 charmID := corecharm.CharmID{URL: curl, Origin: origin} 114 resolvedData := getResolvedData(resultURL, resolvedOrigin) 115 s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil) 116 s.repo.EXPECT().ResolveResources(nil, corecharm.CharmID{URL: resultURL, Origin: resolvedOrigin}).Return(nil, nil) 117 s.model.EXPECT().UUID().Return("") 118 // getCharm 119 s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil) 120 s.state.EXPECT().Charm(gomock.Any()).Return(nil, errors.NotFoundf("charm")) 121 122 arg := params.DeployFromRepositoryArg{ 123 CharmName: "testcharm", 124 AttachStorage: argStorage, 125 } 126 dt, errs := s.iaasDeployFromRepositoryValidator().ValidateArg(arg) 127 if expectedErr == "" { 128 c.Assert(errs, gc.HasLen, 0) 129 c.Assert(dt, gc.DeepEquals, deployTemplate{ 130 applicationName: "test-charm", 131 charm: corecharm.NewCharmInfoAdapter(resolvedData.EssentialMetadata), 132 charmURL: resultURL, 133 numUnits: 1, 134 origin: resolvedOrigin, 135 attachStorage: expectedStorageTags, 136 }) 137 } else { 138 c.Assert(errs, gc.HasLen, 1) 139 c.Assert(errors.Is(errs[0], expectedErr), jc.IsTrue) 140 } 141 } 142 143 func (s *validatorSuite) TestValidatePlacementSuccess(c *gc.C) { 144 defer s.setupMocks(c).Finish() 145 s.expectSimpleValidate() 146 // resolveCharm 147 curl := charm.MustParseURL("testcharm") 148 resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4") 149 origin := corecharm.Origin{ 150 Source: "charm-hub", 151 Channel: &charm.Channel{Risk: "stable"}, 152 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"}, 153 } 154 resolvedOrigin := corecharm.Origin{ 155 Source: "charm-hub", 156 Type: "charm", 157 Channel: &charm.Channel{Track: "default", Risk: "stable"}, 158 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"}, 159 Revision: intptr(4), 160 } 161 // getCharm 162 charmID := corecharm.CharmID{URL: curl, Origin: origin} 163 resolvedData := getResolvedData(resultURL, resolvedOrigin) 164 s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil) 165 s.repo.EXPECT().ResolveResources(nil, corecharm.CharmID{URL: resultURL, Origin: resolvedOrigin}).Return(nil, nil) 166 s.model.EXPECT().UUID().Return("") 167 168 // Placement 169 s.state.EXPECT().Machine("0").Return(s.machine, nil).Times(2) 170 s.machine.EXPECT().IsLockedForSeriesUpgrade().Return(false, nil) 171 s.machine.EXPECT().IsParentLockedForSeriesUpgrade().Return(false, nil) 172 s.machine.EXPECT().Base().Return(state.Base{ 173 OS: "ubuntu", 174 Channel: "22.04", 175 }) 176 hwc := &instance.HardwareCharacteristics{Arch: strptr("amd64")} 177 s.machine.EXPECT().HardwareCharacteristics().Return(hwc, nil) 178 s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil) 179 s.state.EXPECT().Charm(gomock.Any()).Return(nil, errors.NotFoundf("charm")) 180 181 arg := params.DeployFromRepositoryArg{ 182 CharmName: "testcharm", 183 Placement: []*instance.Placement{{Directive: "0", Scope: instance.MachineScope}}, 184 } 185 dt, errs := s.getValidator().validate(arg) 186 c.Assert(errs, gc.HasLen, 0) 187 c.Assert(dt, gc.DeepEquals, deployTemplate{ 188 applicationName: "test-charm", 189 charm: corecharm.NewCharmInfoAdapter(resolvedData.EssentialMetadata), 190 charmURL: resultURL, 191 numUnits: 1, 192 origin: resolvedOrigin, 193 placement: arg.Placement, 194 }) 195 } 196 197 func (s *validatorSuite) TestValidateEndpointBindingSuccess(c *gc.C) { 198 defer s.setupMocks(c).Finish() 199 s.expectSimpleValidate() 200 // resolveCharm 201 curl := charm.MustParseURL("testcharm") 202 resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4") 203 origin := corecharm.Origin{ 204 Source: "charm-hub", 205 Channel: &charm.Channel{Risk: "stable"}, 206 Platform: corecharm.Platform{Architecture: "amd64"}, 207 } 208 resolvedOrigin := corecharm.Origin{ 209 Source: "charm-hub", 210 Type: "charm", 211 Channel: &charm.Channel{Track: "default", Risk: "stable"}, 212 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"}, 213 Revision: intptr(4), 214 } 215 // getCharm 216 charmID := corecharm.CharmID{URL: curl, Origin: origin} 217 resolvedData := getResolvedData(resultURL, resolvedOrigin) 218 s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil) 219 s.repo.EXPECT().ResolveResources(nil, corecharm.CharmID{URL: resultURL, Origin: resolvedOrigin}).Return(nil, nil) 220 s.model.EXPECT().UUID().Return("") 221 222 // state bindings 223 endpointMap := map[string]string{"to": "from"} 224 s.bindings.EXPECT().Map().Return(endpointMap) 225 s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil) 226 s.state.EXPECT().Charm(gomock.Any()).Return(nil, errors.NotFoundf("charm")) 227 228 arg := params.DeployFromRepositoryArg{ 229 CharmName: "testcharm", 230 EndpointBindings: endpointMap, 231 } 232 dt, errs := s.getValidator().validate(arg) 233 c.Assert(errs, gc.HasLen, 0) 234 c.Assert(dt, gc.DeepEquals, deployTemplate{ 235 applicationName: "test-charm", 236 charm: corecharm.NewCharmInfoAdapter(resolvedData.EssentialMetadata), 237 charmURL: resultURL, 238 endpoints: endpointMap, 239 numUnits: 1, 240 origin: resolvedOrigin, 241 }) 242 } 243 244 func (s *validatorSuite) TestValidateEndpointBindingFail(c *gc.C) { 245 defer s.setupMocks(c).Finish() 246 s.expectSimpleValidate() 247 // resolveCharm 248 curl := charm.MustParseURL("testcharm") 249 resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4") 250 origin := corecharm.Origin{ 251 Source: "charm-hub", 252 Channel: &charm.Channel{Risk: "stable"}, 253 Platform: corecharm.Platform{Architecture: "amd64"}, 254 } 255 resolvedOrigin := corecharm.Origin{ 256 Source: "charm-hub", 257 Type: "charm", 258 Channel: &charm.Channel{Track: "default", Risk: "stable"}, 259 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"}, 260 Revision: intptr(4), 261 } 262 // getCharm 263 charmID := corecharm.CharmID{URL: curl, Origin: origin} 264 resolvedData := getResolvedData(resultURL, resolvedOrigin) 265 s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil) 266 s.repo.EXPECT().ResolveResources(nil, corecharm.CharmID{URL: resultURL, Origin: resolvedOrigin}).Return(nil, nil) 267 s.model.EXPECT().UUID().Return("") 268 269 // state bindings 270 endpointMap := map[string]string{"to": "from"} 271 s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil) 272 s.state.EXPECT().Charm(gomock.Any()).Return(nil, errors.NotFoundf("charm")) 273 274 s.repoFactory.EXPECT().GetCharmRepository(gomock.Any()).Return(s.repo, nil).AnyTimes() 275 v := &deployFromRepositoryValidator{ 276 model: s.model, 277 state: s.state, 278 repoFactory: s.repoFactory, 279 newStateBindings: func(st state.EndpointBinding, givenMap map[string]string) (Bindings, error) { 280 return nil, errors.NotFoundf("space") 281 }, 282 } 283 284 arg := params.DeployFromRepositoryArg{ 285 CharmName: "testcharm", 286 EndpointBindings: endpointMap, 287 } 288 _, errs := v.validate(arg) 289 c.Assert(errs, gc.HasLen, 1) 290 c.Assert(errs[0], jc.ErrorIs, errors.NotFound) 291 } 292 293 func (s *validatorSuite) expectSimpleValidate() { 294 // createOrigin 295 s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil) 296 s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig())).AnyTimes() 297 } 298 299 func (s *validatorSuite) TestResolveCharm(c *gc.C) { 300 defer s.setupMocks(c).Finish() 301 curl := charm.MustParseURL("testcharm") 302 resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4") 303 origin := corecharm.Origin{ 304 Source: "charm-hub", 305 Channel: &charm.Channel{Risk: "stable"}, 306 Platform: corecharm.Platform{Architecture: "amd64"}, 307 } 308 resolvedOrigin := corecharm.Origin{ 309 Source: "charm-hub", 310 Type: "charm", 311 Channel: &charm.Channel{Track: "default", Risk: "stable"}, 312 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"}, 313 Revision: intptr(4), 314 } 315 charmID := corecharm.CharmID{URL: curl, Origin: origin} 316 resolvedData := getResolvedData(resultURL, resolvedOrigin) 317 s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil) 318 s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig())) 319 s.state.EXPECT().ModelConstraints().Return(constraints.Value{ 320 Arch: strptr("arm64"), 321 }, nil) 322 323 obtained, err := s.getValidator().resolveCharm(curl, origin, false, false, constraints.Value{}) 324 c.Assert(err, jc.ErrorIsNil) 325 c.Assert(obtained.URL, gc.DeepEquals, resultURL) 326 c.Assert(obtained.EssentialMetadata.ResolvedOrigin, gc.DeepEquals, resolvedOrigin) 327 } 328 329 func (s *validatorSuite) TestResolveCharmArchAll(c *gc.C) { 330 defer s.setupMocks(c).Finish() 331 curl := charm.MustParseURL("testcharm") 332 resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4") 333 origin := corecharm.Origin{ 334 Source: "charm-hub", 335 Channel: &charm.Channel{Risk: "stable"}, 336 Platform: corecharm.Platform{Architecture: "amd64"}, 337 } 338 resolvedOrigin := corecharm.Origin{ 339 Source: "charm-hub", 340 Type: "charm", 341 Channel: &charm.Channel{Track: "default", Risk: "stable"}, 342 Platform: corecharm.Platform{Architecture: "all", OS: "ubuntu", Channel: "22.04"}, 343 Revision: intptr(4), 344 } 345 charmID := corecharm.CharmID{URL: curl, Origin: origin} 346 resolvedData := getResolvedData(resultURL, resolvedOrigin) 347 s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil) 348 s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig())) 349 s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil) 350 351 obtained, err := s.getValidator().resolveCharm(curl, origin, false, false, constraints.Value{}) 352 c.Assert(err, jc.ErrorIsNil) 353 c.Assert(obtained.URL, gc.DeepEquals, resultURL) 354 expectedOrigin := resolvedOrigin 355 expectedOrigin.Platform.Architecture = "arm64" 356 c.Assert(obtained.EssentialMetadata.ResolvedOrigin, gc.DeepEquals, expectedOrigin) 357 } 358 359 func (s *validatorSuite) TestResolveCharmUnsupportedSeriesErrorForce(c *gc.C) { 360 defer s.setupMocks(c).Finish() 361 curl := charm.MustParseURL("testcharm") 362 resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4") 363 origin := corecharm.Origin{ 364 Source: "charm-hub", 365 Channel: &charm.Channel{Risk: "stable"}, 366 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"}, 367 } 368 resolvedOrigin := corecharm.Origin{ 369 Source: "charm-hub", 370 Type: "charm", 371 Channel: &charm.Channel{Track: "default", Risk: "stable"}, 372 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"}, 373 Revision: intptr(4), 374 } 375 supportedSeries := []string{"focal"} 376 newErr := charm.NewUnsupportedSeriesError("jammy", supportedSeries) 377 charmID := corecharm.CharmID{URL: curl, Origin: origin} 378 resolvedData := getResolvedData(resultURL, resolvedOrigin) 379 s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, newErr) 380 s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig())) 381 s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil) 382 383 obtained, err := s.getValidator().resolveCharm(curl, origin, true, false, constraints.Value{}) 384 c.Assert(err, jc.ErrorIsNil) 385 c.Assert(obtained.URL, gc.DeepEquals, resultURL) 386 c.Assert(obtained.EssentialMetadata.ResolvedOrigin, gc.DeepEquals, resolvedOrigin) 387 } 388 389 func (s *validatorSuite) TestResolveCharmUnsupportedSeriesError(c *gc.C) { 390 defer s.setupMocks(c).Finish() 391 curl := charm.MustParseURL("testcharm") 392 origin := corecharm.Origin{ 393 Source: "charm-hub", 394 Channel: &charm.Channel{Risk: "stable"}, 395 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"}, 396 } 397 charmID := corecharm.CharmID{URL: curl, Origin: origin} 398 supportedSeries := []string{"focal"} 399 newErr := charm.NewUnsupportedSeriesError("jammy", supportedSeries) 400 s.repo.EXPECT().ResolveForDeploy(charmID).Return(corecharm.ResolvedDataForDeploy{}, newErr) 401 402 _, err := s.getValidator().resolveCharm(curl, origin, false, false, constraints.Value{}) 403 c.Assert(err, gc.ErrorMatches, `series "jammy" not supported by charm, supported series are: focal. Use --force to deploy the charm anyway.`) 404 } 405 406 func (s *validatorSuite) TestResolveCharmExplicitBaseErrorWhenUserImageID(c *gc.C) { 407 defer s.setupMocks(c).Finish() 408 curl := charm.MustParseURL("testcharm") 409 resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4") 410 origin := corecharm.Origin{ 411 Source: "charm-hub", 412 Channel: &charm.Channel{Risk: "stable"}, 413 Platform: corecharm.Platform{Architecture: "amd64"}, 414 } 415 resolvedOrigin := corecharm.Origin{ 416 Source: "charm-hub", 417 Type: "charm", 418 Channel: &charm.Channel{Track: "default", Risk: "stable"}, 419 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04/stable"}, 420 Revision: intptr(4), 421 } 422 charmID := corecharm.CharmID{URL: curl, Origin: origin} 423 resolvedData := getResolvedData(resultURL, resolvedOrigin) 424 s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil) 425 s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig())) 426 s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("arm64")}, nil) 427 428 _, err := s.getValidator().resolveCharm(curl, origin, false, false, constraints.Value{ImageID: strptr("ubuntu-bf2")}) 429 c.Assert(err, gc.ErrorMatches, `base must be explicitly provided when image-id constraint is used`) 430 } 431 432 func (s *validatorSuite) TestResolveCharmExplicitBaseErrorWhenModelImageID(c *gc.C) { 433 defer s.setupMocks(c).Finish() 434 curl := charm.MustParseURL("testcharm") 435 resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4") 436 origin := corecharm.Origin{ 437 Source: "charm-hub", 438 Channel: &charm.Channel{Risk: "stable"}, 439 Platform: corecharm.Platform{Architecture: "amd64"}, 440 } 441 resolvedOrigin := corecharm.Origin{ 442 Source: "charm-hub", 443 Type: "charm", 444 Channel: &charm.Channel{Track: "default", Risk: "stable"}, 445 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04/stable"}, 446 Revision: intptr(4), 447 } 448 charmID := corecharm.CharmID{URL: curl, Origin: origin} 449 resolvedData := getResolvedData(resultURL, resolvedOrigin) 450 s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil) 451 s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig())) 452 s.state.EXPECT().ModelConstraints().Return(constraints.Value{ 453 Arch: strptr("arm64"), 454 ImageID: strptr("ubuntu-bf2"), 455 }, nil) 456 457 _, err := s.getValidator().resolveCharm(curl, origin, false, false, constraints.Value{}) 458 c.Assert(err, gc.ErrorMatches, `base must be explicitly provided when image-id constraint is used`) 459 } 460 461 func (s *validatorSuite) TestCreateOrigin(c *gc.C) { 462 defer s.setupMocks(c).Finish() 463 s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil) 464 s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig())) 465 466 arg := params.DeployFromRepositoryArg{ 467 CharmName: "testcharm", 468 Revision: intptr(7), 469 } 470 curl, origin, defaultBase, err := s.getValidator().createOrigin(arg) 471 c.Assert(err, jc.ErrorIsNil) 472 c.Assert(curl, gc.DeepEquals, charm.MustParseURL("ch:testcharm-7")) 473 c.Assert(origin, gc.DeepEquals, corecharm.Origin{ 474 Source: "charm-hub", 475 Revision: intptr(7), 476 Channel: &corecharm.DefaultChannel, 477 Platform: corecharm.Platform{Architecture: "amd64"}, 478 }) 479 c.Assert(defaultBase, jc.IsFalse) 480 } 481 482 func (s *validatorSuite) TestCreateOriginChannel(c *gc.C) { 483 defer s.setupMocks(c).Finish() 484 s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil) 485 s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig())) 486 487 arg := params.DeployFromRepositoryArg{ 488 CharmName: "testcharm", 489 Revision: intptr(7), 490 Channel: strptr("yoga/candidate"), 491 } 492 curl, origin, defaultBase, err := s.getValidator().createOrigin(arg) 493 c.Assert(err, jc.ErrorIsNil) 494 c.Assert(curl, gc.DeepEquals, charm.MustParseURL("ch:testcharm-7")) 495 expectedChannel := corecharm.MustParseChannel("yoga/candidate") 496 c.Assert(origin, gc.DeepEquals, corecharm.Origin{ 497 Source: "charm-hub", 498 Revision: intptr(7), 499 Channel: &expectedChannel, 500 Platform: corecharm.Platform{Architecture: "amd64"}, 501 }) 502 c.Assert(defaultBase, jc.IsFalse) 503 } 504 505 func (s *validatorSuite) TestGetCharm(c *gc.C) { 506 defer s.setupMocks(c).Finish() 507 s.expectSimpleValidate() 508 s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil) 509 // resolveCharm 510 curl := charm.MustParseURL("testcharm") 511 resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4") 512 origin := corecharm.Origin{ 513 Source: "charm-hub", 514 Channel: &charm.Channel{Risk: "stable"}, 515 Platform: corecharm.Platform{Architecture: "amd64"}, 516 } 517 resolvedOrigin := corecharm.Origin{ 518 Source: "charm-hub", 519 Type: "charm", 520 Channel: &charm.Channel{Track: "default", Risk: "stable"}, 521 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"}, 522 Revision: intptr(4), 523 } 524 charmID := corecharm.CharmID{URL: curl, Origin: origin} 525 resolvedData := getResolvedData(resultURL, resolvedOrigin) 526 s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil) 527 s.state.EXPECT().Charm(gomock.Any()).Return(nil, errors.NotFoundf("charm")) 528 // getCharm 529 530 arg := params.DeployFromRepositoryArg{ 531 CharmName: "testcharm", 532 } 533 obtainedURL, obtainedOrigin, obtainedCharm, err := s.getValidator().getCharm(arg) 534 c.Assert(err, jc.ErrorIsNil) 535 c.Assert(obtainedOrigin, gc.DeepEquals, resolvedOrigin) 536 c.Assert(obtainedCharm, gc.DeepEquals, corecharm.NewCharmInfoAdapter(resolvedData.EssentialMetadata)) 537 c.Assert(obtainedURL, gc.DeepEquals, resultURL) 538 } 539 540 func (s *validatorSuite) TestGetCharmAlreadyDeployed(c *gc.C) { 541 ctrl := s.setupMocks(c) 542 defer ctrl.Finish() 543 s.expectSimpleValidate() 544 s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil) 545 // resolveCharm 546 curl := charm.MustParseURL("testcharm") 547 resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4") 548 origin := corecharm.Origin{ 549 Source: "charm-hub", 550 Channel: &charm.Channel{Risk: "stable"}, 551 Platform: corecharm.Platform{Architecture: "amd64"}, 552 } 553 resolvedOrigin := corecharm.Origin{ 554 Source: "charm-hub", 555 Type: "charm", 556 Channel: &charm.Channel{Track: "default", Risk: "stable"}, 557 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"}, 558 Revision: intptr(4), 559 } 560 charmID := corecharm.CharmID{URL: curl, Origin: origin} 561 resolvedData := getResolvedData(resultURL, resolvedOrigin) 562 s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil) 563 ch := NewMockCharm(ctrl) 564 s.state.EXPECT().Charm(gomock.Any()).Return(ch, nil) 565 566 arg := params.DeployFromRepositoryArg{ 567 CharmName: "testcharm", 568 } 569 obtainedURL, obtainedOrigin, obtainedCharm, err := s.getValidator().getCharm(arg) 570 571 c.Assert(err, jc.ErrorIsNil) 572 c.Assert(obtainedOrigin, gc.DeepEquals, resolvedOrigin) 573 c.Assert(obtainedCharm, gc.NotNil) 574 c.Assert(obtainedURL, gc.DeepEquals, resultURL) 575 } 576 577 func (s *validatorSuite) TestGetCharmFindsBundle(c *gc.C) { 578 defer s.setupMocks(c).Finish() 579 s.expectSimpleValidate() 580 s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil) 581 // resolveCharm 582 curl := charm.MustParseURL("testcharm") 583 resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4") 584 origin := corecharm.Origin{ 585 Source: "charm-hub", 586 Channel: &charm.Channel{Risk: "stable"}, 587 Platform: corecharm.Platform{Architecture: "amd64"}, 588 } 589 resolvedOrigin := corecharm.Origin{ 590 Source: "charm-hub", 591 Type: "bundle", 592 Channel: &charm.Channel{Track: "default", Risk: "stable"}, 593 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"}, 594 Revision: intptr(4), 595 } 596 charmID := corecharm.CharmID{URL: curl, Origin: origin} 597 resolvedData := getResolvedData(resultURL, resolvedOrigin) 598 s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil) 599 arg := params.DeployFromRepositoryArg{ 600 CharmName: "testcharm", 601 } 602 _, _, _, err := s.getValidator().getCharm(arg) 603 c.Assert(errors.Is(err, errors.BadRequest), jc.IsTrue) 604 } 605 606 func (s *validatorSuite) TestGetCharmNoJujuControllerCharm(c *gc.C) { 607 defer s.setupMocks(c).Finish() 608 s.expectSimpleValidate() 609 s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil) 610 // resolveCharm 611 curl := charm.MustParseURL("testcharm") 612 resultURL := charm.MustParseURL("ch:amd64/jammy/juju-qa-test-4") 613 origin := corecharm.Origin{ 614 Source: "charm-hub", 615 Channel: &charm.Channel{Risk: "stable"}, 616 Platform: corecharm.Platform{Architecture: "amd64"}, 617 } 618 resolvedOrigin := corecharm.Origin{ 619 Source: "charm-hub", 620 Type: "charm", 621 Channel: &charm.Channel{Track: "default", Risk: "stable"}, 622 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"}, 623 Revision: intptr(4), 624 } 625 charmID := corecharm.CharmID{URL: curl, Origin: origin} 626 resolvedData := getResolvedData(resultURL, resolvedOrigin) 627 resolvedData.EssentialMetadata.Meta.Name = "juju-controller" 628 s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil) 629 630 arg := params.DeployFromRepositoryArg{ 631 CharmName: "testcharm", 632 } 633 _, _, _, err := s.getValidator().getCharm(arg) 634 c.Assert(errors.Is(err, errors.NotSupported), jc.IsTrue, gc.Commentf("%+v", err)) 635 } 636 637 func (s *validatorSuite) TestDeducePlatformSimple(c *gc.C) { 638 defer s.setupMocks(c).Finish() 639 //model constraint default 640 s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("amd64")}, nil) 641 s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig())) 642 643 arg := params.DeployFromRepositoryArg{CharmName: "testme"} 644 plat, usedModelDefaultBase, err := s.getValidator().deducePlatform(arg) 645 c.Assert(err, gc.IsNil) 646 c.Assert(usedModelDefaultBase, jc.IsFalse) 647 c.Assert(plat, gc.DeepEquals, corecharm.Platform{Architecture: "amd64"}) 648 } 649 650 func (s *validatorSuite) TestDeducePlatformRiskInChannel(c *gc.C) { 651 defer s.setupMocks(c).Finish() 652 //model constraint default 653 s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("amd64")}, nil) 654 655 arg := params.DeployFromRepositoryArg{ 656 CharmName: "testme", 657 Base: ¶ms.Base{ 658 Name: "ubuntu", 659 Channel: "22.10/stable", 660 }, 661 } 662 plat, usedModelDefaultBase, err := s.getValidator().deducePlatform(arg) 663 c.Assert(err, gc.IsNil) 664 c.Assert(usedModelDefaultBase, jc.IsFalse) 665 c.Assert(plat, gc.DeepEquals, corecharm.Platform{ 666 Architecture: "amd64", 667 OS: "ubuntu", 668 Channel: "22.10", 669 }) 670 } 671 672 func (s *validatorSuite) TestDeducePlatformArgArchBase(c *gc.C) { 673 defer s.setupMocks(c).Finish() 674 675 arg := params.DeployFromRepositoryArg{ 676 CharmName: "testme", 677 Cons: constraints.Value{Arch: strptr("arm64")}, 678 Base: ¶ms.Base{ 679 Name: "ubuntu", 680 Channel: "22.10", 681 }, 682 } 683 plat, usedModelDefaultBase, err := s.getValidator().deducePlatform(arg) 684 c.Assert(err, gc.IsNil) 685 c.Assert(usedModelDefaultBase, jc.IsFalse) 686 c.Assert(plat, gc.DeepEquals, corecharm.Platform{ 687 Architecture: "arm64", 688 OS: "ubuntu", 689 Channel: "22.10", 690 }) 691 } 692 693 func (s *validatorSuite) TestDeducePlatformModelDefaultBase(c *gc.C) { 694 defer s.setupMocks(c).Finish() 695 //model constraint default 696 s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil) 697 sConfig := coretesting.FakeConfig() 698 sConfig = sConfig.Merge(coretesting.Attrs{ 699 "default-base": "ubuntu@22.04", 700 }) 701 cfg, err := config.New(config.NoDefaults, sConfig) 702 c.Assert(err, jc.ErrorIsNil) 703 s.model.EXPECT().Config().Return(cfg, nil) 704 705 arg := params.DeployFromRepositoryArg{ 706 CharmName: "testme", 707 } 708 plat, usedModelDefaultBase, err := s.getValidator().deducePlatform(arg) 709 c.Assert(err, gc.IsNil) 710 c.Assert(usedModelDefaultBase, jc.IsTrue) 711 c.Assert(plat, gc.DeepEquals, corecharm.Platform{ 712 Architecture: "amd64", 713 OS: "ubuntu", 714 Channel: "22.04", 715 }) 716 } 717 718 func (s *validatorSuite) TestDeducePlatformPlacementSimpleFound(c *gc.C) { 719 defer s.setupMocks(c).Finish() 720 s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil) 721 s.state.EXPECT().Machine("0").Return(s.machine, nil) 722 s.machine.EXPECT().Base().Return(state.Base{ 723 OS: "ubuntu", 724 Channel: "18.04", 725 }) 726 hwc := &instance.HardwareCharacteristics{Arch: strptr("arm64")} 727 s.machine.EXPECT().HardwareCharacteristics().Return(hwc, nil) 728 729 arg := params.DeployFromRepositoryArg{ 730 CharmName: "testme", 731 Placement: []*instance.Placement{{ 732 Directive: "0", 733 }}, 734 } 735 plat, usedModelDefaultBase, err := s.getValidator().deducePlatform(arg) 736 c.Assert(err, gc.IsNil) 737 c.Assert(usedModelDefaultBase, jc.IsFalse) 738 c.Assert(plat, gc.DeepEquals, corecharm.Platform{ 739 Architecture: "arm64", 740 OS: "ubuntu", 741 Channel: "18.04", 742 }) 743 } 744 745 func (s *validatorSuite) TestDeducePlatformPlacementSimpleNotFound(c *gc.C) { 746 defer s.setupMocks(c).Finish() 747 //model constraint default 748 s.state.EXPECT().ModelConstraints().Return(constraints.Value{Arch: strptr("amd64")}, nil) 749 s.model.EXPECT().Config().Return(config.New(config.UseDefaults, coretesting.FakeConfig())) 750 s.state.EXPECT().Machine("0/lxd/0").Return(nil, errors.NotFoundf("machine 0/lxd/0 not found")) 751 752 arg := params.DeployFromRepositoryArg{ 753 CharmName: "testme", 754 Placement: []*instance.Placement{{ 755 Directive: "0/lxd/0", 756 }}, 757 } 758 plat, usedModelDefaultBase, err := s.getValidator().deducePlatform(arg) 759 c.Assert(err, gc.IsNil) 760 c.Assert(usedModelDefaultBase, jc.IsFalse) 761 c.Assert(plat, gc.DeepEquals, corecharm.Platform{Architecture: "amd64"}) 762 } 763 764 func (s *validatorSuite) TestResolvedCharmValidationSubordinate(c *gc.C) { 765 ctrl := s.setupMocks(c) 766 defer ctrl.Finish() 767 ch := NewMockCharm(ctrl) 768 meta := &charm.Meta{ 769 Name: "testcharm", 770 Subordinate: true, 771 } 772 ch.EXPECT().Config().Return(nil) 773 ch.EXPECT().Meta().Return(meta).AnyTimes() 774 arg := params.DeployFromRepositoryArg{ 775 NumUnits: intptr(1), 776 } 777 dt, err := s.getValidator().resolvedCharmValidation(ch, arg) 778 c.Assert(err, gc.HasLen, 0) 779 c.Assert(dt.numUnits, gc.Equals, 0) 780 } 781 782 func (s *validatorSuite) TestDeducePlatformPlacementMutipleMatch(c *gc.C) { 783 defer s.setupMocks(c).Finish() 784 s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil) 785 s.state.EXPECT().Machine(gomock.Any()).Return(s.machine, nil).Times(3) 786 s.machine.EXPECT().Base().Return(state.Base{ 787 OS: "ubuntu", 788 Channel: "18.04", 789 }).Times(3) 790 hwc := &instance.HardwareCharacteristics{Arch: strptr("arm64")} 791 s.machine.EXPECT().HardwareCharacteristics().Return(hwc, nil).Times(3) 792 793 arg := params.DeployFromRepositoryArg{ 794 CharmName: "testme", 795 Placement: []*instance.Placement{ 796 {Directive: "0"}, 797 {Directive: "1"}, 798 {Directive: "3"}, 799 }, 800 } 801 plat, usedModelDefaultBase, err := s.getValidator().deducePlatform(arg) 802 c.Assert(err, gc.IsNil) 803 c.Assert(usedModelDefaultBase, jc.IsFalse) 804 c.Assert(plat, gc.DeepEquals, corecharm.Platform{ 805 Architecture: "arm64", 806 OS: "ubuntu", 807 Channel: "18.04", 808 }) 809 } 810 811 func (s *validatorSuite) TestDeducePlatformPlacementMutipleMatchFail(c *gc.C) { 812 defer s.setupMocks(c).Finish() 813 s.state.EXPECT().ModelConstraints().Return(constraints.Value{}, nil) 814 s.state.EXPECT().Machine(gomock.Any()).Return(s.machine, nil).AnyTimes() 815 s.machine.EXPECT().Base().Return( 816 state.Base{ 817 OS: "ubuntu", 818 Channel: "18.04", 819 }).AnyTimes() 820 gomock.InOrder( 821 s.machine.EXPECT().HardwareCharacteristics().Return( 822 &instance.HardwareCharacteristics{Arch: strptr("arm64")}, 823 nil), 824 s.machine.EXPECT().HardwareCharacteristics().Return( 825 &instance.HardwareCharacteristics{Arch: strptr("amd64")}, 826 nil), 827 ) 828 829 arg := params.DeployFromRepositoryArg{ 830 CharmName: "testme", 831 Placement: []*instance.Placement{ 832 {Directive: "0"}, 833 {Directive: "1"}, 834 }, 835 } 836 _, _, err := s.getValidator().deducePlatform(arg) 837 c.Assert(errors.Is(err, errors.BadRequest), jc.IsTrue, gc.Commentf("%+v", err)) 838 } 839 840 var configYaml = ` 841 testme: 842 optionOne: one 843 optionTwo: 8 844 `[1:] 845 846 func (s *validatorSuite) TestAppCharmSettings(c *gc.C) { 847 defer s.setupMocks(c).Finish() 848 s.model.EXPECT().Type().Return(state.ModelTypeIAAS) 849 850 cfg := charm.NewConfig() 851 cfg.Options = map[string]charm.Option{ 852 "optionOne": { 853 Type: "string", 854 Description: "option one", 855 }, 856 "optionTwo": { 857 Type: "int", 858 Description: "option two", 859 }, 860 } 861 862 appCfgSchema, _, err := applicationConfigSchema(state.ModelTypeIAAS) 863 c.Assert(err, jc.ErrorIsNil) 864 865 expectedAppConfig, err := coreconfig.NewConfig(map[string]interface{}{"trust": true}, appCfgSchema, nil) 866 c.Assert(err, jc.ErrorIsNil) 867 868 appConfig, charmConfig, err := s.getValidator().appCharmSettings("testme", true, cfg, configYaml) 869 c.Assert(err, jc.ErrorIsNil) 870 c.Check(appConfig, gc.DeepEquals, expectedAppConfig) 871 c.Assert(charmConfig["optionOne"], gc.DeepEquals, "one") 872 c.Assert(charmConfig["optionTwo"], gc.DeepEquals, int64(8)) 873 } 874 875 // The purpose of the resolveResourcesArgsMatcher is 876 // to compare the slices of resource.Resource, b/c the 877 // order is non-deterministic. 878 type resolveResourcesArgsMatcher struct { 879 c *gc.C 880 expected *[]resource.Resource 881 } 882 883 func (m resolveResourcesArgsMatcher) String() string { 884 return "match ResolveResources arg map" 885 } 886 887 func (m resolveResourcesArgsMatcher) Matches(x interface{}) bool { 888 obtainedSlice, ok := x.([]resource.Resource) 889 if !ok { 890 return false 891 } 892 893 m.c.Assert(obtainedSlice, gc.HasLen, len(*m.expected)) 894 // Unfortunately the jc.SameContents don't work here 895 // because resource.Resource is unhashable 896 for _, r := range obtainedSlice { 897 found := false 898 for _, exR := range *m.expected { 899 if reflect.DeepEqual(r, exR) { 900 found = true 901 break 902 } 903 } 904 m.c.Assert(found, gc.Equals, true) 905 } 906 return true 907 } 908 909 func (s *validatorSuite) TestResolveResourcesSuccess(c *gc.C) { 910 defer s.setupMocks(c).Finish() 911 curl := charm.MustParseURL("testcharm") 912 origin := corecharm.Origin{ 913 Source: "charm-hub", 914 Channel: &charm.Channel{Risk: "stable"}, 915 Platform: corecharm.Platform{Architecture: "amd64"}, 916 } 917 // Resource 1 : file upload from client 918 meta1 := resource.Meta{ 919 Name: "foo-resource", 920 Type: resource.TypeFile, 921 Path: "foo.txt", 922 Description: "bar", 923 } 924 res := resource.Resource{ 925 Meta: meta1, 926 Origin: resource.OriginUpload, 927 Revision: -1, 928 } 929 // Resource 2 : store resource with --resource <revision> flag 930 meta2 := resource.Meta{ 931 Name: "foo-resource2", 932 Type: resource.TypeFile, 933 Path: "foo.txt", 934 Description: "bar", 935 } 936 res2 := resource.Resource{ 937 Meta: meta2, 938 Origin: resource.OriginStore, 939 Revision: 3, 940 } 941 // Resource 3 : store resource without the --resource flag 942 // (revision is reported by the store) 943 meta3 := resource.Meta{ 944 Name: "foo-resource3", 945 Type: resource.TypeFile, 946 Path: "foo.txt", 947 Description: "bar", 948 } 949 res3 := resource.Resource{ 950 Meta: meta3, 951 Origin: resource.OriginStore, 952 Revision: -1, 953 } 954 955 resMeta := map[string]resource.Meta{"foo-file": meta1, "foo-file2": meta2, "store-file-res": meta3} 956 resArgs := []resource.Resource{res, res2, res3} 957 // Note that for the Resource 3, in the args res3 has revision -1, and the result below has revision 4 958 r4 := resource.Resource{ 959 Meta: meta3, 960 Origin: resource.OriginStore, 961 Revision: 4, 962 } 963 resResult := []resource.Resource{res, res2, r4} 964 // First one of below is the file upload for Resource 1, the second is the revision for Resource 2e 965 deployResArg := map[string]string{"foo-file": "bar", "foo-file2": "3"} 966 967 s.repo.EXPECT().ResolveResources(resolveResourcesArgsMatcher{c: c, expected: &resArgs}, corecharm.CharmID{URL: curl, Origin: origin}).Return(resResult, nil) 968 resources, pendingResourceUploads, resolveResErr := s.getValidator().resolveResources(curl, origin, deployResArg, resMeta) 969 pendUp := ¶ms.PendingResourceUpload{ 970 Name: "foo-resource", 971 Type: "file", 972 Filename: "bar", 973 } 974 c.Assert(resolveResErr, jc.ErrorIsNil) 975 c.Assert(resources, gc.DeepEquals, resResult) 976 c.Assert(pendingResourceUploads, gc.DeepEquals, []*params.PendingResourceUpload{pendUp}) 977 } 978 979 func (s *validatorSuite) TestCaasDeployFromRepositoryValidator(c *gc.C) { 980 defer s.setupMocks(c).Finish() 981 s.expectSimpleValidate() 982 // resolveCharm 983 curl := charm.MustParseURL("testcharm") 984 resultURL := charm.MustParseURL("ch:amd64/jammy/testcharm-4") 985 origin := corecharm.Origin{ 986 Source: "charm-hub", 987 Channel: &charm.Channel{Risk: "stable"}, 988 Platform: corecharm.Platform{Architecture: "amd64"}, 989 } 990 resolvedOrigin := corecharm.Origin{ 991 Source: "charm-hub", 992 Type: "charm", 993 Channel: &charm.Channel{Track: "default", Risk: "stable"}, 994 Platform: corecharm.Platform{Architecture: "amd64", OS: "ubuntu", Channel: "22.04"}, 995 Revision: intptr(4), 996 } 997 charmID := corecharm.CharmID{URL: curl, Origin: origin} 998 resolvedData := getResolvedData(resultURL, resolvedOrigin) 999 s.repo.EXPECT().ResolveForDeploy(charmID).Return(resolvedData, nil) 1000 s.repo.EXPECT().ResolveResources(nil, corecharm.CharmID{URL: resultURL, Origin: resolvedOrigin}).Return(nil, nil) 1001 s.state.EXPECT().Charm(gomock.Any()).Return(nil, errors.NotFoundf("charm")) 1002 s.state.EXPECT().ModelConstraints().Return(constraints.Value{ 1003 Arch: strptr("arm64"), 1004 }, nil) 1005 s.model.EXPECT().UUID().Return("") 1006 1007 arg := params.DeployFromRepositoryArg{ 1008 CharmName: "testcharm", 1009 } 1010 1011 obtainedDT, errs := s.caasDeployFromRepositoryValidator(c).ValidateArg(arg) 1012 c.Assert(errs, gc.HasLen, 0) 1013 c.Assert(obtainedDT, gc.DeepEquals, deployTemplate{ 1014 applicationName: "test-charm", 1015 charm: corecharm.NewCharmInfoAdapter(resolvedData.EssentialMetadata), 1016 charmURL: resultURL, 1017 numUnits: 1, 1018 origin: resolvedOrigin, 1019 }) 1020 } 1021 1022 func (s *validatorSuite) TestIaaSDeployFromRepositoryFailResolveCharm(c *gc.C) { 1023 defer s.setupMocks(c).Finish() 1024 s.expectSimpleValidate() 1025 s.repo.EXPECT().ResolveForDeploy(gomock.Any()).Return(corecharm.ResolvedDataForDeploy{}, fmt.Errorf("fail resolve")) 1026 s.model.EXPECT().UUID().Return("") 1027 1028 arg := params.DeployFromRepositoryArg{ 1029 CharmName: "testcharm", 1030 } 1031 1032 _, errs := s.iaasDeployFromRepositoryValidator().ValidateArg(arg) 1033 c.Assert(errs, gc.HasLen, 1) 1034 } 1035 1036 func (s *validatorSuite) TestCaaSDeployFromRepositoryFailResolveCharm(c *gc.C) { 1037 defer s.setupMocks(c).Finish() 1038 s.expectSimpleValidate() 1039 s.repo.EXPECT().ResolveForDeploy(gomock.Any()).Return(corecharm.ResolvedDataForDeploy{}, fmt.Errorf("fail resolve")) 1040 s.model.EXPECT().UUID().Return("") 1041 1042 arg := params.DeployFromRepositoryArg{ 1043 CharmName: "testcharm", 1044 } 1045 1046 _, errs := s.caasDeployFromRepositoryValidator(c).ValidateArg(arg) 1047 c.Assert(errs, gc.HasLen, 1) 1048 } 1049 1050 func getResolvedData(resultURL *charm.URL, resolvedOrigin corecharm.Origin) corecharm.ResolvedDataForDeploy { 1051 expMeta := &charm.Meta{ 1052 Name: "test-charm", 1053 } 1054 expManifest := &charm.Manifest{Bases: []charm.Base{ 1055 {Name: "ubuntu", Channel: charm.Channel{Track: "22.04", Risk: "stable"}}, 1056 {Name: "ubuntu", Channel: charm.Channel{Track: "20.04", Risk: "stable"}}, 1057 }} 1058 expConfig := new(charm.Config) 1059 essMeta := corecharm.EssentialMetadata{ 1060 Meta: expMeta, 1061 Manifest: expManifest, 1062 Config: expConfig, 1063 ResolvedOrigin: resolvedOrigin, 1064 } 1065 return corecharm.ResolvedDataForDeploy{ 1066 URL: resultURL, 1067 EssentialMetadata: essMeta, 1068 Resources: nil, 1069 } 1070 } 1071 1072 func (s *validatorSuite) setupMocks(c *gc.C) *gomock.Controller { 1073 ctrl := gomock.NewController(c) 1074 s.bindings = NewMockBindings(ctrl) 1075 s.machine = NewMockMachine(ctrl) 1076 s.model = NewMockModel(ctrl) 1077 s.repo = NewMockRepository(ctrl) 1078 s.repoFactory = NewMockRepositoryFactory(ctrl) 1079 s.state = NewMockDeployFromRepositoryState(ctrl) 1080 return ctrl 1081 } 1082 1083 func (s *validatorSuite) getValidator() *deployFromRepositoryValidator { 1084 s.repoFactory.EXPECT().GetCharmRepository(gomock.Any()).Return(s.repo, nil).AnyTimes() 1085 return &deployFromRepositoryValidator{ 1086 model: s.model, 1087 state: s.state, 1088 repoFactory: s.repoFactory, 1089 newStateBindings: func(st state.EndpointBinding, givenMap map[string]string) (Bindings, error) { 1090 return s.bindings, nil 1091 }, 1092 } 1093 } 1094 1095 func (s *validatorSuite) caasDeployFromRepositoryValidator(c *gc.C) caasDeployFromRepositoryValidator { 1096 return caasDeployFromRepositoryValidator{ 1097 validator: s.getValidator(), 1098 caasPrecheckFunc: func(dt deployTemplate) error { 1099 // Do a quick check to ensure the expected deployTemplate 1100 // has been passed. 1101 c.Assert(dt.applicationName, gc.Equals, "test-charm") 1102 return nil 1103 }, 1104 } 1105 } 1106 1107 func (s *validatorSuite) iaasDeployFromRepositoryValidator() iaasDeployFromRepositoryValidator { 1108 return iaasDeployFromRepositoryValidator{ 1109 validator: s.getValidator(), 1110 } 1111 } 1112 1113 func strptr(s string) *string { 1114 return &s 1115 } 1116 1117 func intptr(i int) *int { 1118 return &i 1119 } 1120 1121 type deployRepositorySuite struct { 1122 application *MockApplication 1123 charm *MockCharm 1124 state *MockDeployFromRepositoryState 1125 validator *MockDeployFromRepositoryValidator 1126 } 1127 1128 func (s *deployRepositorySuite) TestDeployFromRepositoryAPI(c *gc.C) { 1129 defer s.setupMocks(c).Finish() 1130 arg := params.DeployFromRepositoryArg{ 1131 CharmName: "testme", 1132 } 1133 template := deployTemplate{ 1134 applicationName: "metadata-name", 1135 charm: corecharm.NewCharmInfoAdapter(corecharm.EssentialMetadata{}), 1136 charmURL: charm.MustParseURL("ch:amd64/jammy/testme-5"), 1137 endpoints: map[string]string{"to": "from"}, 1138 numUnits: 1, 1139 origin: corecharm.Origin{ 1140 Source: "charm-hub", 1141 Revision: intptr(5), 1142 Channel: &charm.Channel{Risk: "stable"}, 1143 Platform: corecharm.MustParsePlatform("amd64/ubuntu/22.04"), 1144 }, 1145 placement: []*instance.Placement{{Directive: "0", Scope: instance.MachineScope}}, 1146 } 1147 s.validator.EXPECT().ValidateArg(arg).Return(template, nil) 1148 info := state.CharmInfo{ 1149 Charm: template.charm, 1150 ID: "ch:amd64/jammy/testme-5", 1151 } 1152 1153 s.state.EXPECT().AddCharmMetadata(info).Return(s.charm, nil) 1154 1155 addAppArgs := state.AddApplicationArgs{ 1156 Name: "metadata-name", 1157 // the app.Charm is casted into a state.Charm in the code 1158 // we mock it separately here (s.charm above), the test works 1159 // thanks to the addApplicationArgsMatcher used below 1160 Charm: &state.Charm{}, 1161 CharmOrigin: &state.CharmOrigin{ 1162 Source: "charm-hub", 1163 Revision: intptr(5), 1164 Channel: &state.Channel{ 1165 Risk: "stable", 1166 }, 1167 Platform: &state.Platform{ 1168 Architecture: "amd64", 1169 OS: "ubuntu", 1170 Channel: "22.04", 1171 }, 1172 }, 1173 Devices: map[string]state.DeviceConstraints{}, 1174 EndpointBindings: map[string]string{"to": "from"}, 1175 NumUnits: 1, 1176 Placement: []*instance.Placement{{Directive: "0", Scope: instance.MachineScope}}, 1177 Resources: map[string]string{}, 1178 Storage: map[string]state.StorageConstraints{}, 1179 } 1180 s.state.EXPECT().AddApplication(addApplicationArgsMatcher{c: c, expectedArgs: addAppArgs}).Return(s.application, nil) 1181 1182 deployFromRepositoryAPI := s.getDeployFromRepositoryAPI() 1183 1184 obtainedInfo, resources, errs := deployFromRepositoryAPI.DeployFromRepository(arg) 1185 c.Assert(errs, gc.HasLen, 0) 1186 c.Assert(resources, gc.HasLen, 0) 1187 c.Assert(obtainedInfo, gc.DeepEquals, params.DeployFromRepositoryInfo{ 1188 Architecture: "amd64", 1189 Base: params.Base{Name: "ubuntu", Channel: "22.04"}, 1190 Channel: "stable", 1191 EffectiveChannel: nil, 1192 Name: "metadata-name", 1193 Revision: 5, 1194 }) 1195 } 1196 1197 // The reason for this matcher is that the AddApplicationArgs.Charm is 1198 // obtained by casting application.Charm into a state.Charm, but we 1199 // can't do that cast with a MockCharm 1200 type addApplicationArgsMatcher struct { 1201 c *gc.C 1202 expectedArgs state.AddApplicationArgs 1203 } 1204 1205 func (m addApplicationArgsMatcher) String() string { 1206 return "match AddApplicationArgs" 1207 } 1208 1209 func (m addApplicationArgsMatcher) Matches(x interface{}) bool { 1210 1211 oA, ok := x.(state.AddApplicationArgs) 1212 if !ok { 1213 return false 1214 } 1215 1216 eA := m.expectedArgs 1217 // Check everything but the Charm 1218 m.c.Assert(oA.Name, gc.DeepEquals, eA.Name) 1219 m.c.Assert(oA.ApplicationConfig, gc.DeepEquals, eA.ApplicationConfig) 1220 m.c.Assert(oA.NumUnits, gc.DeepEquals, eA.NumUnits) 1221 m.c.Assert(oA.Constraints, gc.DeepEquals, eA.Constraints) 1222 m.c.Assert(oA.Storage, gc.DeepEquals, eA.Storage) 1223 m.c.Assert(oA.Devices, gc.DeepEquals, eA.Devices) 1224 m.c.Assert(eA.AttachStorage, gc.DeepEquals, eA.AttachStorage) 1225 m.c.Assert(oA.EndpointBindings, gc.DeepEquals, eA.EndpointBindings) 1226 m.c.Assert(oA.CharmConfig, gc.DeepEquals, eA.CharmConfig) 1227 m.c.Assert(oA.Placement, gc.DeepEquals, eA.Placement) 1228 m.c.Assert(oA.Resources, gc.DeepEquals, eA.Resources) 1229 return true 1230 } 1231 1232 func (s *deployRepositorySuite) TestAddPendingResourcesForDeployFromRepositoryAPI(c *gc.C) { 1233 defer s.setupMocks(c).Finish() 1234 arg := params.DeployFromRepositoryArg{ 1235 CharmName: "testme", 1236 } 1237 pendUp := ¶ms.PendingResourceUpload{ 1238 Name: "foo-resource", 1239 Type: "file", 1240 Filename: "bar", 1241 } 1242 meta := resource.Meta{ 1243 Name: "foo-resource", 1244 Type: resource.TypeFile, 1245 Path: "foo.txt", 1246 Description: "bar", 1247 } 1248 r := resource.Resource{ 1249 Meta: meta, 1250 Origin: resource.OriginUpload, 1251 } 1252 1253 template := deployTemplate{ 1254 applicationName: "metadata-name", 1255 charm: corecharm.NewCharmInfoAdapter(corecharm.EssentialMetadata{}), 1256 charmURL: charm.MustParseURL("ch:amd64/jammy/testme-5"), 1257 endpoints: map[string]string{"to": "from"}, 1258 numUnits: 1, 1259 origin: corecharm.Origin{ 1260 Source: "charm-hub", 1261 Revision: intptr(5), 1262 Channel: &charm.Channel{Risk: "stable"}, 1263 Platform: corecharm.MustParsePlatform("amd64/ubuntu/22.04"), 1264 }, 1265 placement: []*instance.Placement{{Directive: "0", Scope: instance.MachineScope}}, 1266 resources: map[string]string{"foo-file": "bar"}, 1267 pendingResourceUploads: []*params.PendingResourceUpload{pendUp}, 1268 resolvedResources: []resource.Resource{r}, 1269 } 1270 s.validator.EXPECT().ValidateArg(arg).Return(template, nil) 1271 info := state.CharmInfo{ 1272 Charm: template.charm, 1273 ID: "ch:amd64/jammy/testme-5", 1274 } 1275 1276 s.state.EXPECT().AddCharmMetadata(info).Return(s.charm, nil) 1277 1278 s.state.EXPECT().AddPendingResource("metadata-name", r).Return("3", nil) 1279 1280 addAppArgs := state.AddApplicationArgs{ 1281 Name: "metadata-name", 1282 // the app.Charm is casted into a state.Charm in the code 1283 // we mock it separately here (s.charm above), the test works 1284 // thanks to the addApplicationArgsMatcher used below 1285 Charm: &state.Charm{}, 1286 CharmOrigin: &state.CharmOrigin{ 1287 Source: "charm-hub", 1288 Revision: intptr(5), 1289 Channel: &state.Channel{ 1290 Risk: "stable", 1291 }, 1292 Platform: &state.Platform{ 1293 Architecture: "amd64", 1294 OS: "ubuntu", 1295 Channel: "22.04", 1296 }, 1297 }, 1298 Devices: map[string]state.DeviceConstraints{}, 1299 EndpointBindings: map[string]string{"to": "from"}, 1300 NumUnits: 1, 1301 Placement: []*instance.Placement{{Directive: "0", Scope: instance.MachineScope}}, 1302 Resources: map[string]string{"foo-resource": "3"}, 1303 Storage: map[string]state.StorageConstraints{}, 1304 } 1305 s.state.EXPECT().AddApplication(addApplicationArgsMatcher{c: c, expectedArgs: addAppArgs}).Return(s.application, nil) 1306 1307 deployFromRepositoryAPI := s.getDeployFromRepositoryAPI() 1308 1309 obtainedInfo, resources, errs := deployFromRepositoryAPI.DeployFromRepository(arg) 1310 c.Assert(errs, gc.HasLen, 0) 1311 c.Assert(resources, gc.HasLen, 1) 1312 c.Assert(obtainedInfo, gc.DeepEquals, params.DeployFromRepositoryInfo{ 1313 Architecture: "amd64", 1314 Base: params.Base{Name: "ubuntu", Channel: "22.04"}, 1315 Channel: "stable", 1316 EffectiveChannel: nil, 1317 Name: "metadata-name", 1318 Revision: 5, 1319 }) 1320 1321 c.Assert(resources, gc.DeepEquals, []*params.PendingResourceUpload{pendUp}) 1322 } 1323 1324 func (s *deployRepositorySuite) TestRemovePendingResourcesWhenDeployErrors(c *gc.C) { 1325 defer s.setupMocks(c).Finish() 1326 arg := params.DeployFromRepositoryArg{ 1327 CharmName: "testme", 1328 } 1329 pendUp := ¶ms.PendingResourceUpload{ 1330 Name: "foo-resource", 1331 Type: "file", 1332 Filename: "bar", 1333 } 1334 meta := resource.Meta{ 1335 Name: "foo-resource", 1336 Type: resource.TypeFile, 1337 Path: "foo.txt", 1338 Description: "bar", 1339 } 1340 r := resource.Resource{ 1341 Meta: meta, 1342 Origin: resource.OriginUpload, 1343 } 1344 template := deployTemplate{ 1345 applicationName: "metadata-name", 1346 charm: corecharm.NewCharmInfoAdapter(corecharm.EssentialMetadata{}), 1347 charmURL: charm.MustParseURL("ch:amd64/jammy/testme-5"), 1348 endpoints: map[string]string{"to": "from"}, 1349 numUnits: 1, 1350 origin: corecharm.Origin{ 1351 Source: "charm-hub", 1352 Revision: intptr(5), 1353 Channel: &charm.Channel{Risk: "stable"}, 1354 Platform: corecharm.MustParsePlatform("amd64/ubuntu/22.04"), 1355 }, 1356 placement: []*instance.Placement{{Directive: "0", Scope: instance.MachineScope}}, 1357 resources: map[string]string{"foo-file": "bar"}, 1358 pendingResourceUploads: []*params.PendingResourceUpload{pendUp}, 1359 resolvedResources: []resource.Resource{r}, 1360 } 1361 s.validator.EXPECT().ValidateArg(arg).Return(template, nil) 1362 info := state.CharmInfo{ 1363 Charm: template.charm, 1364 ID: "ch:amd64/jammy/testme-5", 1365 } 1366 1367 s.state.EXPECT().AddCharmMetadata(info).Return(s.charm, nil) 1368 1369 s.state.EXPECT().AddPendingResource("metadata-name", r).Return("3", nil) 1370 1371 addAppArgs := state.AddApplicationArgs{ 1372 Name: "metadata-name", 1373 // the app.Charm is casted into a state.Charm in the code 1374 // we mock it separately here (s.charm above), the test works 1375 // thanks to the addApplicationArgsMatcher used below 1376 Charm: &state.Charm{}, 1377 CharmOrigin: &state.CharmOrigin{ 1378 Source: "charm-hub", 1379 Revision: intptr(5), 1380 Channel: &state.Channel{ 1381 Risk: "stable", 1382 }, 1383 Platform: &state.Platform{ 1384 Architecture: "amd64", 1385 OS: "ubuntu", 1386 Channel: "22.04", 1387 }, 1388 }, 1389 Devices: map[string]state.DeviceConstraints{}, 1390 EndpointBindings: map[string]string{"to": "from"}, 1391 NumUnits: 1, 1392 Placement: []*instance.Placement{{Directive: "0", Scope: instance.MachineScope}}, 1393 Resources: map[string]string{"foo-resource": "3"}, 1394 Storage: map[string]state.StorageConstraints{}, 1395 } 1396 1397 s.state.EXPECT().RemovePendingResources("metadata-name", map[string]string{"foo-resource": "3"}) 1398 1399 s.state.EXPECT().AddApplication(addApplicationArgsMatcher{c: c, expectedArgs: addAppArgs}).Return(s.application, 1400 errors.New("fail")) 1401 1402 deployFromRepositoryAPI := s.getDeployFromRepositoryAPI() 1403 1404 obtainedInfo, resources, errs := deployFromRepositoryAPI.DeployFromRepository(arg) 1405 c.Assert(errs, gc.HasLen, 1) 1406 c.Assert(resources, gc.HasLen, 0) 1407 c.Assert(obtainedInfo, gc.DeepEquals, params.DeployFromRepositoryInfo{}) 1408 } 1409 1410 func (s *deployRepositorySuite) getDeployFromRepositoryAPI() *DeployFromRepositoryAPI { 1411 return &DeployFromRepositoryAPI{ 1412 state: s.state, 1413 validator: s.validator, 1414 stateCharm: func(Charm) *state.Charm { return nil }, 1415 } 1416 } 1417 1418 func (s *deployRepositorySuite) setupMocks(c *gc.C) *gomock.Controller { 1419 ctrl := gomock.NewController(c) 1420 s.charm = NewMockCharm(ctrl) 1421 s.state = NewMockDeployFromRepositoryState(ctrl) 1422 s.validator = NewMockDeployFromRepositoryValidator(ctrl) 1423 return ctrl 1424 }