github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/specs/base_test.go (about)

     1  // Copyright 2019 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package specs_test
     5  
     6  import (
     7  	jc "github.com/juju/testing/checkers"
     8  	gc "gopkg.in/check.v1"
     9  
    10  	"github.com/juju/juju/caas/specs"
    11  	"github.com/juju/juju/testing"
    12  )
    13  
    14  type baseSuite struct {
    15  	testing.BaseSuite
    16  }
    17  
    18  var _ = gc.Suite(&baseSuite{})
    19  
    20  type validator interface {
    21  	Validate() error
    22  }
    23  
    24  type validateTc struct {
    25  	spec   validator
    26  	errStr string
    27  }
    28  
    29  func (s *baseSuite) TestGetVersion(c *gc.C) {
    30  	type testcase struct {
    31  		strSpec string
    32  		version specs.Version
    33  	}
    34  
    35  	for i, tc := range []testcase{
    36  		{
    37  			strSpec: `
    38  `[1:],
    39  			version: specs.Version(0),
    40  		},
    41  		{
    42  			strSpec: `
    43  version: 0
    44  `[1:],
    45  			version: specs.Version(0),
    46  		},
    47  		{
    48  			strSpec: `
    49  version: 2
    50  `[1:],
    51  			version: specs.Version(2),
    52  		},
    53  		{
    54  			strSpec: `
    55  version: 3
    56  `[1:],
    57  			version: specs.Version(3),
    58  		},
    59  	} {
    60  		c.Logf("#%d: testing GetVersion: %d", i, tc.version)
    61  		v, err := specs.GetVersion(tc.strSpec)
    62  		c.Check(err, jc.ErrorIsNil)
    63  		c.Check(v, gc.DeepEquals, tc.version)
    64  	}
    65  }
    66  
    67  func (s *baseSuite) TestValidateServiceSpec(c *gc.C) {
    68  	spec := specs.ServiceSpec{
    69  		ScalePolicy: "bar",
    70  	}
    71  	c.Assert(spec.Validate(), gc.ErrorMatches, `scale policy "bar" not supported`)
    72  
    73  	spec = specs.ServiceSpec{
    74  		ScalePolicy: "parallel",
    75  	}
    76  	c.Assert(spec.Validate(), jc.ErrorIsNil)
    77  
    78  	spec = specs.ServiceSpec{
    79  		ScalePolicy: "serial",
    80  	}
    81  	c.Assert(spec.Validate(), jc.ErrorIsNil)
    82  
    83  	spec = specs.ServiceSpec{
    84  		UpdateStrategy: &specs.UpdateStrategy{
    85  			Type: "Recreate",
    86  			RollingUpdate: &specs.RollingUpdateSpec{
    87  				MaxUnavailable: &specs.IntOrString{Type: specs.String, StrVal: "10%"},
    88  				MaxSurge:       &specs.IntOrString{Type: specs.String, StrVal: "25%"},
    89  			},
    90  		},
    91  	}
    92  	c.Assert(spec.Validate(), jc.ErrorIsNil)
    93  
    94  	spec = specs.ServiceSpec{
    95  		UpdateStrategy: &specs.UpdateStrategy{
    96  			Type: "",
    97  			RollingUpdate: &specs.RollingUpdateSpec{
    98  				MaxUnavailable: &specs.IntOrString{Type: specs.String, StrVal: "10%"},
    99  				MaxSurge:       &specs.IntOrString{Type: specs.String, StrVal: "25%"},
   100  			},
   101  		},
   102  	}
   103  	c.Assert(spec.Validate(), gc.ErrorMatches, `type is required`)
   104  
   105  	spec = specs.ServiceSpec{
   106  		UpdateStrategy: &specs.UpdateStrategy{
   107  			Type: "Recreate",
   108  		},
   109  	}
   110  	c.Assert(spec.Validate(), jc.ErrorIsNil)
   111  
   112  	var partition int32 = 3
   113  	spec = specs.ServiceSpec{
   114  		UpdateStrategy: &specs.UpdateStrategy{
   115  			Type: "Recreate",
   116  			RollingUpdate: &specs.RollingUpdateSpec{
   117  				Partition: &partition,
   118  				MaxSurge:  &specs.IntOrString{Type: specs.String, StrVal: "25%"},
   119  			},
   120  		},
   121  	}
   122  	c.Assert(spec.Validate(), gc.ErrorMatches, `partion can not be defined with maxUnavailable or maxSurge together`)
   123  
   124  	spec = specs.ServiceSpec{
   125  		UpdateStrategy: &specs.UpdateStrategy{
   126  			Type: "Recreate",
   127  			RollingUpdate: &specs.RollingUpdateSpec{
   128  				Partition:      &partition,
   129  				MaxUnavailable: &specs.IntOrString{Type: specs.String, StrVal: "10%"},
   130  			},
   131  		},
   132  	}
   133  	c.Assert(spec.Validate(), gc.ErrorMatches, `partion can not be defined with maxUnavailable or maxSurge together`)
   134  
   135  	spec = specs.ServiceSpec{
   136  		UpdateStrategy: &specs.UpdateStrategy{
   137  			Type: "Recreate",
   138  			RollingUpdate: &specs.RollingUpdateSpec{
   139  				Partition:      &partition,
   140  				MaxUnavailable: &specs.IntOrString{Type: specs.String, StrVal: "10%"},
   141  				MaxSurge:       &specs.IntOrString{Type: specs.String, StrVal: "25%"},
   142  			},
   143  		},
   144  	}
   145  	c.Assert(spec.Validate(), gc.ErrorMatches, `partion can not be defined with maxUnavailable or maxSurge together`)
   146  }
   147  
   148  func (s *baseSuite) TestValidateContainerSpec(c *gc.C) {
   149  	for i, tc := range []validateTc{
   150  		{
   151  			spec: &specs.ContainerSpec{
   152  				Name: "container1",
   153  			},
   154  			errStr: `spec image details is missing`,
   155  		},
   156  		{
   157  			spec: &specs.ContainerSpec{
   158  				Image: "gitlab",
   159  			},
   160  			errStr: `spec name is missing`,
   161  		},
   162  		{
   163  			spec: &specs.ContainerSpec{
   164  				ImageDetails: specs.ImageDetails{
   165  					ImagePath: "gitlab",
   166  				},
   167  			},
   168  			errStr: `spec name is missing`,
   169  		},
   170  		{
   171  			spec: &specs.ContainerSpec{
   172  				Name:  "container1",
   173  				Image: "gitlab",
   174  			},
   175  			errStr: "",
   176  		},
   177  		{
   178  			spec: &specs.ContainerSpec{
   179  				Name: "container1",
   180  				ImageDetails: specs.ImageDetails{
   181  					ImagePath: "gitlab",
   182  				},
   183  			},
   184  			errStr: "",
   185  		},
   186  	} {
   187  		c.Logf("#%d: testing FileSet.Validate", i)
   188  		err := tc.spec.Validate()
   189  		if tc.errStr == "" {
   190  			c.Check(err, jc.ErrorIsNil)
   191  		} else {
   192  			c.Check(err, gc.ErrorMatches, tc.errStr)
   193  		}
   194  	}
   195  }
   196  
   197  func (s *baseSuite) TestValidatePodSpecBase(c *gc.C) {
   198  	minSpecs := specs.PodSpecBase{}
   199  	minSpecs.Containers = []specs.ContainerSpec{
   200  		{
   201  			Name:  "gitlab-helper",
   202  			Image: "gitlab-helper/latest",
   203  			Ports: []specs.ContainerPort{
   204  				{ContainerPort: 8080, Protocol: "TCP"},
   205  			},
   206  		},
   207  	}
   208  	c.Assert(minSpecs.Validate(specs.VersionLegacy), jc.ErrorIsNil)
   209  	minSpecs.Version = specs.VersionLegacy
   210  	c.Assert(minSpecs.Validate(specs.VersionLegacy), jc.ErrorIsNil)
   211  
   212  	c.Assert(minSpecs.Validate(specs.Version2), gc.ErrorMatches, `expected version 2, but found 0`)
   213  	minSpecs.Version = specs.Version2
   214  	c.Assert(minSpecs.Validate(specs.Version2), jc.ErrorIsNil)
   215  }
   216  
   217  func (s *baseSuite) TestValidateCaaSContainers(c *gc.C) {
   218  	k8sSpec := specs.CaasContainers{}
   219  	fileSet1 := specs.FileSet{
   220  		Name:      "file1",
   221  		MountPath: "/foo/file1",
   222  		VolumeSource: specs.VolumeSource{
   223  			Files: []specs.File{
   224  				{Path: "foo", Content: "bar"},
   225  			},
   226  		},
   227  	}
   228  	fileSet2 := specs.FileSet{
   229  		Name:      "file2",
   230  		MountPath: "/foo/file2",
   231  		VolumeSource: specs.VolumeSource{
   232  			Files: []specs.File{
   233  				{Path: "foo", Content: "bar"},
   234  			},
   235  		},
   236  	}
   237  
   238  	k8sSpec.Containers = []specs.ContainerSpec{
   239  		{
   240  			Name:  "gitlab-helper",
   241  			Image: "gitlab-helper/latest",
   242  			Ports: []specs.ContainerPort{
   243  				{ContainerPort: 8080, Protocol: "TCP"},
   244  			},
   245  			VolumeConfig: []specs.FileSet{
   246  				fileSet1, fileSet2,
   247  			},
   248  		},
   249  	}
   250  	c.Assert(k8sSpec.Validate(), jc.ErrorIsNil)
   251  
   252  	k8sSpec = specs.CaasContainers{}
   253  	k8sSpec.Containers = []specs.ContainerSpec{
   254  		{
   255  			Name:  "gitlab-helper",
   256  			Image: "gitlab-helper/latest",
   257  			Ports: []specs.ContainerPort{
   258  				{ContainerPort: 8080, Protocol: "TCP"},
   259  			},
   260  			VolumeConfig: []specs.FileSet{
   261  				fileSet1, fileSet1,
   262  			},
   263  		},
   264  	}
   265  	c.Assert(k8sSpec.Validate(), gc.ErrorMatches, `duplicated file "file1" in container "gitlab-helper" not valid`)
   266  
   267  	k8sSpec = specs.CaasContainers{}
   268  	k8sSpec.Containers = []specs.ContainerSpec{
   269  		{
   270  			Name:  "gitlab-helper",
   271  			Image: "gitlab-helper/latest",
   272  			Ports: []specs.ContainerPort{
   273  				{ContainerPort: 8080, Protocol: "TCP"},
   274  			},
   275  			VolumeConfig: []specs.FileSet{
   276  				{
   277  					Name:      "file1",
   278  					MountPath: "/same-mount-path",
   279  					VolumeSource: specs.VolumeSource{
   280  						Files: []specs.File{
   281  							{Path: "foo", Content: "bar"},
   282  						},
   283  					},
   284  				},
   285  				{
   286  					Name:      "file2",
   287  					MountPath: "/same-mount-path",
   288  					VolumeSource: specs.VolumeSource{
   289  						HostPath: &specs.HostPathVol{
   290  							Path: "/foo/bar",
   291  						},
   292  					},
   293  				},
   294  			},
   295  		},
   296  	}
   297  	c.Assert(k8sSpec.Validate(), gc.ErrorMatches, `duplicated mount path "/same-mount-path" in container "gitlab-helper" not valid`)
   298  
   299  	k8sSpec = specs.CaasContainers{}
   300  	k8sSpec.Containers = []specs.ContainerSpec{
   301  		{
   302  			Name:  "gitlab-helper",
   303  			Image: "gitlab-helper/latest",
   304  			Ports: []specs.ContainerPort{
   305  				{ContainerPort: 8080, Protocol: "TCP"},
   306  			},
   307  			VolumeConfig: []specs.FileSet{
   308  				{
   309  					Name:      "file1",
   310  					MountPath: "/etc/config",
   311  					VolumeSource: specs.VolumeSource{
   312  						Files: []specs.File{
   313  							{Path: "foo", Content: "bar"},
   314  						},
   315  					},
   316  				},
   317  			},
   318  		},
   319  		{
   320  			Name:  "busybox",
   321  			Image: "busybox",
   322  			Ports: []specs.ContainerPort{
   323  				{ContainerPort: 80, Protocol: "TCP"},
   324  			},
   325  			VolumeConfig: []specs.FileSet{
   326  				{
   327  					Name:      "file1",
   328  					MountPath: "/etc/config",
   329  					VolumeSource: specs.VolumeSource{
   330  						HostPath: &specs.HostPathVol{
   331  							Path: "/foo/bar",
   332  						},
   333  					},
   334  				},
   335  			},
   336  		},
   337  	}
   338  	c.Assert(k8sSpec.Validate(), gc.ErrorMatches, `duplicated file "file1" with different volume spec not valid`)
   339  
   340  	k8sSpec = specs.CaasContainers{}
   341  	k8sSpec.Containers = []specs.ContainerSpec{
   342  		{
   343  			Name:  "gitlab-helper",
   344  			Image: "gitlab-helper/latest",
   345  			Ports: []specs.ContainerPort{
   346  				{ContainerPort: 8080, Protocol: "TCP"},
   347  			},
   348  			VolumeConfig: []specs.FileSet{
   349  				{
   350  					Name:      "file1",
   351  					MountPath: "/foo/file1",
   352  					VolumeSource: specs.VolumeSource{
   353  						Files: []specs.File{
   354  							{Path: "foo", Content: "bar"},
   355  						},
   356  					},
   357  				},
   358  				{
   359  					Name:      "file1", // same file in same container mount to different path.
   360  					MountPath: "/foo/another-file1",
   361  					VolumeSource: specs.VolumeSource{
   362  						Files: []specs.File{
   363  							{Path: "foo", Content: "bar"},
   364  						},
   365  					},
   366  				},
   367  				{
   368  					Name:      "file2",
   369  					MountPath: "/foo/file2",
   370  					VolumeSource: specs.VolumeSource{
   371  						Files: []specs.File{
   372  							{Path: "foo", Content: "bar"},
   373  						},
   374  					},
   375  				},
   376  				{
   377  					Name:      "host-path-1",
   378  					MountPath: "/etc/host-path",
   379  					VolumeSource: specs.VolumeSource{
   380  						HostPath: &specs.HostPathVol{
   381  							Path: "/foo/bar",
   382  						},
   383  					},
   384  				},
   385  				{
   386  					Name:      "empty-dir-1",
   387  					MountPath: "/etc/empty-dir",
   388  					VolumeSource: specs.VolumeSource{
   389  						EmptyDir: &specs.EmptyDirVol{
   390  							Medium: "Memory",
   391  						},
   392  					},
   393  				},
   394  				{
   395  					Name:      "config-map-1",
   396  					MountPath: "/etc/config",
   397  					VolumeSource: specs.VolumeSource{
   398  						ConfigMap: &specs.ResourceRefVol{
   399  							Name: "log-config",
   400  							Files: []specs.FileRef{
   401  								{
   402  									Key:  "log_level",
   403  									Path: "log_level",
   404  								},
   405  							},
   406  						},
   407  					},
   408  				},
   409  				{
   410  					Name:      "mysecret2",
   411  					MountPath: "/secrets",
   412  					VolumeSource: specs.VolumeSource{
   413  						Secret: &specs.ResourceRefVol{
   414  							Name: "mysecret2",
   415  							Files: []specs.FileRef{
   416  								{
   417  									Key:  "password",
   418  									Path: "my-group/my-password",
   419  								},
   420  							},
   421  						},
   422  					},
   423  				},
   424  			},
   425  		},
   426  		{
   427  			Name:  "busybox",
   428  			Image: "busybox",
   429  			Ports: []specs.ContainerPort{
   430  				{ContainerPort: 80, Protocol: "TCP"},
   431  			},
   432  			VolumeConfig: []specs.FileSet{
   433  				{
   434  					Name:      "file1", // exact same file1 can be mounted to same path in a different container.
   435  					MountPath: "/foo/file1",
   436  					VolumeSource: specs.VolumeSource{
   437  						Files: []specs.File{
   438  							{Path: "foo", Content: "bar"},
   439  						},
   440  					},
   441  				},
   442  			},
   443  		},
   444  	}
   445  	c.Assert(k8sSpec.Validate(), jc.ErrorIsNil)
   446  }