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