github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/lxd/server_integration_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lxd_test
     5  
     6  import (
     7  	"net"
     8  	"net/url"
     9  	"os"
    10  	"syscall"
    11  
    12  	client "github.com/canonical/lxd/client"
    13  	"github.com/canonical/lxd/shared/api"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/testing"
    16  	jc "github.com/juju/testing/checkers"
    17  	"go.uber.org/mock/gomock"
    18  	gc "gopkg.in/check.v1"
    19  
    20  	"github.com/juju/juju/cloud"
    21  	environscloudspec "github.com/juju/juju/environs/cloudspec"
    22  	"github.com/juju/juju/provider/lxd"
    23  )
    24  
    25  var (
    26  	_ = gc.Suite(&serverIntegrationSuite{})
    27  )
    28  
    29  // serverIntegrationSuite tests server module functionality from outside the
    30  // lxd package. See server_test.go for package-local unit tests.
    31  type serverIntegrationSuite struct {
    32  	testing.IsolationSuite
    33  }
    34  
    35  func (s *serverIntegrationSuite) TestLocalServer(c *gc.C) {
    36  	ctrl := gomock.NewController(c)
    37  	defer ctrl.Finish()
    38  
    39  	profile := &api.Profile{}
    40  	etag := "etag"
    41  	bridgeName := "lxdbr0"
    42  	hostAddress := "192.168.0.1"
    43  	connectionInfo := &client.ConnectionInfo{
    44  		Addresses: []string{
    45  			"https://192.168.0.1:8443",
    46  		},
    47  	}
    48  
    49  	factory, server, interfaceAddr := lxd.NewLocalServerFactory(ctrl)
    50  
    51  	gomock.InOrder(
    52  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
    53  		server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil),
    54  		server.EXPECT().EnableHTTPSListener().Return(nil),
    55  		server.EXPECT().LocalBridgeName().Return(bridgeName),
    56  		interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil),
    57  		server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil),
    58  		server.EXPECT().StorageSupported().Return(true),
    59  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
    60  		server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil),
    61  		server.EXPECT().ServerVersion().Return("5.2"),
    62  	)
    63  
    64  	svr, err := factory.LocalServer()
    65  	c.Assert(svr, gc.Not(gc.IsNil))
    66  	c.Assert(svr, gc.Equals, server)
    67  	c.Assert(err, gc.IsNil)
    68  }
    69  
    70  func (s *serverIntegrationSuite) TestLocalServerRetrySemantics(c *gc.C) {
    71  	ctrl := gomock.NewController(c)
    72  	defer ctrl.Finish()
    73  
    74  	profile := &api.Profile{}
    75  	etag := "etag"
    76  	bridgeName := "lxdbr0"
    77  	hostAddress := "192.168.0.1"
    78  	emptyConnectionInfo := &client.ConnectionInfo{
    79  		Addresses: []string{},
    80  	}
    81  	connectionInfo := &client.ConnectionInfo{
    82  		Addresses: []string{
    83  			"https://192.168.0.1:8443",
    84  		},
    85  	}
    86  
    87  	factory, server, interfaceAddr := lxd.NewLocalServerFactory(ctrl)
    88  
    89  	gomock.InOrder(
    90  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
    91  		server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil),
    92  		server.EXPECT().EnableHTTPSListener().Return(nil),
    93  		server.EXPECT().LocalBridgeName().Return(bridgeName),
    94  		interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil),
    95  		server.EXPECT().GetConnectionInfo().Return(emptyConnectionInfo, nil),
    96  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
    97  		server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil),
    98  		server.EXPECT().EnableHTTPSListener().Return(nil),
    99  		server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil),
   100  		server.EXPECT().StorageSupported().Return(true),
   101  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
   102  		server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil),
   103  		server.EXPECT().ServerVersion().Return("5.2"),
   104  	)
   105  
   106  	svr, err := factory.LocalServer()
   107  	c.Assert(svr, gc.Not(gc.IsNil))
   108  	c.Assert(svr, gc.Equals, server)
   109  	c.Assert(err, gc.IsNil)
   110  }
   111  
   112  func (s *serverIntegrationSuite) TestLocalServerRetrySemanticsFailure(c *gc.C) {
   113  	ctrl := gomock.NewController(c)
   114  	defer ctrl.Finish()
   115  
   116  	profile := &api.Profile{}
   117  	etag := "etag"
   118  	bridgeName := "lxdbr0"
   119  	hostAddress := "192.168.0.1"
   120  	emptyConnectionInfo := &client.ConnectionInfo{
   121  		Addresses: []string{},
   122  	}
   123  
   124  	factory, server, interfaceAddr := lxd.NewLocalServerFactory(ctrl)
   125  
   126  	server.EXPECT().GetProfile("default").Return(profile, etag, nil).Times(31)
   127  	server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil).Times(31)
   128  	server.EXPECT().EnableHTTPSListener().Return(nil).Times(31)
   129  	server.EXPECT().LocalBridgeName().Return(bridgeName)
   130  	interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil)
   131  	server.EXPECT().GetConnectionInfo().Return(emptyConnectionInfo, nil).Times(30)
   132  
   133  	svr, err := factory.LocalServer()
   134  	c.Assert(svr, gc.IsNil)
   135  	c.Assert(err, gc.NotNil)
   136  	c.Assert(err.Error(), gc.Equals, "LXD is not listening on address https://192.168.0.1 (reported addresses: [])")
   137  }
   138  
   139  func (s *serverIntegrationSuite) TestLocalServerWithInvalidAPIVersion(c *gc.C) {
   140  	ctrl := gomock.NewController(c)
   141  	defer ctrl.Finish()
   142  
   143  	profile := &api.Profile{}
   144  	etag := "etag"
   145  	bridgeName := "lxdbr0"
   146  	hostAddress := "192.168.0.1"
   147  	connectionInfo := &client.ConnectionInfo{
   148  		Addresses: []string{
   149  			"https://192.168.0.1:8443",
   150  		},
   151  	}
   152  
   153  	factory, server, interfaceAddr := lxd.NewLocalServerFactory(ctrl)
   154  
   155  	gomock.InOrder(
   156  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
   157  		server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil),
   158  		server.EXPECT().EnableHTTPSListener().Return(nil),
   159  		server.EXPECT().LocalBridgeName().Return(bridgeName),
   160  		interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil),
   161  		server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil),
   162  		server.EXPECT().StorageSupported().Return(true),
   163  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
   164  		server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil),
   165  		server.EXPECT().ServerVersion().Return("a.b"),
   166  	)
   167  
   168  	svr, err := factory.LocalServer()
   169  	c.Assert(svr, gc.Not(gc.IsNil))
   170  	c.Assert(svr, gc.Equals, server)
   171  	c.Assert(err, gc.IsNil)
   172  }
   173  
   174  func (s *serverIntegrationSuite) TestLocalServerErrorMessageShowsInstallMessage(c *gc.C) {
   175  	ctrl := gomock.NewController(c)
   176  	defer ctrl.Finish()
   177  
   178  	factory := lxd.NewServerFactoryWithMocks(
   179  		func() (lxd.Server, error) {
   180  			return nil, errors.New("bad")
   181  		},
   182  		lxd.DefaultRemoteServerFunc(ctrl),
   183  		nil,
   184  		&lxd.MockClock{},
   185  	)
   186  
   187  	_, err := factory.LocalServer()
   188  	c.Assert(errors.Cause(err).Error(), gc.Equals, `bad
   189  
   190  Please install LXD by running:
   191  	$ sudo snap install lxd
   192  and then configure it with:
   193  	$ newgrp lxd
   194  	$ lxd init
   195  `)
   196  }
   197  
   198  func (s *serverIntegrationSuite) TestLocalServerErrorMessageShowsConfigureMessage(c *gc.C) {
   199  	ctrl := gomock.NewController(c)
   200  	defer ctrl.Finish()
   201  
   202  	factory := lxd.NewServerFactoryWithMocks(
   203  		func() (lxd.Server, error) {
   204  			return nil, errors.Annotatef(&url.Error{
   205  				Err: &net.OpError{
   206  					Op:  "dial",
   207  					Net: "unix",
   208  					Err: &os.SyscallError{
   209  						Err: syscall.ECONNREFUSED,
   210  					},
   211  				},
   212  			}, "bad")
   213  		},
   214  		lxd.DefaultRemoteServerFunc(ctrl),
   215  		nil,
   216  		&lxd.MockClock{},
   217  	)
   218  
   219  	_, err := factory.LocalServer()
   220  	c.Assert(errors.Cause(err).Error(), gc.Equals, `LXD refused connections; is LXD running?
   221  
   222  Please configure LXD by running:
   223  	$ newgrp lxd
   224  	$ lxd init
   225  `)
   226  }
   227  
   228  func (s *serverIntegrationSuite) TestLocalServerErrorMessageShowsConfigureMessageWhenEACCES(c *gc.C) {
   229  	ctrl := gomock.NewController(c)
   230  	defer ctrl.Finish()
   231  
   232  	factory := lxd.NewServerFactoryWithMocks(
   233  		func() (lxd.Server, error) {
   234  			return nil, errors.Annotatef(&url.Error{
   235  				Err: &net.OpError{
   236  					Op:  "dial",
   237  					Net: "unix",
   238  					Err: &os.SyscallError{
   239  						Err: syscall.EACCES,
   240  					},
   241  				},
   242  			}, "bad")
   243  		},
   244  		lxd.DefaultRemoteServerFunc(ctrl),
   245  		nil,
   246  		&lxd.MockClock{},
   247  	)
   248  
   249  	_, err := factory.LocalServer()
   250  	c.Assert(errors.Cause(err).Error(), gc.Equals, `Permission denied, are you in the lxd group?
   251  
   252  Please configure LXD by running:
   253  	$ newgrp lxd
   254  	$ lxd init
   255  `)
   256  }
   257  
   258  func (s *serverIntegrationSuite) TestLocalServerErrorMessageShowsInstallMessageWhenENOENT(c *gc.C) {
   259  	ctrl := gomock.NewController(c)
   260  	defer ctrl.Finish()
   261  
   262  	factory := lxd.NewServerFactoryWithMocks(
   263  		func() (lxd.Server, error) {
   264  			return nil, errors.Annotatef(&url.Error{
   265  				Err: &net.OpError{
   266  					Op:  "dial",
   267  					Net: "unix",
   268  					Err: &os.SyscallError{
   269  						Err: syscall.ENOENT,
   270  					},
   271  				},
   272  			}, "bad")
   273  		},
   274  		lxd.DefaultRemoteServerFunc(ctrl),
   275  		nil,
   276  		&lxd.MockClock{},
   277  	)
   278  
   279  	_, err := factory.LocalServer()
   280  	c.Assert(errors.Cause(err).Error(), gc.Equals, `LXD socket not found; is LXD installed & running?
   281  
   282  Please install LXD by running:
   283  	$ sudo snap install lxd
   284  and then configure it with:
   285  	$ newgrp lxd
   286  	$ lxd init
   287  `)
   288  }
   289  
   290  func (s *serverIntegrationSuite) TestLocalServerWithStorageNotSupported(c *gc.C) {
   291  	ctrl := gomock.NewController(c)
   292  	defer ctrl.Finish()
   293  
   294  	profile := &api.Profile{}
   295  	etag := "etag"
   296  	bridgeName := "lxdbr0"
   297  	hostAddress := "192.168.0.1"
   298  	connectionInfo := &client.ConnectionInfo{
   299  		Addresses: []string{
   300  			"https://192.168.0.1:8443",
   301  		},
   302  	}
   303  
   304  	factory, server, interfaceAddr := lxd.NewLocalServerFactory(ctrl)
   305  
   306  	gomock.InOrder(
   307  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
   308  		server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil),
   309  		server.EXPECT().EnableHTTPSListener().Return(nil),
   310  		server.EXPECT().LocalBridgeName().Return(bridgeName),
   311  		interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil),
   312  		server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil),
   313  		server.EXPECT().StorageSupported().Return(false),
   314  		server.EXPECT().ServerVersion().Return("5.2"),
   315  	)
   316  
   317  	svr, err := factory.RemoteServer(lxd.CloudSpec{})
   318  	c.Assert(svr, gc.Not(gc.IsNil))
   319  	c.Assert(err, gc.IsNil)
   320  }
   321  
   322  func (s *serverIntegrationSuite) TestRemoteServerWithEmptyEndpointYieldsLocalServer(c *gc.C) {
   323  	ctrl := gomock.NewController(c)
   324  	defer ctrl.Finish()
   325  
   326  	profile := &api.Profile{}
   327  	etag := "etag"
   328  	bridgeName := "lxdbr0"
   329  	hostAddress := "192.168.0.1"
   330  	connectionInfo := &client.ConnectionInfo{
   331  		Addresses: []string{
   332  			"https://192.168.0.1:8443",
   333  		},
   334  	}
   335  
   336  	factory, server, interfaceAddr := lxd.NewLocalServerFactory(ctrl)
   337  
   338  	gomock.InOrder(
   339  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
   340  		server.EXPECT().VerifyNetworkDevice(profile, etag).Return(nil),
   341  		server.EXPECT().EnableHTTPSListener().Return(nil),
   342  		server.EXPECT().LocalBridgeName().Return(bridgeName),
   343  		interfaceAddr.EXPECT().InterfaceAddress(bridgeName).Return(hostAddress, nil),
   344  		server.EXPECT().GetConnectionInfo().Return(connectionInfo, nil),
   345  		server.EXPECT().StorageSupported().Return(true),
   346  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
   347  		server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil),
   348  		server.EXPECT().ServerVersion().Return("5.2"),
   349  	)
   350  
   351  	svr, err := factory.RemoteServer(lxd.CloudSpec{})
   352  	c.Assert(svr, gc.Not(gc.IsNil))
   353  	c.Assert(err, gc.IsNil)
   354  }
   355  
   356  func (s *serverIntegrationSuite) TestRemoteServer(c *gc.C) {
   357  	ctrl := gomock.NewController(c)
   358  	defer ctrl.Finish()
   359  
   360  	profile := &api.Profile{}
   361  	etag := "etag"
   362  
   363  	factory, server := lxd.NewRemoteServerFactory(ctrl)
   364  
   365  	gomock.InOrder(
   366  		server.EXPECT().StorageSupported().Return(true),
   367  		server.EXPECT().GetProfile("default").Return(profile, etag, nil),
   368  		server.EXPECT().EnsureDefaultStorage(profile, etag).Return(nil),
   369  		server.EXPECT().ServerVersion().Return("5.2"),
   370  	)
   371  
   372  	creds := cloud.NewCredential("any", map[string]string{
   373  		"client-cert": "client-cert",
   374  		"client-key":  "client-key",
   375  		"server-cert": "server-cert",
   376  	})
   377  	svr, err := factory.RemoteServer(
   378  		lxd.CloudSpec{
   379  			CloudSpec: environscloudspec.CloudSpec{
   380  				Endpoint:   "https://10.0.0.9:8443",
   381  				Credential: &creds,
   382  			},
   383  		})
   384  	c.Assert(svr, gc.Not(gc.IsNil))
   385  	c.Assert(svr, gc.Equals, server)
   386  	c.Assert(err, gc.IsNil)
   387  }
   388  
   389  func (s *serverIntegrationSuite) TestRemoteServerWithNoStorage(c *gc.C) {
   390  	ctrl := gomock.NewController(c)
   391  	defer ctrl.Finish()
   392  
   393  	factory, server := lxd.NewRemoteServerFactory(ctrl)
   394  
   395  	gomock.InOrder(
   396  		server.EXPECT().StorageSupported().Return(false),
   397  		server.EXPECT().ServerVersion().Return("5.2"),
   398  	)
   399  
   400  	creds := cloud.NewCredential("any", map[string]string{
   401  		"client-cert": "client-cert",
   402  		"client-key":  "client-key",
   403  		"server-cert": "server-cert",
   404  	})
   405  	svr, err := factory.RemoteServer(
   406  		lxd.CloudSpec{
   407  			CloudSpec: environscloudspec.CloudSpec{
   408  				Endpoint:   "https://10.0.0.9:8443",
   409  				Credential: &creds,
   410  			},
   411  		})
   412  	c.Assert(svr, gc.Not(gc.IsNil))
   413  	c.Assert(svr, gc.Equals, server)
   414  	c.Assert(err, gc.IsNil)
   415  }
   416  
   417  func (s *serverIntegrationSuite) TestInsecureRemoteServerDoesNotCallGetServer(c *gc.C) {
   418  	ctrl := gomock.NewController(c)
   419  	defer ctrl.Finish()
   420  
   421  	factory, server := lxd.NewRemoteServerFactory(ctrl)
   422  
   423  	creds := cloud.NewCredential("any", map[string]string{
   424  		"client-cert": "client-cert",
   425  		"client-key":  "client-key",
   426  		"server-cert": "server-cert",
   427  	})
   428  	svr, err := factory.InsecureRemoteServer(
   429  		lxd.CloudSpec{
   430  			CloudSpec: environscloudspec.CloudSpec{
   431  				Endpoint:   "https://10.0.0.9:8443",
   432  				Credential: &creds,
   433  			},
   434  		})
   435  	c.Assert(svr, gc.Not(gc.IsNil))
   436  	c.Assert(svr, gc.Equals, server)
   437  	c.Assert(err, gc.IsNil)
   438  }
   439  
   440  func (s *serverIntegrationSuite) TestRemoteServerMissingCertificates(c *gc.C) {
   441  	ctrl := gomock.NewController(c)
   442  	defer ctrl.Finish()
   443  
   444  	factory, _ := lxd.NewRemoteServerFactory(ctrl)
   445  
   446  	creds := cloud.NewCredential("any", map[string]string{})
   447  	svr, err := factory.RemoteServer(
   448  		lxd.CloudSpec{
   449  			CloudSpec: environscloudspec.CloudSpec{
   450  				Endpoint:   "https://10.0.0.9:8443",
   451  				Credential: &creds,
   452  			},
   453  		})
   454  	c.Assert(svr, gc.IsNil)
   455  	c.Assert(errors.Cause(err).Error(), gc.Equals, "credentials not valid")
   456  }
   457  
   458  func (s *serverIntegrationSuite) TestRemoteServerBadServerFuncError(c *gc.C) {
   459  	factory := lxd.NewServerFactoryWithError()
   460  
   461  	creds := cloud.NewCredential("any", map[string]string{
   462  		"client-cert": "client-cert",
   463  		"client-key":  "client-key",
   464  		"server-cert": "server-cert",
   465  	})
   466  	svr, err := factory.RemoteServer(
   467  		lxd.CloudSpec{
   468  			CloudSpec: environscloudspec.CloudSpec{
   469  				Endpoint:   "https://10.0.0.9:8443",
   470  				Credential: &creds,
   471  			},
   472  		})
   473  	c.Assert(svr, gc.IsNil)
   474  	c.Assert(errors.Cause(err).Error(), gc.Equals, "oops")
   475  }
   476  
   477  func (s *serverIntegrationSuite) TestRemoteServerWithUnSupportedAPIVersion(c *gc.C) {
   478  	ctrl := gomock.NewController(c)
   479  	defer ctrl.Finish()
   480  
   481  	factory, server := lxd.NewRemoteServerFactory(ctrl)
   482  
   483  	gomock.InOrder(
   484  		server.EXPECT().StorageSupported().Return(false),
   485  		server.EXPECT().ServerVersion().Return("4.0"),
   486  	)
   487  
   488  	creds := cloud.NewCredential("any", map[string]string{
   489  		"client-cert": "client-cert",
   490  		"client-key":  "client-key",
   491  		"server-cert": "server-cert",
   492  	})
   493  	_, err := factory.RemoteServer(
   494  		lxd.CloudSpec{
   495  			CloudSpec: environscloudspec.CloudSpec{
   496  				Endpoint:   "https://10.0.0.9:8443",
   497  				Credential: &creds,
   498  			},
   499  		})
   500  	c.Assert(errors.Cause(err).Error(), gc.Equals, `LXD version has to be at least "5.0.0", but current version is only "4.0.0"`)
   501  }
   502  
   503  func (s *serverIntegrationSuite) TestIsSupportedAPIVersion(c *gc.C) {
   504  	for _, t := range []struct {
   505  		input  string
   506  		output string
   507  	}{
   508  		{
   509  			input:  "foo",
   510  			output: `LXD API version "foo": expected format <major>\.<minor>`,
   511  		},
   512  		{
   513  			input:  "a.b",
   514  			output: `major version number  a not valid`,
   515  		},
   516  		{
   517  			input:  "4.0",
   518  			output: `LXD version has to be at least "5.0.0", but current version is only "4.0.0"`,
   519  		},
   520  		{
   521  			input:  "5.0",
   522  			output: "",
   523  		},
   524  	} {
   525  		err := lxd.ValidateAPIVersion(t.input)
   526  		if t.output == "" {
   527  			c.Check(err, jc.ErrorIsNil)
   528  		} else {
   529  			c.Check(err, gc.ErrorMatches, t.output)
   530  		}
   531  	}
   532  }