github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/core/charm/baseselector_test.go (about)

     1  // Copyright 2023 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package charm
     5  
     6  import (
     7  	"github.com/juju/collections/set"
     8  	"github.com/juju/testing"
     9  	jc "github.com/juju/testing/checkers"
    10  	"go.uber.org/mock/gomock"
    11  	gc "gopkg.in/check.v1"
    12  
    13  	"github.com/juju/juju/core/base"
    14  	"github.com/juju/juju/version"
    15  )
    16  
    17  type baseSelectorSuite struct {
    18  	testing.IsolationSuite
    19  
    20  	logger *MockSelectorLogger
    21  	cfg    *MockSelectorModelConfig
    22  }
    23  
    24  var _ = gc.Suite(&baseSelectorSuite{})
    25  
    26  var (
    27  	bionic      = base.MustParseBaseFromString("ubuntu@18.04/stable")
    28  	cosmic      = base.MustParseBaseFromString("ubuntu@18.10/stable")
    29  	disco       = base.MustParseBaseFromString("ubuntu@19.04")
    30  	jammy       = base.MustParseBaseFromString("ubuntu@22.04")
    31  	focal       = base.MustParseBaseFromString("ubuntu@20.04")
    32  	precise     = base.MustParseBaseFromString("ubuntu@14.04")
    33  	utopic      = base.MustParseBaseFromString("ubuntu@16.10")
    34  	vivid       = base.MustParseBaseFromString("ubuntu@17.04")
    35  	latest      = base.LatestLTSBase()
    36  	jujuDefault = version.DefaultSupportedLTSBase()
    37  )
    38  
    39  func (s *baseSelectorSuite) setup(c *gc.C) *gomock.Controller {
    40  	ctrl := gomock.NewController(c)
    41  	s.logger = NewMockSelectorLogger(ctrl)
    42  	s.logger.EXPECT().Infof(gomock.Any(), gomock.Any()).AnyTimes()
    43  	s.logger.EXPECT().Tracef(gomock.Any(), gomock.Any()).AnyTimes()
    44  
    45  	s.cfg = NewMockSelectorModelConfig(ctrl)
    46  
    47  	return ctrl
    48  }
    49  
    50  func (s *baseSelectorSuite) TestCharmBase(c *gc.C) {
    51  	defer s.setup(c).Finish()
    52  
    53  	deployBasesTests := []struct {
    54  		title        string
    55  		selector     BaseSelector
    56  		expectedBase base.Base
    57  		err          string
    58  	}{
    59  		{
    60  			title: "juju deploy simple   # default base set",
    61  			selector: BaseSelector{
    62  				defaultBase:         focal,
    63  				explicitDefaultBase: true,
    64  				supportedBases:      []base.Base{bionic, focal},
    65  			},
    66  			expectedBase: focal,
    67  		},
    68  		{
    69  			title: "juju deploy simple --base=ubuntu@14.04   # no supported base",
    70  			selector: BaseSelector{
    71  				requestedBase:  precise,
    72  				supportedBases: []base.Base{bionic, cosmic},
    73  			},
    74  			err: `base: ubuntu@14.04/stable`,
    75  		},
    76  		{
    77  			title: "juju deploy simple --base=ubuntu@18.04   # user provided base takes precedence over default base ",
    78  			selector: BaseSelector{
    79  				requestedBase:       bionic,
    80  				defaultBase:         focal,
    81  				explicitDefaultBase: true,
    82  				supportedBases:      []base.Base{bionic, focal, cosmic},
    83  			},
    84  			expectedBase: bionic,
    85  		},
    86  
    87  		// Now charms with supported base.
    88  
    89  		{
    90  			title: "juju deploy multiseries   # use charm default, nothing specified, no default base",
    91  			selector: BaseSelector{
    92  				supportedBases: []base.Base{bionic, cosmic},
    93  			},
    94  			expectedBase: bionic,
    95  		},
    96  		{
    97  			title: "juju deploy multiseries   # use charm defaults used if default base doesn't match, nothing specified",
    98  			selector: BaseSelector{
    99  				supportedBases:      []base.Base{bionic, cosmic},
   100  				defaultBase:         precise,
   101  				explicitDefaultBase: true,
   102  			},
   103  			err: `base: ubuntu@14.04/stable`,
   104  		},
   105  		{
   106  			title: "juju deploy multiseries   # use model base defaults if supported by charm",
   107  			selector: BaseSelector{
   108  				supportedBases:      []base.Base{bionic, cosmic, disco},
   109  				defaultBase:         disco,
   110  				explicitDefaultBase: true,
   111  			},
   112  			expectedBase: disco,
   113  		},
   114  		{
   115  			title: "juju deploy multiseries --base=ubuntu@18.04   # use supported requested",
   116  			selector: BaseSelector{
   117  				requestedBase:  bionic,
   118  				supportedBases: []base.Base{cosmic, bionic},
   119  			},
   120  			expectedBase: bionic,
   121  		},
   122  		{
   123  			title: "juju deploy multiseries --base=ubuntu@18.04   # unsupported requested",
   124  			selector: BaseSelector{
   125  				requestedBase:  bionic,
   126  				supportedBases: []base.Base{utopic, vivid},
   127  			},
   128  			err: `base: ubuntu@18.04/stable`,
   129  		},
   130  		{
   131  			title: "juju deploy multiseries    # fallback to base.LatestLTSBase()",
   132  			selector: BaseSelector{
   133  				supportedBases: []base.Base{utopic, vivid, latest},
   134  			},
   135  			expectedBase: latest,
   136  		},
   137  		{
   138  			title: "juju deploy multiseries    # fallback to version.DefaultSupportedLTSBase()",
   139  			selector: BaseSelector{
   140  				supportedBases: []base.Base{utopic, vivid, jujuDefault},
   141  			},
   142  			expectedBase: jujuDefault,
   143  		},
   144  		{
   145  			title: "juju deploy multiseries    # prefer base.LatestLTSBase() to  version.DefaultSupportedLTSBase()",
   146  			selector: BaseSelector{
   147  				supportedBases: []base.Base{utopic, vivid, jujuDefault, latest},
   148  			},
   149  			expectedBase: latest,
   150  		},
   151  	}
   152  
   153  	// Use bionic for LTS for all calls.
   154  	previous := base.SetLatestLtsForTesting("focal")
   155  	defer base.SetLatestLtsForTesting(previous)
   156  
   157  	for i, test := range deployBasesTests {
   158  		c.Logf("test %d [%s]", i, test.title)
   159  		test.selector.logger = s.logger
   160  		base, err := test.selector.CharmBase()
   161  		if test.err != "" {
   162  			c.Check(err, gc.ErrorMatches, test.err)
   163  		} else {
   164  			c.Check(err, jc.ErrorIsNil)
   165  			c.Check(base.IsCompatible(test.expectedBase), jc.IsTrue,
   166  				gc.Commentf("%q compatible with %q", base, test.expectedBase))
   167  		}
   168  	}
   169  }
   170  
   171  func (s *baseSelectorSuite) TestValidate(c *gc.C) {
   172  	defer s.setup(c).Finish()
   173  
   174  	deploySeriesTests := []struct {
   175  		title               string
   176  		selector            BaseSelector
   177  		supportedCharmBases []base.Base
   178  		supportedJujuBases  []base.Base
   179  		err                 string
   180  	}{{
   181  		// Simple selectors first, no supported bases, check we're validating
   182  		title:    "juju deploy simple   # no default base, no supported base",
   183  		selector: BaseSelector{},
   184  		err:      "charm does not define any bases, not valid",
   185  	}, {
   186  		title: "should fail when image-id constraint is used and no base is explicitly set",
   187  		selector: BaseSelector{
   188  			usingImageID: true,
   189  		},
   190  		err: "base must be explicitly provided when image-id constraint is used",
   191  	}, {
   192  		title: "should return no errors when using image-id and base flag",
   193  		selector: BaseSelector{
   194  			requestedBase: jammy,
   195  			usingImageID:  true,
   196  		},
   197  		supportedJujuBases:  []base.Base{jammy, bionic},
   198  		supportedCharmBases: []base.Base{jammy, bionic},
   199  	}, {
   200  		title: "should return no errors when using image-id and explicit base from Config",
   201  		selector: BaseSelector{
   202  			explicitDefaultBase: true,
   203  			usingImageID:        true,
   204  		},
   205  		supportedJujuBases:  []base.Base{jammy, bionic},
   206  		supportedCharmBases: []base.Base{jammy, bionic},
   207  	}, {
   208  		title:               "juju deploy multiseries with invalid series  # use charm default, nothing specified, no default base",
   209  		selector:            BaseSelector{},
   210  		supportedCharmBases: []base.Base{precise},
   211  		supportedJujuBases:  []base.Base{jammy},
   212  		err:                 `the charm defined bases "ubuntu@14.04" not supported`,
   213  	}}
   214  
   215  	for i, test := range deploySeriesTests {
   216  		c.Logf("test %d [%s]", i, test.title)
   217  		test.selector.logger = s.logger
   218  		test.selector.jujuSupportedBases = set.NewStrings()
   219  		_, err := test.selector.validate(test.supportedCharmBases, test.supportedJujuBases)
   220  		if test.err != "" {
   221  			c.Check(err, gc.ErrorMatches, test.err)
   222  		} else {
   223  			c.Check(err, jc.ErrorIsNil)
   224  		}
   225  	}
   226  }
   227  
   228  func (s *baseSelectorSuite) TestConfigureBaseSelector(c *gc.C) {
   229  	defer s.setup(c).Finish()
   230  
   231  	s.cfg.EXPECT().DefaultBase()
   232  	cfg := SelectorConfig{
   233  		Config:              s.cfg,
   234  		Force:               false,
   235  		Logger:              s.logger,
   236  		RequestedBase:       base.Base{},
   237  		SupportedCharmBases: []base.Base{jammy, focal, bionic},
   238  		WorkloadBases:       []base.Base{jammy, focal},
   239  		UsingImageID:        false,
   240  	}
   241  
   242  	obtained, err := ConfigureBaseSelector(cfg)
   243  	c.Assert(err, jc.ErrorIsNil)
   244  	c.Check(obtained.supportedBases, gc.DeepEquals, []base.Base{jammy, focal})
   245  }
   246  
   247  func (s *baseSelectorSuite) TestConfigureBaseSelectorCentos(c *gc.C) {
   248  	defer s.setup(c).Finish()
   249  
   250  	s.cfg.EXPECT().DefaultBase()
   251  	c7 := base.MustParseBaseFromString("centos@7/stable")
   252  	c8 := base.MustParseBaseFromString("centos@8/stable")
   253  	c6 := base.MustParseBaseFromString("centos@6/stable")
   254  	cfg := SelectorConfig{
   255  		Config:              s.cfg,
   256  		Force:               false,
   257  		Logger:              s.logger,
   258  		RequestedBase:       base.Base{},
   259  		SupportedCharmBases: []base.Base{c6, c7, c8},
   260  		WorkloadBases:       []base.Base{c7, c8},
   261  		UsingImageID:        false,
   262  	}
   263  
   264  	obtained, err := ConfigureBaseSelector(cfg)
   265  	c.Assert(err, jc.ErrorIsNil)
   266  	c.Check(obtained.supportedBases, gc.DeepEquals, []base.Base{c7, c8})
   267  }
   268  
   269  func (s *baseSelectorSuite) TestConfigureBaseSelectorDefaultBase(c *gc.C) {
   270  	defer s.setup(c).Finish()
   271  
   272  	s.cfg.EXPECT().DefaultBase().Return("ubuntu@20.04", true)
   273  	cfg := SelectorConfig{
   274  		Config:              s.cfg,
   275  		Force:               false,
   276  		Logger:              s.logger,
   277  		RequestedBase:       base.Base{},
   278  		SupportedCharmBases: []base.Base{jammy, focal, bionic},
   279  		WorkloadBases:       []base.Base{jammy, focal},
   280  		UsingImageID:        false,
   281  	}
   282  
   283  	baseSelector, err := ConfigureBaseSelector(cfg)
   284  	c.Assert(err, jc.ErrorIsNil)
   285  	c.Check(baseSelector.supportedBases, jc.SameContents, []base.Base{jammy, focal})
   286  	c.Check(baseSelector.defaultBase, gc.DeepEquals, focal)
   287  	c.Check(baseSelector.explicitDefaultBase, jc.IsTrue)
   288  
   289  	obtained, err := baseSelector.CharmBase()
   290  	c.Assert(err, jc.ErrorIsNil)
   291  	expectedBase := base.MustParseBaseFromString("ubuntu@20.04")
   292  	c.Check(obtained.IsCompatible(expectedBase), jc.IsTrue, gc.Commentf("obtained: %q, expected %q", obtained, expectedBase))
   293  }
   294  
   295  func (s *baseSelectorSuite) TestConfigureBaseSelectorDefaultBaseFail(c *gc.C) {
   296  	defer s.setup(c).Finish()
   297  
   298  	s.cfg.EXPECT().DefaultBase().Return("ubuntu@18.04", true)
   299  	cfg := SelectorConfig{
   300  		Config:              s.cfg,
   301  		Force:               false,
   302  		Logger:              s.logger,
   303  		RequestedBase:       base.Base{},
   304  		SupportedCharmBases: []base.Base{jammy, focal, bionic},
   305  		WorkloadBases:       []base.Base{jammy, focal},
   306  		UsingImageID:        false,
   307  	}
   308  
   309  	baseSelector, err := ConfigureBaseSelector(cfg)
   310  	c.Assert(err, jc.ErrorIsNil)
   311  	_, err = baseSelector.CharmBase()
   312  	c.Assert(err, gc.ErrorMatches, `base: ubuntu@18.04/stable`)
   313  }